forked from TrueCloudLab/frostfs-node
[#671] Add general sanity tests for object stores
Signed-off-by: Alejandro Lopez <a.lopez@yadro.com>
This commit is contained in:
parent
b9b86d2ec8
commit
98744aa5e1
1 changed files with 182 additions and 0 deletions
182
pkg/local_object_storage/internal/storagetest/sanity.go
Normal file
182
pkg/local_object_storage/internal/storagetest/sanity.go
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
package storagetest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/internal/testutil"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||||
|
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||||
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
|
oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"golang.org/x/exp/rand"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ObjectStore is the general interface of implementations of object storage.
|
||||||
|
//
|
||||||
|
// It can be used to wrap other storages and execute generic sanity tests on them.
|
||||||
|
type ObjectStore interface {
|
||||||
|
Put(*objectSDK.Object) error
|
||||||
|
Get(oid.Address) (*objectSDK.Object, error)
|
||||||
|
Delete(oid.Address) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSanityBasicLifecycle(t *testing.T, st ObjectStore) {
|
||||||
|
obj := testutil.GenerateObject()
|
||||||
|
addr := testutil.AddressFromObject(t, obj)
|
||||||
|
data, err := obj.Marshal()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Get nonexistent object
|
||||||
|
{
|
||||||
|
_, gotErr := st.Get(addr)
|
||||||
|
require.True(t, client.IsErrObjectNotFound(gotErr))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put an object
|
||||||
|
require.NoError(t, st.Put(obj))
|
||||||
|
|
||||||
|
// Get the object previously put
|
||||||
|
{
|
||||||
|
gotObj, err := st.Get(addr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
gotData, err := gotObj.Marshal()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, data, gotData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the object previously put
|
||||||
|
{
|
||||||
|
require.NoError(t, st.Delete(addr))
|
||||||
|
require.True(t, client.IsErrObjectNotFound(st.Delete(addr)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the object previously deleted
|
||||||
|
{
|
||||||
|
_, gotErr := st.Get(addr)
|
||||||
|
require.True(t, client.IsErrObjectNotFound(gotErr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSanityPutGetSequence(t *testing.T, st ObjectStore, iterations int) {
|
||||||
|
var objs []*objectSDK.Object
|
||||||
|
|
||||||
|
for i := 0; i < iterations; i++ {
|
||||||
|
obj := testutil.GenerateObject()
|
||||||
|
require.NoError(t, st.Put(obj))
|
||||||
|
objs = append(objs, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, wantObj := range objs {
|
||||||
|
addr := testutil.AddressFromObject(t, wantObj)
|
||||||
|
gotObj, err := st.Get(addr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
wantData, err := wantObj.Marshal()
|
||||||
|
require.NoError(t, err)
|
||||||
|
gotData, err := gotObj.Marshal()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, wantData, gotData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSanityPutDeleteSequence(t *testing.T, st ObjectStore, iterations int) {
|
||||||
|
for i := 0; i < iterations; i++ {
|
||||||
|
obj := testutil.GenerateObject()
|
||||||
|
addr := testutil.AddressFromObject(t, obj)
|
||||||
|
require.NoError(t, st.Put(obj))
|
||||||
|
require.NoError(t, st.Delete(addr))
|
||||||
|
require.True(t, client.IsErrObjectNotFound(st.Delete(addr)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSanityStress(t *testing.T, st ObjectStore, iterations, maxResidentObjects int) {
|
||||||
|
type entry struct {
|
||||||
|
obj *objectSDK.Object
|
||||||
|
index int
|
||||||
|
}
|
||||||
|
|
||||||
|
objs := map[oid.Address]*entry{}
|
||||||
|
var addrs []oid.Address
|
||||||
|
|
||||||
|
putOne := func() {
|
||||||
|
obj := testutil.GenerateObject()
|
||||||
|
addr := testutil.AddressFromObject(t, obj)
|
||||||
|
require.NoError(t, st.Put(obj))
|
||||||
|
objs[addr] = &entry{obj, len(addrs)}
|
||||||
|
addrs = append(addrs, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
putOverwrite := func() {
|
||||||
|
index := rand.Intn(len(addrs))
|
||||||
|
addr := addrs[index]
|
||||||
|
require.NoError(t, st.Put(objs[addr].obj))
|
||||||
|
}
|
||||||
|
|
||||||
|
getOne := func() {
|
||||||
|
index := rand.Intn(len(addrs))
|
||||||
|
addr := addrs[index]
|
||||||
|
|
||||||
|
wantObj := objs[addr].obj
|
||||||
|
wantData, err := wantObj.Marshal()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
gotObj, err := st.Get(addr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
gotData, err := gotObj.Marshal()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, wantData, gotData)
|
||||||
|
}
|
||||||
|
|
||||||
|
getNonexistent := func() {
|
||||||
|
addr := randNonexistingAddr(objs)
|
||||||
|
_, err := st.Get(addr)
|
||||||
|
require.True(t, client.IsErrObjectNotFound(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteOne := func() {
|
||||||
|
index := rand.Intn(len(addrs))
|
||||||
|
addr := addrs[index]
|
||||||
|
|
||||||
|
require.NoError(t, st.Delete(addr))
|
||||||
|
|
||||||
|
objs[addrs[len(addrs)-1]].index = index
|
||||||
|
addrs[index] = addrs[len(addrs)-1]
|
||||||
|
addrs = addrs[:len(addrs)-1]
|
||||||
|
delete(objs, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteNonexistent := func() {
|
||||||
|
addr := randNonexistingAddr(objs)
|
||||||
|
require.True(t, client.IsErrObjectNotFound(st.Delete(addr)))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < iterations; i++ {
|
||||||
|
// Make a slice of the operations that are currently possible
|
||||||
|
validOps := []func(){getNonexistent, deleteNonexistent}
|
||||||
|
|
||||||
|
if len(addrs) < maxResidentObjects {
|
||||||
|
validOps = append(validOps, putOne)
|
||||||
|
}
|
||||||
|
if len(addrs) > 0 {
|
||||||
|
validOps = append(validOps, getOne, putOverwrite, deleteOne)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run a random operation
|
||||||
|
validOps[rand.Intn(len(validOps))]()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func randNonexistingAddr[V any](objs map[oid.Address]V) oid.Address {
|
||||||
|
addr := oidtest.Address()
|
||||||
|
// Make sure the key doesn't exist
|
||||||
|
for {
|
||||||
|
if _, exists := objs[addr]; !exists {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
addr = oidtest.Address()
|
||||||
|
}
|
||||||
|
return addr
|
||||||
|
}
|
Loading…
Reference in a new issue