Skip to content

Commit

Permalink
[nspcc-dev#300] container: Allow to iterate over container list
Browse files Browse the repository at this point in the history
Signed-off-by: Evgenii Stratonikov <[email protected]>
  • Loading branch information
fyrchik authored and alexvanin committed Jan 16, 2023
1 parent 03bff78 commit 4f3c08f
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 17 deletions.
2 changes: 1 addition & 1 deletion container/config.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: "FrostFS Container"
safemethods: ["count", "get", "owner", "list", "eACL", "getContainerSize", "listContainerSizes", "iterateContainerSizes", "version"]
safemethods: ["count", "containersOf", "get", "owner", "list", "eACL", "getContainerSize", "listContainerSizes", "iterateContainerSizes", "version"]
permissions:
- methods: ["update", "addKey", "transferX",
"register", "addRecord", "deleteRecords"]
Expand Down
62 changes: 46 additions & 16 deletions container/container_contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ const (

singleEstimatePrefix = "est"
estimateKeyPrefix = "cnr"
containerKeyPrefix = 'x'
ownerKeyPrefix = 'o'
estimatePostfixSize = 10
// CleanupDelta contains the number of the last epochs for which container estimations are present.
CleanupDelta = 3
Expand Down Expand Up @@ -93,6 +95,26 @@ func _deploy(data interface{}, isUpdate bool) {
if isUpdate {
args := data.([]interface{})
common.CheckVersion(args[len(args)-1].(int))

it := storage.Find(ctx, []byte{}, storage.None)
for iterator.Next(it) {
item := iterator.Value(it).(struct {
key []byte
value []byte
})

// Migrate container.
if len(item.key) == containerIDSize {
storage.Delete(ctx, item.key)
storage.Put(ctx, append([]byte{containerKeyPrefix}, item.key...), item.value)
}

// Migrate owner-cid map.
if len(item.key) == 25 /* owner id size */ +containerIDSize {
storage.Delete(ctx, item.key)
storage.Put(ctx, append([]byte{ownerKeyPrefix}, item.key...), item.value)
}
}
return
}

Expand Down Expand Up @@ -391,17 +413,24 @@ func Owner(containerID []byte) []byte {
func Count() int {
count := 0
ctx := storage.GetReadOnlyContext()
it := storage.Find(ctx, []byte{}, storage.KeysOnly)
it := storage.Find(ctx, []byte{containerKeyPrefix}, storage.KeysOnly)
for iterator.Next(it) {
key := iterator.Value(it).([]byte)
// V2 format
if len(key) == containerIDSize {
count++
}
count++
}
return count
}

// ContainersOf iterates over all container IDs owned by the specified owner.
// If owner is nil, it iterates over all containers.
func ContainersOf(owner []byte) iterator.Iterator {
ctx := storage.GetReadOnlyContext()
key := []byte{ownerKeyPrefix}
if len(owner) != 0 {
key = append(key, owner...)
}
return storage.Find(ctx, key, storage.ValuesOnly)
}

// List method returns a list of all container IDs owned by the specified owner.
func List(owner []byte) [][]byte {
ctx := storage.GetReadOnlyContext()
Expand All @@ -412,7 +441,7 @@ func List(owner []byte) [][]byte {

var list [][]byte

it := storage.Find(ctx, owner, storage.ValuesOnly)
it := storage.Find(ctx, append([]byte{ownerKeyPrefix}, owner...), storage.ValuesOnly)
for iterator.Next(it) {
id := iterator.Value(it).([]byte)
list = append(list, id)
Expand Down Expand Up @@ -700,29 +729,30 @@ func Version() int {
}

func addContainer(ctx storage.Context, id, owner []byte, container Container) {
containerListKey := append(owner, id...)
containerListKey := append([]byte{ownerKeyPrefix}, owner...)
containerListKey = append(containerListKey, id...)
storage.Put(ctx, containerListKey, id)

common.SetSerialized(ctx, id, container)
idKey := append([]byte{containerKeyPrefix}, id...)
common.SetSerialized(ctx, idKey, container)
}

func removeContainer(ctx storage.Context, id []byte, owner []byte) {
containerListKey := append(owner, id...)
containerListKey := append([]byte{ownerKeyPrefix}, owner...)
containerListKey = append(containerListKey, id...)
storage.Delete(ctx, containerListKey)

storage.Delete(ctx, id)
storage.Delete(ctx, append([]byte{containerKeyPrefix}, id...))
}

func getAllContainers(ctx storage.Context) [][]byte {
var list [][]byte

it := storage.Find(ctx, []byte{}, storage.KeysOnly)
it := storage.Find(ctx, []byte{containerKeyPrefix}, storage.KeysOnly|storage.RemovePrefix)
for iterator.Next(it) {
key := iterator.Value(it).([]byte) // it MUST BE `storage.KeysOnly`
// V2 format
if len(key) == containerIDSize {
list = append(list, key)
}
list = append(list, key)
}

return list
Expand All @@ -739,7 +769,7 @@ func getEACL(ctx storage.Context, cid []byte) ExtendedACL {
}

func getContainer(ctx storage.Context, cid []byte) Container {
data := storage.Get(ctx, cid)
data := storage.Get(ctx, append([]byte{containerKeyPrefix}, cid...))
if data != nil {
return std.Deserialize(data.([]byte)).(Container)
}
Expand Down
47 changes: 47 additions & 0 deletions tests/container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,15 +103,62 @@ func TestContainerCount(t *testing.T) {
cnt3 := dummyContainer(acc1)
balanceMint(t, cBal, acc1, containerFee*1, []byte{})
c.Invoke(t, stackitem.Null{}, "put", cnt3.value, cnt3.sig, cnt3.pub, cnt3.token)
checkContainerList(t, c, [][]byte{cnt1.id[:], cnt2.id[:], cnt3.id[:]})

c.Invoke(t, stackitem.Null{}, "delete", cnt1.id[:], cnt1.sig, cnt1.token)
checkCount(t, 2)
checkContainerList(t, c, [][]byte{cnt2.id[:], cnt3.id[:]})

c.Invoke(t, stackitem.Null{}, "delete", cnt2.id[:], cnt2.sig, cnt2.token)
checkCount(t, 1)
checkContainerList(t, c, [][]byte{cnt3.id[:]})

c.Invoke(t, stackitem.Null{}, "delete", cnt3.id[:], cnt3.sig, cnt3.token)
checkCount(t, 0)
checkContainerList(t, c, [][]byte{})
}

func checkContainerList(t *testing.T, c *neotest.ContractInvoker, expected [][]byte) {
t.Run("check with `list`", func(t *testing.T) {
s, err := c.TestInvoke(t, "list", nil)
require.NoError(t, err)
require.Equal(t, 1, s.Len())

if len(expected) == 0 {
_, ok := s.Top().Item().(stackitem.Null)
require.True(t, ok)
return
}

arr, ok := s.Top().Value().([]stackitem.Item)
require.True(t, ok)
require.Equal(t, len(expected), len(arr))

actual := make([][]byte, 0, len(expected))
for i := range arr {
id, ok := arr[i].Value().([]byte)
require.True(t, ok)
actual = append(actual, id)
}
require.ElementsMatch(t, expected, actual)
})
t.Run("check with `containersOf`", func(t *testing.T) {
s, err := c.TestInvoke(t, "containersOf", nil)
require.NoError(t, err)
require.Equal(t, 1, s.Len())

iter, ok := s.Top().Value().(*storage.Iterator)
require.True(t, ok)

actual := make([][]byte, 0, len(expected))
for iter.Next() {
id, ok := iter.Value().Value().([]byte)
require.True(t, ok)
actual = append(actual, id)
}
require.ElementsMatch(t, expected, actual)
})

}

func TestContainerPut(t *testing.T) {
Expand Down

0 comments on commit 4f3c08f

Please sign in to comment.