frostfs-node/pkg/local_object_storage/internal/storagetest/storage.go

123 lines
3.6 KiB
Go

package storagetest
import (
"context"
"testing"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/shard/mode"
"github.com/stretchr/testify/require"
)
// Component represents single storage component.
type Component interface {
Open(context.Context, mode.Mode) error
SetMode(mode.Mode) error
Init() error
Close() error
}
// Constructor constructs storage component.
// Each call must create a component using different file-system path.
type Constructor = func(t *testing.T) Component
// TestAll checks that storage component doesn't panic under
// any circumstances during shard operation.
func TestAll(t *testing.T, cons Constructor) {
modes := []mode.Mode{
mode.ReadWrite,
mode.ReadOnly,
mode.Degraded,
mode.DegradedReadOnly,
}
t.Run("close after open", func(t *testing.T) {
TestCloseAfterOpen(t, cons)
})
t.Run("close twice", func(t *testing.T) {
TestCloseTwice(t, cons)
})
t.Run("set mode", func(t *testing.T) {
for _, m := range modes {
t.Run(m.String(), func(t *testing.T) {
TestSetMode(t, cons, m)
})
}
})
t.Run("mode transition", func(t *testing.T) {
for _, from := range modes {
for _, to := range modes {
TestModeTransition(t, cons, from, to)
}
}
})
}
// TestCloseAfterOpen checks that `Close` can be done right after `Open`.
// Use-case: open shard, encounter error, close before the initialization.
func TestCloseAfterOpen(t *testing.T, cons Constructor) {
t.Run("RW", func(t *testing.T) {
// Use-case: irrecoverable error on some components, close everything.
s := cons(t)
require.NoError(t, s.Open(context.Background(), mode.ReadWrite))
require.NoError(t, s.Close())
})
t.Run("RO", func(t *testing.T) {
// Use-case: irrecoverable error on some components, close everything.
// Open in read-only must be done after the db is here.
s := cons(t)
require.NoError(t, s.Open(context.Background(), mode.ReadWrite))
require.NoError(t, s.Init())
require.NoError(t, s.Close())
require.NoError(t, s.Open(context.Background(), mode.ReadOnly))
require.NoError(t, s.Close())
})
}
// TestCloseTwice checks that `Close` can be done twice.
func TestCloseTwice(t *testing.T, cons Constructor) {
// Use-case: move to maintenance mode twice, first time failed.
s := cons(t)
require.NoError(t, s.Open(context.Background(), mode.ReadWrite))
require.NoError(t, s.Init())
require.NoError(t, s.Close())
require.NoError(t, s.Close()) // already closed, no-op
}
// TestSetMode checks that any mode transition can be done safely.
func TestSetMode(t *testing.T, cons Constructor, m mode.Mode) {
t.Run("before init", func(t *testing.T) {
// Use-case: metabase `Init` failed,
// call `SetMode` on all not-yet-initialized components.
s := cons(t)
require.NoError(t, s.Open(context.Background(), mode.ReadWrite))
require.NoError(t, s.SetMode(m))
t.Run("after open in RO", func(t *testing.T) {
require.NoError(t, s.Close())
require.NoError(t, s.Open(context.Background(), mode.ReadOnly))
require.NoError(t, s.SetMode(m))
})
require.NoError(t, s.Close())
})
t.Run("after init", func(t *testing.T) {
s := cons(t)
// Use-case: notmal node operation.
require.NoError(t, s.Open(context.Background(), mode.ReadWrite))
require.NoError(t, s.Init())
require.NoError(t, s.SetMode(m))
require.NoError(t, s.Close())
})
}
func TestModeTransition(t *testing.T, cons Constructor, from, to mode.Mode) {
// Use-case: normal node operation.
s := cons(t)
require.NoError(t, s.Open(context.Background(), mode.ReadWrite))
require.NoError(t, s.Init())
require.NoError(t, s.SetMode(from))
require.NoError(t, s.SetMode(to))
require.NoError(t, s.Close())
}