478 lines
14 KiB
Go
478 lines
14 KiB
Go
/*
|
|
Copyright 2018 the Velero contributors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package clientmgmt
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/heptio/velero/pkg/plugin/framework"
|
|
"github.com/heptio/velero/pkg/util/test"
|
|
)
|
|
|
|
type mockRegistry struct {
|
|
mock.Mock
|
|
}
|
|
|
|
func (r *mockRegistry) DiscoverPlugins() error {
|
|
args := r.Called()
|
|
return args.Error(0)
|
|
}
|
|
|
|
func (r *mockRegistry) List(kind framework.PluginKind) []framework.PluginIdentifier {
|
|
args := r.Called(kind)
|
|
return args.Get(0).([]framework.PluginIdentifier)
|
|
}
|
|
|
|
func (r *mockRegistry) Get(kind framework.PluginKind, name string) (framework.PluginIdentifier, error) {
|
|
args := r.Called(kind, name)
|
|
var id framework.PluginIdentifier
|
|
if args.Get(0) != nil {
|
|
id = args.Get(0).(framework.PluginIdentifier)
|
|
}
|
|
return id, args.Error(1)
|
|
}
|
|
|
|
func TestNewManager(t *testing.T) {
|
|
logger := test.NewLogger()
|
|
logLevel := logrus.InfoLevel
|
|
|
|
registry := &mockRegistry{}
|
|
defer registry.AssertExpectations(t)
|
|
|
|
m := NewManager(logger, logLevel, registry).(*manager)
|
|
assert.Equal(t, logger, m.logger)
|
|
assert.Equal(t, logLevel, m.logLevel)
|
|
assert.Equal(t, registry, m.registry)
|
|
assert.NotNil(t, m.restartableProcesses)
|
|
assert.Empty(t, m.restartableProcesses)
|
|
}
|
|
|
|
type mockRestartableProcessFactory struct {
|
|
mock.Mock
|
|
}
|
|
|
|
func (f *mockRestartableProcessFactory) newRestartableProcess(command string, logger logrus.FieldLogger, logLevel logrus.Level) (RestartableProcess, error) {
|
|
args := f.Called(command, logger, logLevel)
|
|
var rp RestartableProcess
|
|
if args.Get(0) != nil {
|
|
rp = args.Get(0).(RestartableProcess)
|
|
}
|
|
return rp, args.Error(1)
|
|
}
|
|
|
|
type mockRestartableProcess struct {
|
|
mock.Mock
|
|
}
|
|
|
|
func (rp *mockRestartableProcess) addReinitializer(key kindAndName, r reinitializer) {
|
|
rp.Called(key, r)
|
|
}
|
|
|
|
func (rp *mockRestartableProcess) reset() error {
|
|
args := rp.Called()
|
|
return args.Error(0)
|
|
}
|
|
|
|
func (rp *mockRestartableProcess) resetIfNeeded() error {
|
|
args := rp.Called()
|
|
return args.Error(0)
|
|
}
|
|
|
|
func (rp *mockRestartableProcess) getByKindAndName(key kindAndName) (interface{}, error) {
|
|
args := rp.Called(key)
|
|
return args.Get(0), args.Error(1)
|
|
}
|
|
|
|
func (rp *mockRestartableProcess) stop() {
|
|
rp.Called()
|
|
}
|
|
|
|
func TestGetRestartableProcess(t *testing.T) {
|
|
logger := test.NewLogger()
|
|
logLevel := logrus.InfoLevel
|
|
|
|
registry := &mockRegistry{}
|
|
defer registry.AssertExpectations(t)
|
|
|
|
m := NewManager(logger, logLevel, registry).(*manager)
|
|
factory := &mockRestartableProcessFactory{}
|
|
defer factory.AssertExpectations(t)
|
|
m.restartableProcessFactory = factory
|
|
|
|
// Test 1: registry error
|
|
pluginKind := framework.PluginKindBackupItemAction
|
|
pluginName := "pod"
|
|
registry.On("Get", pluginKind, pluginName).Return(nil, errors.Errorf("registry")).Once()
|
|
rp, err := m.getRestartableProcess(pluginKind, pluginName)
|
|
assert.Nil(t, rp)
|
|
assert.EqualError(t, err, "registry")
|
|
|
|
// Test 2: registry ok, factory error
|
|
podID := framework.PluginIdentifier{
|
|
Command: "/command",
|
|
Kind: pluginKind,
|
|
Name: pluginName,
|
|
}
|
|
registry.On("Get", pluginKind, pluginName).Return(podID, nil)
|
|
factory.On("newRestartableProcess", podID.Command, logger, logLevel).Return(nil, errors.Errorf("factory")).Once()
|
|
rp, err = m.getRestartableProcess(pluginKind, pluginName)
|
|
assert.Nil(t, rp)
|
|
assert.EqualError(t, err, "factory")
|
|
|
|
// Test 3: registry ok, factory ok
|
|
restartableProcess := &mockRestartableProcess{}
|
|
defer restartableProcess.AssertExpectations(t)
|
|
factory.On("newRestartableProcess", podID.Command, logger, logLevel).Return(restartableProcess, nil).Once()
|
|
rp, err = m.getRestartableProcess(pluginKind, pluginName)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, restartableProcess, rp)
|
|
|
|
// Test 4: retrieve from cache
|
|
rp, err = m.getRestartableProcess(pluginKind, pluginName)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, restartableProcess, rp)
|
|
}
|
|
|
|
func TestCleanupClients(t *testing.T) {
|
|
logger := test.NewLogger()
|
|
logLevel := logrus.InfoLevel
|
|
|
|
registry := &mockRegistry{}
|
|
defer registry.AssertExpectations(t)
|
|
|
|
m := NewManager(logger, logLevel, registry).(*manager)
|
|
|
|
for i := 0; i < 5; i++ {
|
|
rp := &mockRestartableProcess{}
|
|
defer rp.AssertExpectations(t)
|
|
rp.On("stop")
|
|
m.restartableProcesses[fmt.Sprintf("rp%d", i)] = rp
|
|
}
|
|
|
|
m.CleanupClients()
|
|
}
|
|
|
|
func TestGetObjectStore(t *testing.T) {
|
|
getPluginTest(t,
|
|
framework.PluginKindObjectStore,
|
|
"velero.io/aws",
|
|
func(m Manager, name string) (interface{}, error) {
|
|
return m.GetObjectStore(name)
|
|
},
|
|
func(name string, sharedPluginProcess RestartableProcess) interface{} {
|
|
return &restartableObjectStore{
|
|
key: kindAndName{kind: framework.PluginKindObjectStore, name: name},
|
|
sharedPluginProcess: sharedPluginProcess,
|
|
}
|
|
},
|
|
true,
|
|
)
|
|
}
|
|
|
|
func TestGetVolumeSnapshotter(t *testing.T) {
|
|
getPluginTest(t,
|
|
framework.PluginKindVolumeSnapshotter,
|
|
"velero.io/aws",
|
|
func(m Manager, name string) (interface{}, error) {
|
|
return m.GetVolumeSnapshotter(name)
|
|
},
|
|
func(name string, sharedPluginProcess RestartableProcess) interface{} {
|
|
return &restartableVolumeSnapshotter{
|
|
key: kindAndName{kind: framework.PluginKindVolumeSnapshotter, name: name},
|
|
sharedPluginProcess: sharedPluginProcess,
|
|
}
|
|
},
|
|
true,
|
|
)
|
|
}
|
|
|
|
func TestGetBackupItemAction(t *testing.T) {
|
|
getPluginTest(t,
|
|
framework.PluginKindBackupItemAction,
|
|
"velero.io/pod",
|
|
func(m Manager, name string) (interface{}, error) {
|
|
return m.GetBackupItemAction(name)
|
|
},
|
|
func(name string, sharedPluginProcess RestartableProcess) interface{} {
|
|
return &restartableBackupItemAction{
|
|
key: kindAndName{kind: framework.PluginKindBackupItemAction, name: name},
|
|
sharedPluginProcess: sharedPluginProcess,
|
|
}
|
|
},
|
|
false,
|
|
)
|
|
}
|
|
|
|
func TestGetRestoreItemAction(t *testing.T) {
|
|
getPluginTest(t,
|
|
framework.PluginKindRestoreItemAction,
|
|
"velero.io/pod",
|
|
func(m Manager, name string) (interface{}, error) {
|
|
return m.GetRestoreItemAction(name)
|
|
},
|
|
func(name string, sharedPluginProcess RestartableProcess) interface{} {
|
|
return &restartableRestoreItemAction{
|
|
key: kindAndName{kind: framework.PluginKindRestoreItemAction, name: name},
|
|
sharedPluginProcess: sharedPluginProcess,
|
|
}
|
|
},
|
|
false,
|
|
)
|
|
}
|
|
|
|
func getPluginTest(
|
|
t *testing.T,
|
|
kind framework.PluginKind,
|
|
name string,
|
|
getPluginFunc func(m Manager, name string) (interface{}, error),
|
|
expectedResultFunc func(name string, sharedPluginProcess RestartableProcess) interface{},
|
|
reinitializable bool,
|
|
) {
|
|
logger := test.NewLogger()
|
|
logLevel := logrus.InfoLevel
|
|
|
|
registry := &mockRegistry{}
|
|
defer registry.AssertExpectations(t)
|
|
|
|
m := NewManager(logger, logLevel, registry).(*manager)
|
|
factory := &mockRestartableProcessFactory{}
|
|
defer factory.AssertExpectations(t)
|
|
m.restartableProcessFactory = factory
|
|
|
|
pluginKind := kind
|
|
pluginName := name
|
|
pluginID := framework.PluginIdentifier{
|
|
Command: "/command",
|
|
Kind: pluginKind,
|
|
Name: pluginName,
|
|
}
|
|
registry.On("Get", pluginKind, pluginName).Return(pluginID, nil)
|
|
|
|
restartableProcess := &mockRestartableProcess{}
|
|
defer restartableProcess.AssertExpectations(t)
|
|
|
|
// Test 1: error getting restartable process
|
|
factory.On("newRestartableProcess", pluginID.Command, logger, logLevel).Return(nil, errors.Errorf("newRestartableProcess")).Once()
|
|
actual, err := getPluginFunc(m, pluginName)
|
|
assert.Nil(t, actual)
|
|
assert.EqualError(t, err, "newRestartableProcess")
|
|
|
|
// Test 2: happy path
|
|
factory.On("newRestartableProcess", pluginID.Command, logger, logLevel).Return(restartableProcess, nil).Once()
|
|
|
|
expected := expectedResultFunc(name, restartableProcess)
|
|
if reinitializable {
|
|
key := kindAndName{kind: pluginID.Kind, name: pluginID.Name}
|
|
restartableProcess.On("addReinitializer", key, expected)
|
|
}
|
|
|
|
actual, err = getPluginFunc(m, pluginName)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, expected, actual)
|
|
}
|
|
|
|
func TestGetBackupItemActions(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
names []string
|
|
newRestartableProcessError error
|
|
expectedError string
|
|
}{
|
|
{
|
|
name: "No items",
|
|
names: []string{},
|
|
},
|
|
{
|
|
name: "Error getting restartable process",
|
|
names: []string{"velero.io/a", "velero.io/b", "velero.io/c"},
|
|
newRestartableProcessError: errors.Errorf("newRestartableProcess"),
|
|
expectedError: "newRestartableProcess",
|
|
},
|
|
{
|
|
name: "Happy path",
|
|
names: []string{"velero.io/a", "velero.io/b", "velero.io/c"},
|
|
},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
logger := test.NewLogger()
|
|
logLevel := logrus.InfoLevel
|
|
|
|
registry := &mockRegistry{}
|
|
defer registry.AssertExpectations(t)
|
|
|
|
m := NewManager(logger, logLevel, registry).(*manager)
|
|
factory := &mockRestartableProcessFactory{}
|
|
defer factory.AssertExpectations(t)
|
|
m.restartableProcessFactory = factory
|
|
|
|
pluginKind := framework.PluginKindBackupItemAction
|
|
var pluginIDs []framework.PluginIdentifier
|
|
for i := range tc.names {
|
|
pluginID := framework.PluginIdentifier{
|
|
Command: "/command",
|
|
Kind: pluginKind,
|
|
Name: tc.names[i],
|
|
}
|
|
pluginIDs = append(pluginIDs, pluginID)
|
|
}
|
|
registry.On("List", pluginKind).Return(pluginIDs)
|
|
|
|
var expectedActions []interface{}
|
|
for i := range pluginIDs {
|
|
pluginID := pluginIDs[i]
|
|
pluginName := pluginID.Name
|
|
|
|
registry.On("Get", pluginKind, pluginName).Return(pluginID, nil)
|
|
|
|
restartableProcess := &mockRestartableProcess{}
|
|
defer restartableProcess.AssertExpectations(t)
|
|
|
|
expected := &restartableBackupItemAction{
|
|
key: kindAndName{kind: pluginKind, name: pluginName},
|
|
sharedPluginProcess: restartableProcess,
|
|
}
|
|
|
|
if tc.newRestartableProcessError != nil {
|
|
// Test 1: error getting restartable process
|
|
factory.On("newRestartableProcess", pluginID.Command, logger, logLevel).Return(nil, errors.Errorf("newRestartableProcess")).Once()
|
|
break
|
|
}
|
|
|
|
// Test 2: happy path
|
|
if i == 0 {
|
|
factory.On("newRestartableProcess", pluginID.Command, logger, logLevel).Return(restartableProcess, nil).Once()
|
|
}
|
|
|
|
expectedActions = append(expectedActions, expected)
|
|
}
|
|
|
|
backupItemActions, err := m.GetBackupItemActions()
|
|
if tc.newRestartableProcessError != nil {
|
|
assert.Nil(t, backupItemActions)
|
|
assert.EqualError(t, err, "newRestartableProcess")
|
|
} else {
|
|
require.NoError(t, err)
|
|
var actual []interface{}
|
|
for i := range backupItemActions {
|
|
actual = append(actual, backupItemActions[i])
|
|
}
|
|
assert.Equal(t, expectedActions, actual)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetRestoreItemActions(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
names []string
|
|
newRestartableProcessError error
|
|
expectedError string
|
|
}{
|
|
{
|
|
name: "No items",
|
|
names: []string{},
|
|
},
|
|
{
|
|
name: "Error getting restartable process",
|
|
names: []string{"velero.io/a", "velero.io/b", "velero.io/c"},
|
|
newRestartableProcessError: errors.Errorf("newRestartableProcess"),
|
|
expectedError: "newRestartableProcess",
|
|
},
|
|
{
|
|
name: "Happy path",
|
|
names: []string{"velero.io/a", "velero.io/b", "velero.io/c"},
|
|
},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
logger := test.NewLogger()
|
|
logLevel := logrus.InfoLevel
|
|
|
|
registry := &mockRegistry{}
|
|
defer registry.AssertExpectations(t)
|
|
|
|
m := NewManager(logger, logLevel, registry).(*manager)
|
|
factory := &mockRestartableProcessFactory{}
|
|
defer factory.AssertExpectations(t)
|
|
m.restartableProcessFactory = factory
|
|
|
|
pluginKind := framework.PluginKindRestoreItemAction
|
|
var pluginIDs []framework.PluginIdentifier
|
|
for i := range tc.names {
|
|
pluginID := framework.PluginIdentifier{
|
|
Command: "/command",
|
|
Kind: pluginKind,
|
|
Name: tc.names[i],
|
|
}
|
|
pluginIDs = append(pluginIDs, pluginID)
|
|
}
|
|
registry.On("List", pluginKind).Return(pluginIDs)
|
|
|
|
var expectedActions []interface{}
|
|
for i := range pluginIDs {
|
|
pluginID := pluginIDs[i]
|
|
pluginName := pluginID.Name
|
|
|
|
registry.On("Get", pluginKind, pluginName).Return(pluginID, nil)
|
|
|
|
restartableProcess := &mockRestartableProcess{}
|
|
defer restartableProcess.AssertExpectations(t)
|
|
|
|
expected := &restartableRestoreItemAction{
|
|
key: kindAndName{kind: pluginKind, name: pluginName},
|
|
sharedPluginProcess: restartableProcess,
|
|
}
|
|
|
|
if tc.newRestartableProcessError != nil {
|
|
// Test 1: error getting restartable process
|
|
factory.On("newRestartableProcess", pluginID.Command, logger, logLevel).Return(nil, errors.Errorf("newRestartableProcess")).Once()
|
|
break
|
|
}
|
|
|
|
// Test 2: happy path
|
|
if i == 0 {
|
|
factory.On("newRestartableProcess", pluginID.Command, logger, logLevel).Return(restartableProcess, nil).Once()
|
|
}
|
|
|
|
expectedActions = append(expectedActions, expected)
|
|
}
|
|
|
|
restoreItemActions, err := m.GetRestoreItemActions()
|
|
if tc.newRestartableProcessError != nil {
|
|
assert.Nil(t, restoreItemActions)
|
|
assert.EqualError(t, err, "newRestartableProcess")
|
|
} else {
|
|
require.NoError(t, err)
|
|
var actual []interface{}
|
|
for i := range restoreItemActions {
|
|
actual = append(actual, restoreItemActions[i])
|
|
}
|
|
assert.Equal(t, expectedActions, actual)
|
|
}
|
|
})
|
|
}
|
|
}
|