git.frostfs.info/TrueCloudLab/frostfs-sdk-go@v0.0.0-20241022124111-5361f0ecebd3/object/transformer/transformer_test.go (about)

     1  package transformer
     2  
     3  import (
     4  	"context"
     5  	"crypto/rand"
     6  	"crypto/sha256"
     7  	"testing"
     8  
     9  	cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
    10  	cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
    11  	objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
    12  	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
    13  	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/version"
    14  	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
    15  	"github.com/stretchr/testify/require"
    16  )
    17  
    18  func TestTransformer(t *testing.T) {
    19  	const maxSize = 100
    20  
    21  	tt := new(testTarget)
    22  
    23  	target, pk := newPayloadSizeLimiter(maxSize, 0, func() ObjectWriter { return tt })
    24  
    25  	cnr := cidtest.ID()
    26  	hdr := newObject(cnr)
    27  
    28  	var owner user.ID
    29  	user.IDFromKey(&owner, pk.PrivateKey.PublicKey)
    30  	hdr.SetOwnerID(owner)
    31  
    32  	expectedPayload := make([]byte, maxSize*2+maxSize/2)
    33  	_, _ = rand.Read(expectedPayload)
    34  
    35  	ids := writeObject(t, context.Background(), target, hdr, expectedPayload)
    36  	require.Equal(t, 4, len(tt.objects)) // 3 parts + linking object
    37  
    38  	var actualPayload []byte
    39  	for i := range tt.objects {
    40  		childCnr, ok := tt.objects[i].ContainerID()
    41  		require.True(t, ok)
    42  		require.Equal(t, cnr, childCnr)
    43  		require.Equal(t, objectSDK.TypeRegular, tt.objects[i].Type())
    44  		require.Equal(t, owner, tt.objects[i].OwnerID())
    45  
    46  		payload := tt.objects[i].Payload()
    47  		require.EqualValues(t, tt.objects[i].PayloadSize(), len(payload))
    48  		actualPayload = append(actualPayload, payload...)
    49  
    50  		if len(payload) != 0 {
    51  			cs, ok := tt.objects[i].PayloadChecksum()
    52  			require.True(t, ok)
    53  
    54  			h := sha256.Sum256(payload)
    55  			require.Equal(t, h[:], cs.Value())
    56  		}
    57  
    58  		require.True(t, tt.objects[i].VerifyIDSignature())
    59  		switch i {
    60  		case 0, 1:
    61  			require.EqualValues(t, maxSize, len(payload))
    62  			require.Nil(t, tt.objects[i].Parent())
    63  		case 2:
    64  			require.EqualValues(t, maxSize/2, len(payload))
    65  			parent := tt.objects[i].Parent()
    66  			require.NotNil(t, parent)
    67  			require.Nil(t, parent.SplitID())
    68  			require.True(t, parent.VerifyIDSignature())
    69  		case 3:
    70  			parID, ok := tt.objects[i].ParentID()
    71  			require.True(t, ok)
    72  			require.Equal(t, ids.ParentID, &parID)
    73  
    74  			children := tt.objects[i].Children()
    75  			for j := range i {
    76  				id, ok := tt.objects[j].ID()
    77  				require.True(t, ok)
    78  				require.Equal(t, id, children[j])
    79  			}
    80  		}
    81  	}
    82  	require.Equal(t, expectedPayload, actualPayload)
    83  
    84  	t.Run("parent checksum", func(t *testing.T) {
    85  		cs, ok := ids.ParentHeader.PayloadChecksum()
    86  		require.True(t, ok)
    87  
    88  		h := sha256.Sum256(expectedPayload)
    89  		require.Equal(t, h[:], cs.Value())
    90  	})
    91  }
    92  
    93  func newObject(cnr cid.ID) *objectSDK.Object {
    94  	ver := version.Current()
    95  	hdr := objectSDK.New()
    96  	hdr.SetContainerID(cnr)
    97  	hdr.SetType(objectSDK.TypeRegular)
    98  	hdr.SetVersion(&ver)
    99  	return hdr
   100  }
   101  
   102  func writeObject(t *testing.T, ctx context.Context, target ChunkedObjectWriter, header *objectSDK.Object, payload []byte) *AccessIdentifiers {
   103  	require.NoError(t, target.WriteHeader(ctx, header))
   104  
   105  	_, err := target.Write(ctx, payload)
   106  	require.NoError(t, err)
   107  
   108  	ids, err := target.Close(ctx)
   109  	require.NoError(t, err)
   110  
   111  	return ids
   112  }
   113  
   114  func BenchmarkTransformer(b *testing.B) {
   115  	hdr := newObject(cidtest.ID())
   116  
   117  	const (
   118  		// bufferSize is taken from https://git.frostfs.info/TrueCloudLab/frostfs-sdk-go/src/commit/670619d2426fee233a37efe21a0471989b16a4fc/pool/pool.go#L1825
   119  		bufferSize = 3 * 1024 * 1024
   120  		smallSize  = 8 * 1024
   121  		bigSize    = 64 * 1024 * 1024 * 9 / 2 // 4.5 parts
   122  	)
   123  	b.Run("small", func(b *testing.B) {
   124  		b.Run("no size hint", func(b *testing.B) {
   125  			benchmarkTransformer(b, hdr, smallSize, 0, 0)
   126  		})
   127  		b.Run("no size hint, with buffer", func(b *testing.B) {
   128  			benchmarkTransformer(b, hdr, smallSize, 0, bufferSize)
   129  		})
   130  		b.Run("with size hint, with buffer", func(b *testing.B) {
   131  			benchmarkTransformer(b, hdr, smallSize, smallSize, bufferSize)
   132  		})
   133  	})
   134  	b.Run("big", func(b *testing.B) {
   135  		b.Run("no size hint", func(b *testing.B) {
   136  			benchmarkTransformer(b, hdr, bigSize, 0, 0)
   137  		})
   138  		b.Run("no size hint, with buffer", func(b *testing.B) {
   139  			benchmarkTransformer(b, hdr, bigSize, 0, bufferSize)
   140  		})
   141  		b.Run("with size hint, with buffer", func(b *testing.B) {
   142  			benchmarkTransformer(b, hdr, bigSize, bigSize, bufferSize)
   143  		})
   144  	})
   145  }
   146  
   147  func benchmarkTransformer(b *testing.B, header *objectSDK.Object, payloadSize, sizeHint, bufferSize int) {
   148  	const maxSize = 64 * 1024 * 1024
   149  
   150  	payload := make([]byte, payloadSize)
   151  	ctx := context.Background()
   152  
   153  	b.ReportAllocs()
   154  	b.ResetTimer()
   155  	for range b.N {
   156  		f, _ := newPayloadSizeLimiter(maxSize, uint64(sizeHint), func() ObjectWriter { return benchTarget{} })
   157  		if err := f.WriteHeader(ctx, header); err != nil {
   158  			b.Fatalf("write header: %v", err)
   159  		}
   160  		if bufferSize == 0 {
   161  			if _, err := f.Write(ctx, payload); err != nil {
   162  				b.Fatalf("write: %v", err)
   163  			}
   164  		} else {
   165  			j := 0
   166  			for ; j+bufferSize < payloadSize; j += bufferSize {
   167  				if _, err := f.Write(ctx, payload[j:j+bufferSize]); err != nil {
   168  					b.Fatalf("write: %v", err)
   169  				}
   170  			}
   171  			if _, err := f.Write(ctx, payload[j:payloadSize]); err != nil {
   172  				b.Fatalf("write: %v", err)
   173  			}
   174  		}
   175  		if _, err := f.Close(ctx); err != nil {
   176  			b.Fatalf("close: %v", err)
   177  		}
   178  	}
   179  }
   180  
   181  func newPayloadSizeLimiter(maxSize uint64, sizeHint uint64, nextTarget TargetInitializer) (ChunkedObjectWriter, *keys.PrivateKey) {
   182  	p, err := keys.NewPrivateKey()
   183  	if err != nil {
   184  		panic(err)
   185  	}
   186  
   187  	return NewPayloadSizeLimiter(Params{
   188  		Key:                    &p.PrivateKey,
   189  		NextTargetInit:         nextTarget,
   190  		NetworkState:           dummyEpochSource(123),
   191  		MaxSize:                maxSize,
   192  		SizeHint:               sizeHint,
   193  		WithoutHomomorphicHash: true,
   194  	}), p
   195  }
   196  
   197  type dummyEpochSource uint64
   198  
   199  func (s dummyEpochSource) CurrentEpoch() uint64 {
   200  	return uint64(s)
   201  }
   202  
   203  type benchTarget struct{}
   204  
   205  func (benchTarget) WriteObject(context.Context, *objectSDK.Object) error {
   206  	return nil
   207  }
   208  
   209  type testTarget struct {
   210  	objects []*objectSDK.Object
   211  }
   212  
   213  func (tt *testTarget) WriteObject(_ context.Context, o *objectSDK.Object) error {
   214  	tt.objects = append(tt.objects, o)
   215  	return nil // AccessIdentifiers should not be used.
   216  }