@@ -3,19 +3,31 @@ package meta
33import (
44 "bytes"
55 "encoding/binary"
6+ "encoding/hex"
67 "errors"
78 "fmt"
9+ "math/rand"
810 "os"
911 "path"
1012 "path/filepath"
13+ "slices"
14+ "strconv"
1115 "testing"
1216
1317 objectconfig "github.com/nspcc-dev/neofs-node/cmd/neofs-node/config/object"
1418 "github.com/nspcc-dev/neofs-node/pkg/local_object_storage/shard/mode"
19+ "github.com/nspcc-dev/neofs-sdk-go/checksum"
20+ "github.com/nspcc-dev/neofs-sdk-go/client"
1521 apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
22+ cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
23+ cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
1624 "github.com/nspcc-dev/neofs-sdk-go/object"
1725 oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
1826 oidtest "github.com/nspcc-dev/neofs-sdk-go/object/id/test"
27+ objecttest "github.com/nspcc-dev/neofs-sdk-go/object/test"
28+ usertest "github.com/nspcc-dev/neofs-sdk-go/user/test"
29+ "github.com/nspcc-dev/neofs-sdk-go/version"
30+ "github.com/nspcc-dev/tzhash/tz"
1931 "github.com/stretchr/testify/require"
2032 "go.etcd.io/bbolt"
2133)
@@ -344,3 +356,228 @@ func TestMigrate2to3(t *testing.T) {
344356 })
345357 require .NoError (t , err )
346358}
359+
360+ func TestMigrate3to4 (t * testing.T ) {
361+ db := newDB (t )
362+
363+ typs := []object.Type {object .TypeRegular , object .TypeTombstone , object .TypeStorageGroup , object .TypeLock , object .TypeLink }
364+ objs := make ([]object.Object , len (typs ))
365+ var css , hcss [][]byte
366+ for i := range objs {
367+ objs [i ].SetContainerID (cidtest .ID ())
368+ id := oidtest .ID ()
369+ objs [i ].SetID (id )
370+ ver := version .New (uint32 (100 * i ), uint32 (100 * i + 1 ))
371+ objs [i ].SetVersion (& ver )
372+ objs [i ].SetOwner (usertest .ID ())
373+ objs [i ].SetType (typs [i ])
374+ objs [i ].SetCreationEpoch (rand .Uint64 ())
375+ objs [i ].SetPayloadSize (rand .Uint64 ())
376+ objs [i ].SetPayloadChecksum (checksum .NewSHA256 (id ))
377+ css = append (css , id [:])
378+ var tzh [tz .Size ]byte
379+ rand .Read (tzh [:]) //nolint:staticcheck
380+ objs [i ].SetPayloadHomomorphicHash (checksum .NewTillichZemor (tzh ))
381+ hcss = append (hcss , tzh [:])
382+ sid := objecttest .SplitID ()
383+ objs [i ].SetSplitID (& sid )
384+ objs [i ].SetParentID (oidtest .ID ())
385+ objs [i ].SetFirstID (oidtest .ID ())
386+ objs [i ].SetAttributes (* object .NewAttribute ("Index" , strconv .Itoa (i )))
387+ }
388+
389+ var par object.Object
390+ par .SetContainerID (objs [0 ].GetContainerID ())
391+ par .SetID (oidtest .ID ())
392+ ver := version .New (1000 , 1001 )
393+ par .SetVersion (& ver )
394+ par .SetOwner (usertest .ID ())
395+ par .SetType (typs [0 ])
396+ par .SetCreationEpoch (rand .Uint64 ())
397+ par .SetPayloadSize (rand .Uint64 ())
398+ pcs := oidtest .ID ()
399+ par .SetPayloadChecksum (checksum .NewSHA256 (pcs ))
400+ var phcs [tz .Size ]byte
401+ rand .Read (phcs [:]) //nolint:staticcheck
402+ par .SetPayloadHomomorphicHash (checksum .NewTillichZemor (phcs ))
403+ sid := objecttest .SplitID ()
404+ par .SetSplitID (& sid )
405+ par .SetParentID (oidtest .ID ())
406+ par .SetFirstID (oidtest .ID ())
407+ par .SetAttributes (* object .NewAttribute ("Index" , "9999" ))
408+
409+ objs [0 ].SetParent (& par )
410+
411+ for _ , item := range []struct {
412+ pref byte
413+ hdr * object.Object
414+ }{
415+ {pref : 0x06 , hdr : & objs [0 ]},
416+ {pref : 0x06 , hdr : & par },
417+ {pref : 0x09 , hdr : & objs [1 ]},
418+ {pref : 0x08 , hdr : & objs [2 ]},
419+ {pref : 0x07 , hdr : & objs [3 ]},
420+ {pref : 0x12 , hdr : & objs [4 ]},
421+ } {
422+ err := db .boltDB .Update (func (tx * bbolt.Tx ) error {
423+ cnr := item .hdr .GetContainerID ()
424+ bkt , err := tx .CreateBucketIfNotExists (slices .Concat ([]byte {item .pref }, cnr [:]))
425+ require .NoError (t , err )
426+ id := item .hdr .GetID ()
427+ return bkt .Put (id [:], item .hdr .Marshal ())
428+ })
429+ require .NoError (t , err )
430+ }
431+
432+ // force old version
433+ err := db .boltDB .Update (func (tx * bbolt.Tx ) error {
434+ if err := tx .ForEach (func (name []byte , b * bbolt.Bucket ) error {
435+ if name [0 ] == 0xFF {
436+ return tx .DeleteBucket (name )
437+ }
438+ return nil
439+ }); err != nil {
440+ return err
441+ }
442+
443+ bkt := tx .Bucket ([]byte {0x05 })
444+ require .NotNil (t , bkt )
445+ return bkt .Put ([]byte ("version" ), []byte {0x03 , 0 , 0 , 0 , 0 , 0 , 0 , 0 })
446+ })
447+ require .NoError (t , err )
448+ // migrate
449+ require .NoError (t , db .Init ())
450+ // check
451+ err = db .boltDB .View (func (tx * bbolt.Tx ) error {
452+ bkt := tx .Bucket ([]byte {0x05 })
453+ require .NotNil (t , bkt )
454+ require .Equal (t , []byte {0x04 , 0 , 0 , 0 , 0 , 0 , 0 , 0 }, bkt .Get ([]byte ("version" )))
455+ return nil
456+ })
457+ require .NoError (t , err )
458+
459+ res , _ , err := db .Search (objs [0 ].GetContainerID (), nil , nil , nil , 1000 )
460+ require .NoError (t , err )
461+ require .Len (t , res , 2 )
462+ require .True (t , slices .ContainsFunc (res , func (r client.SearchResultItem ) bool { return r .ID == objs [0 ].GetID () }))
463+ require .True (t , slices .ContainsFunc (res , func (r client.SearchResultItem ) bool { return r .ID == par .GetID () }))
464+
465+ for i := range objs [1 :] {
466+ res , _ , err := db .Search (objs [1 + i ].GetContainerID (), nil , nil , nil , 1000 )
467+ require .NoError (t , err , i )
468+ require .Len (t , res , 1 , i )
469+ require .Equal (t , objs [1 + i ].GetID (), res [0 ].ID , i )
470+ }
471+
472+ for _ , tc := range []struct {
473+ attr string
474+ val string
475+ cnr cid.ID
476+ exp oid.ID
477+ par bool
478+ }{
479+ {attr : "$Object:version" , val : "v0.1" , cnr : objs [0 ].GetContainerID (), exp : objs [0 ].GetID ()},
480+ {attr : "$Object:version" , val : "v100.101" , cnr : objs [1 ].GetContainerID (), exp : objs [1 ].GetID ()},
481+ {attr : "$Object:version" , val : "v200.201" , cnr : objs [2 ].GetContainerID (), exp : objs [2 ].GetID ()},
482+ {attr : "$Object:version" , val : "v300.301" , cnr : objs [3 ].GetContainerID (), exp : objs [3 ].GetID ()},
483+ {attr : "$Object:version" , val : "v400.401" , cnr : objs [4 ].GetContainerID (), exp : objs [4 ].GetID ()},
484+ {attr : "$Object:version" , val : "v1000.1001" , cnr : par .GetContainerID (), exp : par .GetID ()},
485+ {attr : "$Object:ownerID" , val : objs [0 ].Owner ().String (), cnr : objs [0 ].GetContainerID (), exp : objs [0 ].GetID ()},
486+ {attr : "$Object:ownerID" , val : objs [1 ].Owner ().String (), cnr : objs [1 ].GetContainerID (), exp : objs [1 ].GetID ()},
487+ {attr : "$Object:ownerID" , val : objs [2 ].Owner ().String (), cnr : objs [2 ].GetContainerID (), exp : objs [2 ].GetID ()},
488+ {attr : "$Object:ownerID" , val : objs [3 ].Owner ().String (), cnr : objs [3 ].GetContainerID (), exp : objs [3 ].GetID ()},
489+ {attr : "$Object:ownerID" , val : objs [4 ].Owner ().String (), cnr : objs [4 ].GetContainerID (), exp : objs [4 ].GetID ()},
490+ {attr : "$Object:ownerID" , val : par .Owner ().String (), cnr : par .GetContainerID (), exp : par .GetID ()},
491+ {attr : "$Object:objectType" , val : "REGULAR" , cnr : objs [0 ].GetContainerID (), par : true },
492+ {attr : "$Object:objectType" , val : "TOMBSTONE" , cnr : objs [1 ].GetContainerID (), exp : objs [1 ].GetID ()},
493+ {attr : "$Object:objectType" , val : "STORAGE_GROUP" , cnr : objs [2 ].GetContainerID (), exp : objs [2 ].GetID ()},
494+ {attr : "$Object:objectType" , val : "LOCK" , cnr : objs [3 ].GetContainerID (), exp : objs [3 ].GetID ()},
495+ {attr : "$Object:objectType" , val : "LINK" , cnr : objs [4 ].GetContainerID (), exp : objs [4 ].GetID ()},
496+ {attr : "$Object:creationEpoch" , val : strconv .FormatUint (objs [0 ].CreationEpoch (), 10 ), cnr : objs [0 ].GetContainerID (), exp : objs [0 ].GetID ()},
497+ {attr : "$Object:creationEpoch" , val : strconv .FormatUint (objs [1 ].CreationEpoch (), 10 ), cnr : objs [1 ].GetContainerID (), exp : objs [1 ].GetID ()},
498+ {attr : "$Object:creationEpoch" , val : strconv .FormatUint (objs [2 ].CreationEpoch (), 10 ), cnr : objs [2 ].GetContainerID (), exp : objs [2 ].GetID ()},
499+ {attr : "$Object:creationEpoch" , val : strconv .FormatUint (objs [3 ].CreationEpoch (), 10 ), cnr : objs [3 ].GetContainerID (), exp : objs [3 ].GetID ()},
500+ {attr : "$Object:creationEpoch" , val : strconv .FormatUint (objs [4 ].CreationEpoch (), 10 ), cnr : objs [4 ].GetContainerID (), exp : objs [4 ].GetID ()},
501+ {attr : "$Object:creationEpoch" , val : strconv .FormatUint (par .CreationEpoch (), 10 ), cnr : par .GetContainerID (), exp : par .GetID ()},
502+ {attr : "$Object:payloadLength" , val : strconv .FormatUint (objs [0 ].PayloadSize (), 10 ), cnr : objs [0 ].GetContainerID (), exp : objs [0 ].GetID ()},
503+ {attr : "$Object:payloadLength" , val : strconv .FormatUint (objs [1 ].PayloadSize (), 10 ), cnr : objs [1 ].GetContainerID (), exp : objs [1 ].GetID ()},
504+ {attr : "$Object:payloadLength" , val : strconv .FormatUint (objs [2 ].PayloadSize (), 10 ), cnr : objs [2 ].GetContainerID (), exp : objs [2 ].GetID ()},
505+ {attr : "$Object:payloadLength" , val : strconv .FormatUint (objs [3 ].PayloadSize (), 10 ), cnr : objs [3 ].GetContainerID (), exp : objs [3 ].GetID ()},
506+ {attr : "$Object:payloadLength" , val : strconv .FormatUint (objs [4 ].PayloadSize (), 10 ), cnr : objs [4 ].GetContainerID (), exp : objs [4 ].GetID ()},
507+ {attr : "$Object:payloadLength" , val : strconv .FormatUint (par .PayloadSize (), 10 ), cnr : par .GetContainerID (), exp : par .GetID ()},
508+ {attr : "$Object:payloadHash" , val : hex .EncodeToString (css [0 ]), cnr : objs [0 ].GetContainerID (), exp : objs [0 ].GetID ()},
509+ {attr : "$Object:payloadHash" , val : hex .EncodeToString (css [1 ]), cnr : objs [1 ].GetContainerID (), exp : objs [1 ].GetID ()},
510+ {attr : "$Object:payloadHash" , val : hex .EncodeToString (css [2 ]), cnr : objs [2 ].GetContainerID (), exp : objs [2 ].GetID ()},
511+ {attr : "$Object:payloadHash" , val : hex .EncodeToString (css [3 ]), cnr : objs [3 ].GetContainerID (), exp : objs [3 ].GetID ()},
512+ {attr : "$Object:payloadHash" , val : hex .EncodeToString (css [4 ]), cnr : objs [4 ].GetContainerID (), exp : objs [4 ].GetID ()},
513+ {attr : "$Object:payloadHash" , val : hex .EncodeToString (pcs [:]), cnr : par .GetContainerID (), exp : par .GetID ()},
514+ {attr : "$Object:homomorphicHash" , val : hex .EncodeToString (hcss [0 ]), cnr : objs [0 ].GetContainerID (), exp : objs [0 ].GetID ()},
515+ {attr : "$Object:homomorphicHash" , val : hex .EncodeToString (hcss [1 ]), cnr : objs [1 ].GetContainerID (), exp : objs [1 ].GetID ()},
516+ {attr : "$Object:homomorphicHash" , val : hex .EncodeToString (hcss [2 ]), cnr : objs [2 ].GetContainerID (), exp : objs [2 ].GetID ()},
517+ {attr : "$Object:homomorphicHash" , val : hex .EncodeToString (hcss [3 ]), cnr : objs [3 ].GetContainerID (), exp : objs [3 ].GetID ()},
518+ {attr : "$Object:homomorphicHash" , val : hex .EncodeToString (hcss [4 ]), cnr : objs [4 ].GetContainerID (), exp : objs [4 ].GetID ()},
519+ {attr : "$Object:homomorphicHash" , val : hex .EncodeToString (phcs [:]), cnr : par .GetContainerID (), exp : par .GetID ()},
520+ {attr : "$Object:split.splitID" , val : objs [0 ].SplitID ().String (), cnr : objs [0 ].GetContainerID (), exp : objs [0 ].GetID ()},
521+ {attr : "$Object:split.splitID" , val : objs [1 ].SplitID ().String (), cnr : objs [1 ].GetContainerID (), exp : objs [1 ].GetID ()},
522+ {attr : "$Object:split.splitID" , val : objs [2 ].SplitID ().String (), cnr : objs [2 ].GetContainerID (), exp : objs [2 ].GetID ()},
523+ {attr : "$Object:split.splitID" , val : objs [3 ].SplitID ().String (), cnr : objs [3 ].GetContainerID (), exp : objs [3 ].GetID ()},
524+ {attr : "$Object:split.splitID" , val : objs [4 ].SplitID ().String (), cnr : objs [4 ].GetContainerID (), exp : objs [4 ].GetID ()},
525+ {attr : "$Object:split.splitID" , val : par .SplitID ().String (), cnr : par .GetContainerID (), exp : par .GetID ()},
526+ {attr : "$Object:split.parent" , val : objs [0 ].GetParentID ().String (), cnr : objs [0 ].GetContainerID (), exp : objs [0 ].GetID ()},
527+ {attr : "$Object:split.parent" , val : objs [1 ].GetParentID ().String (), cnr : objs [1 ].GetContainerID (), exp : objs [1 ].GetID ()},
528+ {attr : "$Object:split.parent" , val : objs [2 ].GetParentID ().String (), cnr : objs [2 ].GetContainerID (), exp : objs [2 ].GetID ()},
529+ {attr : "$Object:split.parent" , val : objs [3 ].GetParentID ().String (), cnr : objs [3 ].GetContainerID (), exp : objs [3 ].GetID ()},
530+ {attr : "$Object:split.parent" , val : objs [4 ].GetParentID ().String (), cnr : objs [4 ].GetContainerID (), exp : objs [4 ].GetID ()},
531+ {attr : "$Object:split.parent" , val : par .GetParentID ().String (), cnr : par .GetContainerID (), exp : par .GetID ()},
532+ {attr : "$Object:split.first" , val : objs [0 ].GetFirstID ().String (), cnr : objs [0 ].GetContainerID (), exp : objs [0 ].GetID ()},
533+ {attr : "$Object:split.first" , val : objs [1 ].GetFirstID ().String (), cnr : objs [1 ].GetContainerID (), exp : objs [1 ].GetID ()},
534+ {attr : "$Object:split.first" , val : objs [2 ].GetFirstID ().String (), cnr : objs [2 ].GetContainerID (), exp : objs [2 ].GetID ()},
535+ {attr : "$Object:split.first" , val : objs [3 ].GetFirstID ().String (), cnr : objs [3 ].GetContainerID (), exp : objs [3 ].GetID ()},
536+ {attr : "$Object:split.first" , val : objs [4 ].GetFirstID ().String (), cnr : objs [4 ].GetContainerID (), exp : objs [4 ].GetID ()},
537+ {attr : "$Object:split.first" , val : par .GetFirstID ().String (), cnr : par .GetContainerID (), exp : par .GetID ()},
538+ {attr : "Index" , val : "0" , cnr : objs [0 ].GetContainerID (), exp : objs [0 ].GetID ()},
539+ {attr : "Index" , val : "1" , cnr : objs [1 ].GetContainerID (), exp : objs [1 ].GetID ()},
540+ {attr : "Index" , val : "2" , cnr : objs [2 ].GetContainerID (), exp : objs [2 ].GetID ()},
541+ {attr : "Index" , val : "3" , cnr : objs [3 ].GetContainerID (), exp : objs [3 ].GetID ()},
542+ {attr : "Index" , val : "4" , cnr : objs [4 ].GetContainerID (), exp : objs [4 ].GetID ()},
543+ {attr : "Index" , val : "9999" , cnr : par .GetContainerID (), exp : par .GetID ()},
544+ } {
545+ var fs object.SearchFilters
546+ fs .AddFilter (tc .attr , tc .val , object .MatchStringEqual )
547+ res , _ , err := db .Search (tc .cnr , fs , nil , nil , 1000 )
548+ require .NoError (t , err , tc )
549+ if ! tc .par {
550+ require .Len (t , res , 1 , tc )
551+ require .Equal (t , tc .exp , res [0 ].ID , tc )
552+ } else {
553+ require .Len (t , res , 2 , tc )
554+ require .True (t , slices .ContainsFunc (res , func (r client.SearchResultItem ) bool { return r .ID == objs [0 ].GetID () }))
555+ require .True (t , slices .ContainsFunc (res , func (r client.SearchResultItem ) bool { return r .ID == par .GetID () }))
556+ }
557+ }
558+
559+ for i := range objs {
560+ var fs object.SearchFilters
561+ fs .AddRootFilter ()
562+ res , _ , err = db .Search (objs [i ].GetContainerID (), fs , nil , nil , 1000 )
563+ require .NoError (t , err , i )
564+ require .Len (t , res , 1 , i )
565+ if i == 0 {
566+ require .Equal (t , par .GetID (), res [0 ].ID )
567+ } else {
568+ require .Equal (t , objs [i ].GetID (), res [0 ].ID , i )
569+ }
570+ fs = fs [:0 ]
571+ fs .AddPhyFilter ()
572+ res , _ , err = db .Search (objs [i ].GetContainerID (), fs , nil , nil , 1000 )
573+ require .NoError (t , err , i )
574+ if i == 0 {
575+ require .Len (t , res , 2 )
576+ require .True (t , slices .ContainsFunc (res , func (r client.SearchResultItem ) bool { return r .ID == objs [0 ].GetID () }))
577+ require .True (t , slices .ContainsFunc (res , func (r client.SearchResultItem ) bool { return r .ID == par .GetID () }))
578+ } else {
579+ require .Len (t , res , 1 )
580+ require .Equal (t , objs [i ].GetID (), res [0 ].ID , i )
581+ }
582+ }
583+ }
0 commit comments