2021-09-06 12:59:09 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2021-10-19 07:52:41 +00:00
|
|
|
"archive/zip"
|
2021-09-06 12:59:09 +00:00
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
2021-11-02 15:54:22 +00:00
|
|
|
"math"
|
2021-09-06 12:59:09 +00:00
|
|
|
"mime/multipart"
|
|
|
|
"net/http"
|
2021-10-19 07:52:41 +00:00
|
|
|
"sort"
|
2021-09-06 12:59:09 +00:00
|
|
|
"strconv"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
2021-11-15 11:12:15 +00:00
|
|
|
"github.com/nspcc-dev/neofs-sdk-go/client"
|
|
|
|
"github.com/nspcc-dev/neofs-sdk-go/container"
|
|
|
|
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
|
|
|
"github.com/nspcc-dev/neofs-sdk-go/object"
|
|
|
|
"github.com/nspcc-dev/neofs-sdk-go/policy"
|
|
|
|
"github.com/nspcc-dev/neofs-sdk-go/pool"
|
2021-09-06 12:59:09 +00:00
|
|
|
"github.com/spf13/viper"
|
|
|
|
"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"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestIntegration(t *testing.T) {
|
2021-11-01 06:54:09 +00:00
|
|
|
rootCtx := context.Background()
|
2021-09-06 12:59:09 +00:00
|
|
|
aioImage := "nspccdev/neofs-aio-testcontainer:"
|
2021-11-02 15:54:22 +00:00
|
|
|
versions := []string{"0.24.0", "0.25.1", "0.26.1", "latest"}
|
2021-09-06 12:59:09 +00:00
|
|
|
key, err := keys.NewPrivateKeyFromHex("1dd37fba80fec4e6a6f13fd708d8dcb3b29def768017052f6c930fa1c5d90bbb")
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
for _, version := range versions {
|
2021-11-01 06:54:09 +00:00
|
|
|
ctx, cancel2 := context.WithCancel(rootCtx)
|
|
|
|
|
2021-09-06 12:59:09 +00:00
|
|
|
aioContainer := createDockerContainer(ctx, t, aioImage+version)
|
|
|
|
cancel := runServer()
|
|
|
|
clientPool := getPool(ctx, t, key)
|
|
|
|
CID := createContainer(ctx, t, clientPool)
|
|
|
|
|
|
|
|
t.Run("simple put "+version, func(t *testing.T) { simplePut(ctx, t, clientPool, CID) })
|
|
|
|
t.Run("simple get "+version, func(t *testing.T) { simpleGet(ctx, t, clientPool, CID) })
|
|
|
|
t.Run("get by attribute "+version, func(t *testing.T) { getByAttr(ctx, t, clientPool, CID) })
|
2021-10-19 07:52:41 +00:00
|
|
|
t.Run("get zip "+version, func(t *testing.T) { getZip(ctx, t, clientPool, CID) })
|
2021-09-06 12:59:09 +00:00
|
|
|
|
|
|
|
cancel()
|
|
|
|
err = aioContainer.Terminate(ctx)
|
|
|
|
require.NoError(t, err)
|
2021-11-01 06:54:09 +00:00
|
|
|
cancel2()
|
2021-09-06 12:59:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func runServer() context.CancelFunc {
|
|
|
|
cancelCtx, cancel := context.WithCancel(context.Background())
|
|
|
|
|
|
|
|
v := getDefaultConfig()
|
|
|
|
l := newLogger(v)
|
|
|
|
application := newApp(cancelCtx, WithConfig(v), WithLogger(l))
|
|
|
|
go application.Serve(cancelCtx)
|
|
|
|
|
|
|
|
return cancel
|
|
|
|
}
|
|
|
|
|
|
|
|
func simplePut(ctx context.Context, t *testing.T, clientPool pool.Pool, CID *cid.ID) {
|
|
|
|
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, "http://localhost:8082/upload/"+CID.String(), &buff)
|
|
|
|
require.NoError(t, err)
|
|
|
|
request.Header.Set("Content-Type", w.FormDataContentType())
|
|
|
|
request.Header.Set("X-Attribute-"+keyAttr, valAttr)
|
|
|
|
|
|
|
|
resp, err := http.DefaultClient.Do(request)
|
|
|
|
require.NoError(t, err)
|
|
|
|
defer func() {
|
|
|
|
err = resp.Body.Close()
|
|
|
|
require.NoError(t, err)
|
|
|
|
}()
|
|
|
|
|
|
|
|
addr := &putResponse{}
|
|
|
|
err = json.NewDecoder(resp.Body).Decode(addr)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
err = CID.Parse(addr.CID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
oid := object.NewID()
|
|
|
|
err = oid.Parse(addr.OID)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
objectAddress := object.NewAddress()
|
|
|
|
objectAddress.SetContainerID(CID)
|
|
|
|
objectAddress.SetObjectID(oid)
|
|
|
|
|
|
|
|
payload := bytes.NewBuffer(nil)
|
|
|
|
ops := new(client.GetObjectParams).WithAddress(objectAddress).WithPayloadWriter(payload)
|
|
|
|
obj, err := clientPool.GetObject(ctx, ops)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, content, payload.String())
|
|
|
|
|
|
|
|
for _, attribute := range obj.Attributes() {
|
|
|
|
require.Equal(t, attributes[attribute.Key()], attribute.Value())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func simpleGet(ctx context.Context, t *testing.T, clientPool pool.Pool, CID *cid.ID) {
|
|
|
|
content := "content of file"
|
|
|
|
attributes := map[string]string{
|
|
|
|
"some-attr": "some-get-value",
|
|
|
|
}
|
|
|
|
|
|
|
|
oid := putObject(ctx, t, clientPool, CID, content, attributes)
|
|
|
|
|
|
|
|
resp, err := http.Get("http://localhost:8082/get/" + CID.String() + "/" + oid.String())
|
|
|
|
require.NoError(t, err)
|
|
|
|
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 getByAttr(ctx context.Context, t *testing.T, clientPool pool.Pool, CID *cid.ID) {
|
|
|
|
keyAttr, valAttr := "some-attr", "some-get-by-attr-value"
|
|
|
|
content := "content of file"
|
|
|
|
attributes := map[string]string{keyAttr: valAttr}
|
|
|
|
|
|
|
|
oid := putObject(ctx, t, clientPool, CID, content, attributes)
|
|
|
|
|
|
|
|
expectedAttr := map[string]string{
|
|
|
|
"X-Attribute-" + keyAttr: valAttr,
|
|
|
|
"x-object-id": oid.String(),
|
|
|
|
"x-container-id": CID.String(),
|
|
|
|
}
|
|
|
|
|
|
|
|
resp, err := http.Get("http://localhost:8082/get_by_attribute/" + CID.String() + "/" + keyAttr + "/" + valAttr)
|
|
|
|
require.NoError(t, err)
|
|
|
|
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 expectedAttr {
|
|
|
|
require.Equal(t, v, resp.Header.Get(k))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-19 07:52:41 +00:00
|
|
|
func getZip(ctx context.Context, t *testing.T, clientPool pool.Pool, 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.AttributeFileName: names[0]}
|
|
|
|
attributes2 := map[string]string{object.AttributeFileName: names[1]}
|
|
|
|
|
|
|
|
putObject(ctx, t, clientPool, CID, contents[0], attributes1)
|
|
|
|
putObject(ctx, t, clientPool, CID, contents[1], attributes2)
|
|
|
|
|
|
|
|
resp, err := http.Get("http://localhost:8082/zip/" + CID.String() + "/zipfolder")
|
|
|
|
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, resp.ContentLength, names, contents)
|
|
|
|
|
|
|
|
// check nested folder
|
|
|
|
resp2, err := http.Get("http://localhost:8082/zip/" + CID.String() + "/zipfolder/dir")
|
|
|
|
require.NoError(t, err)
|
|
|
|
defer func() {
|
|
|
|
err = resp2.Body.Close()
|
|
|
|
require.NoError(t, err)
|
|
|
|
}()
|
|
|
|
|
|
|
|
data2, err := io.ReadAll(resp2.Body)
|
|
|
|
require.NoError(t, err)
|
|
|
|
checkZip(t, data2, resp2.ContentLength, names[:1], contents[:1])
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-06 12:59:09 +00:00
|
|
|
func createDockerContainer(ctx context.Context, t *testing.T, image string) testcontainers.Container {
|
|
|
|
req := testcontainers.ContainerRequest{
|
|
|
|
Image: image,
|
|
|
|
WaitingFor: wait.NewLogStrategy("aio container started").WithStartupTimeout(30 * time.Second),
|
|
|
|
Name: "aio",
|
|
|
|
Hostname: "aio",
|
|
|
|
NetworkMode: "host",
|
|
|
|
}
|
|
|
|
aioC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
|
|
|
|
ContainerRequest: req,
|
|
|
|
Started: true,
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
return aioC
|
|
|
|
}
|
|
|
|
|
|
|
|
func getDefaultConfig() *viper.Viper {
|
|
|
|
v := settings()
|
|
|
|
v.SetDefault(cfgPeers+".0.address", "127.0.0.1:8080")
|
|
|
|
v.SetDefault(cfgPeers+".0.weight", 1)
|
|
|
|
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
|
|
|
|
func getPool(ctx context.Context, t *testing.T, key *keys.PrivateKey) pool.Pool {
|
|
|
|
pb := new(pool.Builder)
|
|
|
|
pb.AddNode("localhost:8080", 1)
|
|
|
|
|
|
|
|
opts := &pool.BuilderOptions{
|
2021-11-02 15:54:22 +00:00
|
|
|
Key: &key.PrivateKey,
|
|
|
|
NodeConnectionTimeout: 5 * time.Second,
|
|
|
|
NodeRequestTimeout: 5 * time.Second,
|
|
|
|
SessionExpirationEpoch: math.MaxUint64,
|
2021-09-06 12:59:09 +00:00
|
|
|
}
|
|
|
|
clientPool, err := pb.Build(ctx, opts)
|
|
|
|
require.NoError(t, err)
|
|
|
|
return clientPool
|
|
|
|
}
|
|
|
|
|
|
|
|
func createContainer(ctx context.Context, t *testing.T, clientPool pool.Pool) *cid.ID {
|
|
|
|
pp, err := policy.Parse("REP 1")
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
cnr := container.New(
|
|
|
|
container.WithPolicy(pp),
|
|
|
|
container.WithCustomBasicACL(0x0FFFFFFF),
|
|
|
|
container.WithAttribute(container.AttributeName, "friendlyName"),
|
|
|
|
container.WithAttribute(container.AttributeTimestamp, strconv.FormatInt(time.Now().Unix(), 10)))
|
|
|
|
cnr.SetOwnerID(clientPool.OwnerID())
|
|
|
|
|
|
|
|
CID, err := clientPool.PutContainer(ctx, cnr)
|
|
|
|
require.NoError(t, err)
|
|
|
|
fmt.Println(CID.String())
|
|
|
|
|
|
|
|
err = clientPool.WaitForContainerPresence(ctx, CID, &pool.ContainerPollingParams{
|
|
|
|
CreationTimeout: 15 * time.Second,
|
|
|
|
PollInterval: 3 * time.Second,
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
return CID
|
|
|
|
}
|
|
|
|
|
|
|
|
func putObject(ctx context.Context, t *testing.T, clientPool pool.Pool, CID *cid.ID, content string, attributes map[string]string) *object.ID {
|
|
|
|
rawObject := object.NewRaw()
|
|
|
|
rawObject.SetContainerID(CID)
|
|
|
|
rawObject.SetOwnerID(clientPool.OwnerID())
|
|
|
|
|
|
|
|
var attrs []*object.Attribute
|
|
|
|
for key, val := range attributes {
|
|
|
|
attr := object.NewAttribute()
|
|
|
|
attr.SetKey(key)
|
|
|
|
attr.SetValue(val)
|
|
|
|
attrs = append(attrs, attr)
|
|
|
|
}
|
|
|
|
rawObject.SetAttributes(attrs...)
|
|
|
|
|
|
|
|
ops := new(client.PutObjectParams).WithObject(rawObject.Object()).WithPayloadReader(bytes.NewBufferString(content))
|
|
|
|
oid, err := clientPool.PutObject(ctx, ops)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
return oid
|
|
|
|
}
|