forked from TrueCloudLab/frostfs-contract
[#300] container: Allow to iterate over container list
Signed-off-by: Evgenii Stratonikov <evgeniy@morphbits.ru>
This commit is contained in:
parent
03bff785d2
commit
4f3c08f552
3 changed files with 94 additions and 17 deletions
|
@ -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"]
|
||||
|
|
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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++
|
||||
}
|
||||
}
|
||||
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()
|
||||
|
@ -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)
|
||||
|
@ -700,30 +729,31 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue