[#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
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:
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