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"
|
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"]
|
||||||
|
|
|
@ -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)
|
count++
|
||||||
// V2 format
|
|
||||||
if len(key) == containerIDSize {
|
|
||||||
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,29 +729,30 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue