[#671] Add general sanity tests for object stores
All checks were successful
DCO action / DCO (pull_request) Successful in 4m1s
Vulncheck / Vulncheck (pull_request) Successful in 4m14s
Build / Build Components (1.20) (pull_request) Successful in 5m35s
Build / Build Components (1.21) (pull_request) Successful in 5m35s
Tests and linters / Staticcheck (pull_request) Successful in 6m1s
Tests and linters / Lint (pull_request) Successful in 7m23s
Tests and linters / Tests (1.20) (pull_request) Successful in 7m52s
Tests and linters / Tests (1.21) (pull_request) Successful in 8m15s
Tests and linters / Tests with -race (pull_request) Successful in 12m6s

Signed-off-by: Alejandro Lopez <a.lopez@yadro.com>
This commit is contained in:
Alejandro Lopez 2023-08-31 17:21:24 +03:00
parent b9b86d2ec8
commit 98744aa5e1

View 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
}