[#300] container: Allow to iterate over container list

Signed-off-by: Evgenii Stratonikov <evgeniy@morphbits.ru>
This commit is contained in:
Evgenii Stratonikov 2022-12-22 13:35:52 +03:00 committed by Alex Vanin
parent 03bff785d2
commit 4f3c08f552
3 changed files with 94 additions and 17 deletions

View file

@ -1,5 +1,5 @@
name: "FrostFS Container" 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: permissions:
- methods: ["update", "addKey", "transferX", - methods: ["update", "addKey", "transferX",
"register", "addRecord", "deleteRecords"] "register", "addRecord", "deleteRecords"]

View file

@ -62,6 +62,8 @@ const (
singleEstimatePrefix = "est" singleEstimatePrefix = "est"
estimateKeyPrefix = "cnr" estimateKeyPrefix = "cnr"
containerKeyPrefix = 'x'
ownerKeyPrefix = 'o'
estimatePostfixSize = 10 estimatePostfixSize = 10
// CleanupDelta contains the number of the last epochs for which container estimations are present. // CleanupDelta contains the number of the last epochs for which container estimations are present.
CleanupDelta = 3 CleanupDelta = 3
@ -93,6 +95,26 @@ func _deploy(data interface{}, isUpdate bool) {
if isUpdate { if isUpdate {
args := data.([]interface{}) args := data.([]interface{})
common.CheckVersion(args[len(args)-1].(int)) 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 return
} }
@ -391,17 +413,24 @@ func Owner(containerID []byte) []byte {
func Count() int { func Count() int {
count := 0 count := 0
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
it := storage.Find(ctx, []byte{}, storage.KeysOnly) it := storage.Find(ctx, []byte{containerKeyPrefix}, storage.KeysOnly)
for iterator.Next(it) { for iterator.Next(it) {
key := iterator.Value(it).([]byte)
// V2 format
if len(key) == containerIDSize {
count++ count++
} }
}
return 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. // List method returns a list of all container IDs owned by the specified owner.
func List(owner []byte) [][]byte { func List(owner []byte) [][]byte {
ctx := storage.GetReadOnlyContext() ctx := storage.GetReadOnlyContext()
@ -412,7 +441,7 @@ func List(owner []byte) [][]byte {
var list [][]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) { for iterator.Next(it) {
id := iterator.Value(it).([]byte) id := iterator.Value(it).([]byte)
list = append(list, id) list = append(list, id)
@ -700,30 +729,31 @@ func Version() int {
} }
func addContainer(ctx storage.Context, id, owner []byte, container Container) { 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) 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) { 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, containerListKey)
storage.Delete(ctx, id) storage.Delete(ctx, append([]byte{containerKeyPrefix}, id...))
} }
func getAllContainers(ctx storage.Context) [][]byte { func getAllContainers(ctx storage.Context) [][]byte {
var list [][]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) { for iterator.Next(it) {
key := iterator.Value(it).([]byte) // it MUST BE `storage.KeysOnly` key := iterator.Value(it).([]byte) // it MUST BE `storage.KeysOnly`
// V2 format // V2 format
if len(key) == containerIDSize {
list = append(list, key) list = append(list, key)
} }
}
return list return list
} }
@ -739,7 +769,7 @@ func getEACL(ctx storage.Context, cid []byte) ExtendedACL {
} }
func getContainer(ctx storage.Context, cid []byte) Container { func getContainer(ctx storage.Context, cid []byte) Container {
data := storage.Get(ctx, cid) data := storage.Get(ctx, append([]byte{containerKeyPrefix}, cid...))
if data != nil { if data != nil {
return std.Deserialize(data.([]byte)).(Container) return std.Deserialize(data.([]byte)).(Container)
} }

View file

@ -103,15 +103,62 @@ func TestContainerCount(t *testing.T) {
cnt3 := dummyContainer(acc1) cnt3 := dummyContainer(acc1)
balanceMint(t, cBal, acc1, containerFee*1, []byte{}) balanceMint(t, cBal, acc1, containerFee*1, []byte{})
c.Invoke(t, stackitem.Null{}, "put", cnt3.value, cnt3.sig, cnt3.pub, cnt3.token) 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) c.Invoke(t, stackitem.Null{}, "delete", cnt1.id[:], cnt1.sig, cnt1.token)
checkCount(t, 2) checkCount(t, 2)
checkContainerList(t, c, [][]byte{cnt2.id[:], cnt3.id[:]})
c.Invoke(t, stackitem.Null{}, "delete", cnt2.id[:], cnt2.sig, cnt2.token) c.Invoke(t, stackitem.Null{}, "delete", cnt2.id[:], cnt2.sig, cnt2.token)
checkCount(t, 1) checkCount(t, 1)
checkContainerList(t, c, [][]byte{cnt3.id[:]})
c.Invoke(t, stackitem.Null{}, "delete", cnt3.id[:], cnt3.sig, cnt3.token) c.Invoke(t, stackitem.Null{}, "delete", cnt3.id[:], cnt3.sig, cnt3.token)
checkCount(t, 0) 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) { func TestContainerPut(t *testing.T) {