mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-04-19 23:30:42 +00:00
Refactors the framework package to implement the plugin versioning changes needed for BIA v1 and overall package refactoring to support plugin versions in different packages. This should be all that's needed to move on to v2 for BackupItemAction. The remaining plugin types still need similar refactoring to what's being done here for BIA before attempting a v2 implementation. Signed-off-by: Scott Seago <sseago@redhat.com>
614 lines
18 KiB
Go
614 lines
18 KiB
Go
/*
|
|
Copyright 2020 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"
|
|
|
|
biav1cli "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/backupitemaction/v1"
|
|
"github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/process"
|
|
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
|
|
"github.com/vmware-tanzu/velero/pkg/plugin/framework/common"
|
|
"github.com/vmware-tanzu/velero/pkg/test"
|
|
)
|
|
|
|
type mockRegistry struct {
|
|
mock.Mock
|
|
}
|
|
|
|
func (r *mockRegistry) DiscoverPlugins() error {
|
|
args := r.Called()
|
|
return args.Error(0)
|
|
}
|
|
|
|
func (r *mockRegistry) List(kind common.PluginKind) []framework.PluginIdentifier {
|
|
args := r.Called(kind)
|
|
return args.Get(0).([]framework.PluginIdentifier)
|
|
}
|
|
|
|
func (r *mockRegistry) Get(kind common.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) (process.RestartableProcess, error) {
|
|
args := f.Called(command, logger, logLevel)
|
|
var rp process.RestartableProcess
|
|
if args.Get(0) != nil {
|
|
rp = args.Get(0).(process.RestartableProcess)
|
|
}
|
|
return rp, args.Error(1)
|
|
}
|
|
|
|
type mockRestartableProcess struct {
|
|
mock.Mock
|
|
}
|
|
|
|
func (rp *mockRestartableProcess) AddReinitializer(key process.KindAndName, r process.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 process.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 := common.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,
|
|
common.PluginKindObjectStore,
|
|
"velero.io/aws",
|
|
func(m Manager, name string) (interface{}, error) {
|
|
return m.GetObjectStore(name)
|
|
},
|
|
func(name string, sharedPluginProcess process.RestartableProcess) interface{} {
|
|
return &restartableObjectStore{
|
|
key: process.KindAndName{Kind: common.PluginKindObjectStore, Name: name},
|
|
sharedPluginProcess: sharedPluginProcess,
|
|
}
|
|
},
|
|
true,
|
|
)
|
|
}
|
|
|
|
func TestGetVolumeSnapshotter(t *testing.T) {
|
|
getPluginTest(t,
|
|
common.PluginKindVolumeSnapshotter,
|
|
"velero.io/aws",
|
|
func(m Manager, name string) (interface{}, error) {
|
|
return m.GetVolumeSnapshotter(name)
|
|
},
|
|
func(name string, sharedPluginProcess process.RestartableProcess) interface{} {
|
|
return &restartableVolumeSnapshotter{
|
|
key: process.KindAndName{Kind: common.PluginKindVolumeSnapshotter, Name: name},
|
|
sharedPluginProcess: sharedPluginProcess,
|
|
}
|
|
},
|
|
true,
|
|
)
|
|
}
|
|
|
|
func TestGetBackupItemAction(t *testing.T) {
|
|
getPluginTest(t,
|
|
common.PluginKindBackupItemAction,
|
|
"velero.io/pod",
|
|
func(m Manager, name string) (interface{}, error) {
|
|
return m.GetBackupItemAction(name)
|
|
},
|
|
func(name string, sharedPluginProcess process.RestartableProcess) interface{} {
|
|
return &biav1cli.RestartableBackupItemAction{
|
|
Key: process.KindAndName{Kind: common.PluginKindBackupItemAction, Name: name},
|
|
SharedPluginProcess: sharedPluginProcess,
|
|
}
|
|
},
|
|
false,
|
|
)
|
|
}
|
|
|
|
func TestGetRestoreItemAction(t *testing.T) {
|
|
getPluginTest(t,
|
|
common.PluginKindRestoreItemAction,
|
|
"velero.io/pod",
|
|
func(m Manager, name string) (interface{}, error) {
|
|
return m.GetRestoreItemAction(name)
|
|
},
|
|
func(name string, sharedPluginProcess process.RestartableProcess) interface{} {
|
|
return &restartableRestoreItemAction{
|
|
key: process.KindAndName{Kind: common.PluginKindRestoreItemAction, Name: name},
|
|
sharedPluginProcess: sharedPluginProcess,
|
|
}
|
|
},
|
|
false,
|
|
)
|
|
}
|
|
|
|
func getPluginTest(
|
|
t *testing.T,
|
|
kind common.PluginKind,
|
|
name string,
|
|
getPluginFunc func(m Manager, name string) (interface{}, error),
|
|
expectedResultFunc func(name string, sharedPluginProcess process.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 := process.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 := common.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 := &biav1cli.RestartableBackupItemAction{
|
|
Key: process.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 := common.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: process.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)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetDeleteItemAction(t *testing.T) {
|
|
getPluginTest(t,
|
|
common.PluginKindDeleteItemAction,
|
|
"velero.io/deleter",
|
|
func(m Manager, name string) (interface{}, error) {
|
|
return m.GetDeleteItemAction(name)
|
|
},
|
|
func(name string, sharedPluginProcess process.RestartableProcess) interface{} {
|
|
return &restartableDeleteItemAction{
|
|
key: process.KindAndName{Kind: common.PluginKindDeleteItemAction, Name: name},
|
|
sharedPluginProcess: sharedPluginProcess,
|
|
}
|
|
},
|
|
false,
|
|
)
|
|
}
|
|
|
|
func TestGetDeleteItemActions(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
names []string
|
|
newRestartableProcessError error
|
|
expectedError string
|
|
}{
|
|
{
|
|
name: "No items",
|
|
names: []string{},
|
|
},
|
|
}
|
|
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 := common.PluginKindDeleteItemAction
|
|
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: process.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)
|
|
}
|
|
|
|
deleteItemActions, err := m.GetDeleteItemActions()
|
|
if tc.newRestartableProcessError != nil {
|
|
assert.Nil(t, deleteItemActions)
|
|
assert.EqualError(t, err, "NewRestartableProcess")
|
|
} else {
|
|
require.NoError(t, err)
|
|
var actual []interface{}
|
|
for i := range deleteItemActions {
|
|
actual = append(actual, deleteItemActions[i])
|
|
}
|
|
assert.Equal(t, expectedActions, actual)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSanitizeName(t *testing.T) {
|
|
tests := []struct {
|
|
name, pluginName, expectedName string
|
|
}{
|
|
{
|
|
name: "Legacy, non-namespaced plugin",
|
|
pluginName: "aws",
|
|
expectedName: "velero.io/aws",
|
|
},
|
|
{
|
|
name: "A Velero plugin",
|
|
pluginName: "velero.io/aws",
|
|
expectedName: "velero.io/aws",
|
|
},
|
|
{
|
|
name: "A non-Velero plugin with a Velero namespace",
|
|
pluginName: "velero.io/plugin-for-velero",
|
|
expectedName: "velero.io/plugin-for-velero",
|
|
},
|
|
{
|
|
name: "A non-Velero plugin with a non-Velero namespace",
|
|
pluginName: "digitalocean.com/velero",
|
|
expectedName: "digitalocean.com/velero",
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
sanitizedName := sanitizeName(tc.pluginName)
|
|
assert.Equal(t, sanitizedName, tc.expectedName)
|
|
})
|
|
}
|
|
}
|