//go:build integration package main import ( "archive/zip" "bytes" "context" "encoding/base64" "encoding/json" "fmt" "io" "math/rand" "mime/multipart" "net/http" "os" "sort" "strconv" "strings" "testing" "time" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/ape" containerv2 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/container" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" "git.frostfs.info/TrueCloudLab/policy-engine/schema/native" docker "github.com/docker/docker/api/types/container" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" ) type putResponse struct { CID string `json:"container_id"` OID string `json:"object_id"` } const ( testContainerName = "friendly" testListenAddress = "localhost:8082" testHost = "http://" + testListenAddress testCORSContainerName = "cors" ) func versionToUint(t *testing.T, v string) uint64 { parts := strings.Split(v, ".") require.Len(t, parts, 3) major, err := strconv.ParseUint(parts[0], 10, 16) require.NoError(t, err) minor, err := strconv.ParseUint(parts[1], 10, 16) require.NoError(t, err) patch, err := strconv.ParseUint(parts[2], 10, 32) require.NoError(t, err) versionNumber := major<<48 | minor<<32 | patch return versionNumber } func publicReadWriteRules() []chain.Rule { return []chain.Rule{ { Status: chain.Allow, Actions: chain.Actions{ Inverted: false, Names: []string{ native.MethodPutObject, native.MethodGetObject, native.MethodHeadObject, native.MethodDeleteObject, native.MethodSearchObject, native.MethodRangeObject, native.MethodHashObject, native.MethodPatchObject, }, }, Resources: chain.Resources{ Inverted: false, Names: []string{native.ResourceFormatRootObjects}, }, Any: false}, } } func privateRules() []chain.Rule { rule := publicReadWriteRules() // The same as public-read-write, except that only the owner is allowed to perform the listed actions rule[0].Condition = []chain.Condition{ { Op: chain.CondStringEquals, Kind: chain.KindRequest, Key: native.PropertyKeyActorRole, Value: native.PropertyValueContainerRoleOwner, }, } return rule } func TestIntegration(t *testing.T) { rootCtx := context.Background() aioImage := "git.frostfs.info/truecloudlab/frostfs-aio:" versions := []string{ "1.2.7", "1.3.0", "1.5.0", "1.6.5", "1.7.0", } key, err := keys.NewPrivateKeyFromHex("1dd37fba80fec4e6a6f13fd708d8dcb3b29def768017052f6c930fa1c5d90bbb") require.NoError(t, err) file, err := os.CreateTemp("", "wallet") require.NoError(t, err) defer os.Remove(file.Name()) makeTempWallet(t, key, file.Name()) var ownerID user.ID user.IDFromKey(&ownerID, key.PrivateKey.PublicKey) for _, version := range versions { ctx, cancel2 := context.WithCancel(rootCtx) aioContainer := createDockerContainer(ctx, t, aioImage+version) versionNumber := versionToUint(t, version) if versionNumber >= versionToUint(t, "1.6.0") { registerUser(t, ctx, aioContainer, file.Name()) } createContainer := createContainerWithACL if versionNumber >= versionToUint(t, "1.7.0") { createContainer = createContainerWithAPE } // Creating CORS container clientPool := getPool(ctx, t, key) _, err = createContainer(ctx, t, clientPool, ownerID, testCORSContainerName) require.NoError(t, err, version) // See the logs from the command execution. server, cancel := runServer(file.Name()) CID, err := createContainer(ctx, t, clientPool, ownerID, testContainerName) require.NoError(t, err, version) jsonToken, binaryToken := makeBearerTokens(t, key, ownerID, version) t.Run("simple put "+version, func(t *testing.T) { simplePut(ctx, t, clientPool, CID) }) t.Run("put with json bearer token in header"+version, func(t *testing.T) { putWithBearerTokenInHeader(ctx, t, clientPool, CID, jsonToken) }) t.Run("put with json bearer token in cookie"+version, func(t *testing.T) { putWithBearerTokenInCookie(ctx, t, clientPool, CID, jsonToken) }) t.Run("put with binary bearer token in header"+version, func(t *testing.T) { putWithBearerTokenInHeader(ctx, t, clientPool, CID, binaryToken) }) t.Run("put with binary bearer token in cookie"+version, func(t *testing.T) { putWithBearerTokenInCookie(ctx, t, clientPool, CID, binaryToken) }) t.Run("put with duplicate keys "+version, func(t *testing.T) { putWithDuplicateKeys(t, CID) }) t.Run("simple get "+version, func(t *testing.T) { simpleGet(ctx, t, clientPool, ownerID, CID) }) t.Run("get by attribute "+version, func(t *testing.T) { getByAttr(ctx, t, clientPool, ownerID, CID) }) t.Run("get zip "+version, func(t *testing.T) { getZip(ctx, t, clientPool, ownerID, CID) }) t.Run("test namespaces "+version, func(t *testing.T) { checkNamespaces(ctx, t, clientPool, ownerID, CID) }) t.Run("test status codes "+version, func(t *testing.T) { checkStatusCodes(ctx, t, clientPool, ownerID, version) }) cancel() server.Wait() err = aioContainer.Terminate(ctx) require.NoError(t, err) cancel2() } } func runServer(pathToWallet string) (App, context.CancelFunc) { cancelCtx, cancel := context.WithCancel(context.Background()) v := getDefaultConfig() v.config().Set(cfgWalletPath, pathToWallet) v.config().Set(cfgWalletPassphrase, "") v.config().Set(cfgContainersCORS, testCORSContainerName+"."+containerv2.SysAttributeZoneDefault) application := newApp(cancelCtx, v) go application.Serve() return application, cancel } func simplePut(ctx context.Context, t *testing.T, p *pool.Pool, CID cid.ID) { url := testHost + "/upload/" + CID.String() makePutRequestAndCheck(ctx, t, p, CID, url) url = testHost + "/upload/" + testContainerName makePutRequestAndCheck(ctx, t, p, CID, url) } func putWithBearerTokenInHeader(ctx context.Context, t *testing.T, p *pool.Pool, CID cid.ID, token string) { url := testHost + "/upload/" + CID.String() request, content, attributes := makePutRequest(t, url) request.Header.Set("Authorization", "Bearer "+token) resp, err := http.DefaultClient.Do(request) require.NoError(t, err) checkPutResponse(ctx, t, p, CID, resp, content, attributes) } func putWithBearerTokenInCookie(ctx context.Context, t *testing.T, p *pool.Pool, CID cid.ID, token string) { url := testHost + "/upload/" + CID.String() request, content, attributes := makePutRequest(t, url) request.AddCookie(&http.Cookie{Name: "Bearer", Value: token}) resp, err := http.DefaultClient.Do(request) require.NoError(t, err) checkPutResponse(ctx, t, p, CID, resp, content, attributes) } func makePutRequestAndCheck(ctx context.Context, t *testing.T, p *pool.Pool, cnrID cid.ID, url string) { request, content, attributes := makePutRequest(t, url) resp, err := http.DefaultClient.Do(request) require.NoError(t, err) checkPutResponse(ctx, t, p, cnrID, resp, content, attributes) } func makePutRequest(t *testing.T, url string) (*http.Request, string, map[string]string) { content := "content of file" keyAttr, valAttr := "User-Attribute", "user value" attributes := map[string]string{ object.AttributeFileName: "newFile.txt", keyAttr: valAttr, } var buff bytes.Buffer w := multipart.NewWriter(&buff) fw, err := w.CreateFormFile("file", attributes[object.AttributeFileName]) require.NoError(t, err) _, err = io.Copy(fw, bytes.NewBufferString(content)) require.NoError(t, err) err = w.Close() require.NoError(t, err) request, err := http.NewRequest(http.MethodPost, url, &buff) require.NoError(t, err) request.Header.Set("Content-Type", w.FormDataContentType()) request.Header.Set("X-Attribute-"+keyAttr, valAttr) return request, content, attributes } func checkPutResponse(ctx context.Context, t *testing.T, p *pool.Pool, cnrID cid.ID, resp *http.Response, content string, attributes map[string]string) { defer func() { err := resp.Body.Close() require.NoError(t, err) }() body, err := io.ReadAll(resp.Body) require.NoError(t, err) if resp.StatusCode != http.StatusOK { fmt.Println(string(body)) } require.Equal(t, http.StatusOK, resp.StatusCode) addr := &putResponse{} err = json.Unmarshal(body, addr) require.NoError(t, err) err = cnrID.DecodeString(addr.CID) require.NoError(t, err) var id oid.ID err = id.DecodeString(addr.OID) require.NoError(t, err) var objectAddress oid.Address objectAddress.SetContainer(cnrID) objectAddress.SetObject(id) payload := bytes.NewBuffer(nil) var prm pool.PrmObjectGet prm.SetAddress(objectAddress) res, err := p.GetObject(ctx, prm) require.NoError(t, err) _, err = io.Copy(payload, res.Payload) require.NoError(t, err) require.Equal(t, content, payload.String()) for _, attribute := range res.Header.Attributes() { require.Equal(t, attributes[attribute.Key()], attribute.Value()) } } func putWithDuplicateKeys(t *testing.T, CID cid.ID) { url := testHost + "/upload/" + CID.String() attr := "X-Attribute-User-Attribute" content := "content of file" valOne, valTwo := "first_value", "second_value" fileName := "newFile.txt" var buff bytes.Buffer w := multipart.NewWriter(&buff) fw, err := w.CreateFormFile("file", fileName) require.NoError(t, err) _, err = io.Copy(fw, bytes.NewBufferString(content)) require.NoError(t, err) err = w.Close() require.NoError(t, err) request, err := http.NewRequest(http.MethodPost, url, &buff) require.NoError(t, err) request.Header.Set("Content-Type", w.FormDataContentType()) request.Header.Add(attr, valOne) request.Header.Add(attr, valTwo) resp, err := http.DefaultClient.Do(request) require.NoError(t, err) defer func() { err := resp.Body.Close() require.NoError(t, err) }() body, err := io.ReadAll(resp.Body) require.NoError(t, err) require.Contains(t, string(body), "key duplication error: "+attr+"\n") require.Equal(t, http.StatusBadRequest, resp.StatusCode) } func simpleGet(ctx context.Context, t *testing.T, clientPool *pool.Pool, ownerID user.ID, CID cid.ID) { content := "content of file" attributes := map[string]string{ "some-attr": "some-get-value", } id := putObject(ctx, t, clientPool, ownerID, CID, content, attributes) resp, err := http.Get(testHost + "/get/" + CID.String() + "/" + id.String()) require.NoError(t, err) checkGetResponse(t, resp, content, attributes) resp, err = http.Get(testHost + "/get/" + testContainerName + "/" + id.String()) require.NoError(t, err) checkGetResponse(t, resp, content, attributes) } func checkGetResponse(t *testing.T, resp *http.Response, content string, attributes map[string]string) { defer func() { err := resp.Body.Close() require.NoError(t, err) }() data, err := io.ReadAll(resp.Body) require.NoError(t, err) require.Equal(t, content, string(data)) for k, v := range attributes { require.Equal(t, v, resp.Header.Get("X-Attribute-"+k)) } } func checkGetByAttrResponse(t *testing.T, resp *http.Response, content string, attributes map[string]string) { defer func() { err := resp.Body.Close() require.NoError(t, err) }() data, err := io.ReadAll(resp.Body) require.NoError(t, err) require.Equal(t, content, string(data)) for k, v := range attributes { require.Equal(t, v, resp.Header.Get(k)) } } func getByAttr(ctx context.Context, t *testing.T, clientPool *pool.Pool, ownerID user.ID, CID cid.ID) { keyAttr, valAttr := "some-attr", "some-get-by-attr-value" content := "content of file" attributes := map[string]string{keyAttr: valAttr} id := putObject(ctx, t, clientPool, ownerID, CID, content, attributes) expectedAttr := map[string]string{ "X-Attribute-" + keyAttr: valAttr, "x-object-id": id.String(), "x-container-id": CID.String(), } resp, err := http.Get(testHost + "/get_by_attribute/" + CID.String() + "/" + keyAttr + "/" + valAttr) require.NoError(t, err) checkGetByAttrResponse(t, resp, content, expectedAttr) resp, err = http.Get(testHost + "/get_by_attribute/" + testContainerName + "/" + keyAttr + "/" + valAttr) require.NoError(t, err) checkGetByAttrResponse(t, resp, content, expectedAttr) } func getZip(ctx context.Context, t *testing.T, clientPool *pool.Pool, ownerID user.ID, CID cid.ID) { names := []string{"zipfolder/dir/name1.txt", "zipfolder/name2.txt"} contents := []string{"content of file1", "content of file2"} attributes1 := map[string]string{object.AttributeFilePath: names[0]} attributes2 := map[string]string{object.AttributeFilePath: names[1]} putObject(ctx, t, clientPool, ownerID, CID, contents[0], attributes1) putObject(ctx, t, clientPool, ownerID, CID, contents[1], attributes2) baseURL := testHost + "/zip/" + CID.String() makeZipTest(t, baseURL, names, contents) baseURL = testHost + "/zip/" + testContainerName makeZipTest(t, baseURL, names, contents) } func makeZipTest(t *testing.T, baseURL string, names, contents []string) { url := baseURL + "/zipfolder" makeZipRequest(t, url, names, contents) // check nested folder url = baseURL + "/zipfolder/dir" makeZipRequest(t, url, names[:1], contents[:1]) } func makeZipRequest(t *testing.T, url string, names, contents []string) { resp, err := http.Get(url) require.NoError(t, err) defer func() { err := resp.Body.Close() require.NoError(t, err) }() data, err := io.ReadAll(resp.Body) require.NoError(t, err) checkZip(t, data, int64(len(data)), names, contents) } func checkZip(t *testing.T, data []byte, length int64, names, contents []string) { readerAt := bytes.NewReader(data) zipReader, err := zip.NewReader(readerAt, length) require.NoError(t, err) require.Equal(t, len(names), len(zipReader.File)) sort.Slice(zipReader.File, func(i, j int) bool { return zipReader.File[i].FileHeader.Name < zipReader.File[j].FileHeader.Name }) for i, f := range zipReader.File { require.Equal(t, names[i], f.FileHeader.Name) rc, err := f.Open() require.NoError(t, err) all, err := io.ReadAll(rc) require.NoError(t, err) require.Equal(t, contents[i], string(all)) err = rc.Close() require.NoError(t, err) } } func checkNamespaces(ctx context.Context, t *testing.T, clientPool *pool.Pool, ownerID user.ID, CID cid.ID) { content := "content of file" attributes := map[string]string{ "some-attr": "some-get-value", } id := putObject(ctx, t, clientPool, ownerID, CID, content, attributes) req, err := http.NewRequest(http.MethodGet, testHost+"/get/"+testContainerName+"/"+id.String(), nil) require.NoError(t, err) req.Header.Set(defaultNamespaceHeader, "") resp, err := http.DefaultClient.Do(req) require.NoError(t, err) checkGetResponse(t, resp, content, attributes) req, err = http.NewRequest(http.MethodGet, testHost+"/get/"+testContainerName+"/"+id.String(), nil) require.NoError(t, err) req.Header.Set(defaultNamespaceHeader, "root") resp, err = http.DefaultClient.Do(req) require.NoError(t, err) checkGetResponse(t, resp, content, attributes) req, err = http.NewRequest(http.MethodGet, testHost+"/get/"+testContainerName+"/"+id.String(), nil) require.NoError(t, err) req.Header.Set(defaultNamespaceHeader, "root2") resp, err = http.DefaultClient.Do(req) require.NoError(t, err) require.Equal(t, http.StatusNotFound, resp.StatusCode) } func checkStatusCodes(ctx context.Context, t *testing.T, clientPool *pool.Pool, ownerID user.ID, version string) { cli := http.Client{Timeout: 30 * time.Second} t.Run("container not found by name", func(t *testing.T) { resp, err := cli.Get(testHost + "/get/unknown/object") require.NoError(t, err) require.Equal(t, http.StatusNotFound, resp.StatusCode) requireBodyContains(t, resp, "container not found") }) t.Run("container not found by cid", func(t *testing.T) { cnrIDTest := cidtest.ID() resp, err := cli.Get(testHost + "/get/" + cnrIDTest.EncodeToString() + "/object") require.NoError(t, err) requireBodyContains(t, resp, "container not found") require.Equal(t, http.StatusNotFound, resp.StatusCode) }) t.Run("object not found in storage", func(t *testing.T) { resp, err := cli.Get(testHost + "/get_by_attribute/" + testContainerName + "/FilePath/object2") require.NoError(t, err) requireBodyContains(t, resp, "object not found") require.Equal(t, http.StatusNotFound, resp.StatusCode) }) t.Run("access denied", func(t *testing.T) { basicACL := acl.Private var recs []*eacl.Record var APERules []chain.Rule if version == "1.2.7" { basicACL = acl.PublicRWExtended rec := eacl.NewRecord() rec.SetAction(eacl.ActionDeny) rec.SetOperation(eacl.OperationGet) recs = append(recs, rec) } else if versionToUint(t, version) >= versionToUint(t, "1.7.0") { APERules = privateRules() } cnrID, err := createContainerBase(ctx, t, clientPool, ownerID, basicACL, APERules, "") require.NoError(t, err) key, err := keys.NewPrivateKey() require.NoError(t, err) jsonToken, _ := makeBearerTokens(t, key, ownerID, version, recs...) t.Run("get", func(t *testing.T) { request, err := http.NewRequest(http.MethodGet, testHost+"/get/"+cnrID.EncodeToString()+"/object", nil) require.NoError(t, err) request.Header.Set("Authorization", "Bearer "+jsonToken) resp, err := cli.Do(request) require.NoError(t, err) requireBodyContains(t, resp, "access denied") require.Equal(t, http.StatusForbidden, resp.StatusCode) }) t.Run("upload", func(t *testing.T) { request, _, _ := makePutRequest(t, testHost+"/upload/"+cnrID.EncodeToString()) request.Header.Set("Authorization", "Bearer "+jsonToken) resp, err := cli.Do(request) require.NoError(t, err) requireBodyContains(t, resp, "access denied") require.Equal(t, http.StatusForbidden, resp.StatusCode) }) }) } func requireBodyContains(t *testing.T, resp *http.Response, msg string) { data, err := io.ReadAll(resp.Body) require.NoError(t, err) defer resp.Body.Close() require.Contains(t, strings.ToLower(string(data)), strings.ToLower(msg)) } func createDockerContainer(ctx context.Context, t *testing.T, image string) testcontainers.Container { req := testcontainers.ContainerRequest{ Image: image, WaitingFor: wait.NewLogStrategy("aio container started").WithStartupTimeout(2 * time.Minute), Name: "aio", Hostname: "aio", HostConfigModifier: func(hc *docker.HostConfig) { hc.NetworkMode = "host" }, } aioC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: req, Started: true, }) require.NoError(t, err) return aioC } func getDefaultConfig() *appCfg { v := settings() v.config().SetDefault(cfgPeers+".0.address", "localhost:8080") v.config().SetDefault(cfgPeers+".0.weight", 1) v.config().SetDefault(cfgPeers+".0.priority", 1) v.config().SetDefault(cfgRPCEndpoint, "http://localhost:30333") v.config().SetDefault("server.0.address", testListenAddress) return v } func getPool(ctx context.Context, t *testing.T, key *keys.PrivateKey) *pool.Pool { var prm pool.InitParameters prm.SetKey(&key.PrivateKey) prm.SetNodeDialTimeout(5 * time.Second) prm.AddNode(pool.NewNodeParam(1, "localhost:8080", 1)) clientPool, err := pool.NewPool(prm) require.NoError(t, err) err = clientPool.Dial(ctx) require.NoError(t, err) return clientPool } func createContainerWithACL(ctx context.Context, t *testing.T, clientPool *pool.Pool, ownerID user.ID, name string) (cid.ID, error) { return createContainerBase(ctx, t, clientPool, ownerID, acl.PublicRWExtended, nil, name) } func createContainerWithAPE(ctx context.Context, t *testing.T, clientPool *pool.Pool, ownerID user.ID, name string) (cid.ID, error) { return createContainerBase(ctx, t, clientPool, ownerID, acl.PublicRWExtended, publicReadWriteRules(), name) } func waitForAPECacheInvalidated(ctx context.Context, clientPool *pool.Pool, expectedCh chain.Chain, cnrID cid.ID) error { prmListAPEChains := pool.PrmListAPEChains{ Target: ape.ChainTarget{ TargetType: ape.TargetTypeContainer, Name: cnrID.EncodeToString(), }, } checkCtxDone := func(ctx context.Context) error { if _, ok := ctx.Deadline(); ok { return fmt.Errorf("waiting time for storing APE chain has expired") } return fmt.Errorf("waiting for the APE chain to be stored has been cancelled") } ctx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() for { chains, err := clientPool.ListAPEChains(ctx, prmListAPEChains) if err != nil { return fmt.Errorf("list APE chains: %w", err) } for _, rawChain := range chains { var ch chain.Chain err = ch.UnmarshalBinary(rawChain.Raw) if err != nil { return fmt.Errorf("unmarshal chain: %w", err) } if bytes.Equal(ch.ID, expectedCh.ID) { // At the moment, according to the core team, there is no way through the API // to check whether the APE chain stored in the contact has been applied to the container. // So after we make sure that the APE chain is stored, we just wait for a certain period of time // (8 seconds by default, the time until the next block and cache invalidation) select { case <-ctx.Done(): return checkCtxDone(ctx) case <-time.After(8 * time.Second): return nil } } } select { case <-ctx.Done(): return checkCtxDone(ctx) case <-time.After(500 * time.Millisecond): } } } func addAPEChainToContainer(ctx context.Context, t *testing.T, clientPool *pool.Pool, chainID string, rules []chain.Rule, cnrID cid.ID) { ch := chain.Chain{ ID: chain.ID(chainID), Rules: rules, } data, err := ch.MarshalBinary() require.NoError(t, err) prmAddAPEChain := pool.PrmAddAPEChain{ Target: ape.ChainTarget{ TargetType: ape.TargetTypeContainer, Name: cnrID.EncodeToString(), }, Chain: ape.Chain{Raw: data}, } err = clientPool.AddAPEChain(ctx, prmAddAPEChain) require.NoError(t, err) err = waitForAPECacheInvalidated(ctx, clientPool, ch, cnrID) } func randomString() string { rnd := rand.New(rand.NewSource(time.Now().UnixNano())) b := make([]byte, 8) for i := range b { b[i] = byte('a' + rnd.Intn(26)) } return string(b) } func createContainerBase(ctx context.Context, t *testing.T, clientPool *pool.Pool, ownerID user.ID, basicACL acl.Basic, apeRules []chain.Rule, name string) (cid.ID, error) { var policy netmap.PlacementPolicy err := policy.DecodeString("REP 1") require.NoError(t, err) var cnr container.Container cnr.Init() cnr.SetPlacementPolicy(policy) cnr.SetBasicACL(basicACL) cnr.SetOwner(ownerID) container.SetCreationTime(&cnr, time.Now()) if name != "" { var domain container.Domain domain.SetName(name) cnr.SetAttribute(containerv2.SysAttributeName, domain.Name()) cnr.SetAttribute(containerv2.SysAttributeZone, domain.Zone()) } prm := pool.PrmContainerPut{ ClientParams: client.PrmContainerPut{ Container: &cnr, }, WaitParams: &pool.WaitParams{ Timeout: 15 * time.Second, PollInterval: 3 * time.Second, }, } CID, err := clientPool.PutContainer(ctx, prm) if err != nil { return cid.ID{}, err } fmt.Println(CID.String()) if len(apeRules) != 0 { chainID := randomString() addAPEChainToContainer(ctx, t, clientPool, chainID, apeRules, CID) } return CID, err } func putObject(ctx context.Context, t *testing.T, clientPool *pool.Pool, ownerID user.ID, CID cid.ID, content string, attributes map[string]string) oid.ID { obj := object.New() obj.SetContainerID(CID) obj.SetOwnerID(ownerID) var attrs []object.Attribute for key, val := range attributes { attr := object.NewAttribute() attr.SetKey(key) attr.SetValue(val) attrs = append(attrs, *attr) } obj.SetAttributes(attrs...) var prm pool.PrmObjectPut prm.SetHeader(*obj) prm.SetPayload(bytes.NewBufferString(content)) id, err := clientPool.PutObject(ctx, prm) require.NoError(t, err) return id.ObjectID } func registerUser(t *testing.T, ctx context.Context, aioContainer testcontainers.Container, pathToWallet string) { err := aioContainer.CopyFileToContainer(ctx, pathToWallet, "/usr/wallet.json", 644) require.NoError(t, err) _, _, err = aioContainer.Exec(ctx, []string{ "/usr/bin/frostfs-s3-authmate", "register-user", "--wallet", "/usr/wallet.json", "--rpc-endpoint", "http://localhost:30333", "--contract-wallet", "/config/s3-gw-wallet.json"}) require.NoError(t, err) } func makeBearerTokens(t *testing.T, key *keys.PrivateKey, ownerID user.ID, version string, records ...*eacl.Record) (jsonTokenBase64, binaryTokenBase64 string) { tkn := new(bearer.Token) tkn.ForUser(ownerID) tkn.SetExp(10000) if version == "1.2.7" { table := eacl.NewTable() for i := range records { table.AddRecord(records[i]) } tkn.SetEACLTable(*table) } else { tkn.SetImpersonate(true) } err := tkn.Sign(key.PrivateKey) require.NoError(t, err) jsonToken, err := tkn.MarshalJSON() require.NoError(t, err) jsonTokenBase64 = base64.StdEncoding.EncodeToString(jsonToken) binaryTokenBase64 = base64.StdEncoding.EncodeToString(tkn.Marshal()) require.NotEmpty(t, jsonTokenBase64) require.NotEmpty(t, binaryTokenBase64) return } func makeTempWallet(t *testing.T, key *keys.PrivateKey, path string) { w, err := wallet.NewWallet(path) require.NoError(t, err) acc := wallet.NewAccountFromPrivateKey(key) err = acc.Encrypt("", w.Scrypt) require.NoError(t, err) w.AddAccount(acc) err = w.Save() require.NoError(t, err) }