Merge pull request #5569 from sseago/riav2-impl

Restore Item Action v2 API implementation
pull/5784/head
Shubham Pampattiwar 2023-01-18 10:09:27 -05:00 committed by GitHub
commit cf2b482c97
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 2649 additions and 78 deletions

View File

@ -0,0 +1 @@
RestoreItemAction v2 API implementation

View File

@ -474,11 +474,11 @@ func (c *restoreController) runValidatedRestore(restore *api.Restore, info backu
pluginManager := c.newPluginManager(restoreLog)
defer pluginManager.CleanupClients()
actions, err := pluginManager.GetRestoreItemActions()
actions, err := pluginManager.GetRestoreItemActionsV2()
if err != nil {
return errors.Wrap(err, "error getting restore item actions")
}
actionsResolver := framework.NewRestoreItemActionResolver(actions)
actionsResolver := framework.NewRestoreItemActionResolverV2(actions)
itemSnapshotters, err := pluginManager.GetItemSnapshotters()
if err != nil {

View File

@ -47,7 +47,7 @@ import (
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
pluginmocks "github.com/vmware-tanzu/velero/pkg/plugin/mocks"
isv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/item_snapshotter/v1"
riav1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/restoreitemaction/v1"
riav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/restoreitemaction/v2"
pkgrestore "github.com/vmware-tanzu/velero/pkg/restore"
velerotest "github.com/vmware-tanzu/velero/pkg/test"
"github.com/vmware-tanzu/velero/pkg/util/logging"
@ -556,7 +556,7 @@ func TestProcessQueueItem(t *testing.T) {
}
if test.restore != nil {
pluginManager.On("GetRestoreItemActions").Return(nil, nil)
pluginManager.On("GetRestoreItemActionsV2").Return(nil, nil)
pluginManager.On("GetItemSnapshotters").Return([]isv1.ItemSnapshotter{}, nil)
pluginManager.On("CleanupClients")
}
@ -861,7 +861,7 @@ type fakeRestorer struct {
func (r *fakeRestorer) Restore(
info pkgrestore.Request,
actions []riav1.RestoreItemAction,
actions []riav2.RestoreItemAction,
snapshotLocationLister listers.VolumeSnapshotLocationLister,
volumeSnapshotterGetter pkgrestore.VolumeSnapshotterGetter,
) (pkgrestore.Result, pkgrestore.Result) {
@ -873,7 +873,7 @@ func (r *fakeRestorer) Restore(
}
func (r *fakeRestorer) RestoreWithResolvers(req pkgrestore.Request,
resolver framework.RestoreItemActionResolver,
resolver framework.RestoreItemActionResolverV2,
itemSnapshotterResolver framework.ItemSnapshotterResolver,
snapshotLocationLister listers.VolumeSnapshotLocationLister,
volumeSnapshotterGetter pkgrestore.VolumeSnapshotterGetter,

View File

@ -28,6 +28,7 @@ import (
biav2cli "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/backupitemaction/v2"
"github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/process"
riav1cli "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/restoreitemaction/v1"
riav2cli "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/restoreitemaction/v2"
vsv1cli "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/volumesnapshotter/v1"
"github.com/vmware-tanzu/velero/pkg/plugin/framework/common"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
@ -35,6 +36,7 @@ import (
biav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v2"
isv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/item_snapshotter/v1"
riav1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/restoreitemaction/v1"
riav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/restoreitemaction/v2"
vsv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/volumesnapshotter/v1"
)
@ -64,6 +66,12 @@ type Manager interface {
// GetRestoreItemAction returns the restore item action plugin for name.
GetRestoreItemAction(name string) (riav1.RestoreItemAction, error)
// GetRestoreItemActionsV2 returns all v2 restore item action plugins.
GetRestoreItemActionsV2() ([]riav2.RestoreItemAction, error)
// GetRestoreItemActionV2 returns the restore item action plugin for name.
GetRestoreItemActionV2(name string) (riav2.RestoreItemAction, error)
// GetDeleteItemActions returns all delete item action plugins.
GetDeleteItemActions() ([]velero.DeleteItemAction, error)
@ -302,6 +310,44 @@ func (m *manager) GetRestoreItemAction(name string) (riav1.RestoreItemAction, er
return nil, fmt.Errorf("unable to get valid RestoreItemAction for %q", name)
}
// GetRestoreItemActionsV2 returns all v2 restore item actions as restartableRestoreItemActions.
func (m *manager) GetRestoreItemActionsV2() ([]riav2.RestoreItemAction, error) {
list := m.registry.List(common.PluginKindRestoreItemActionV2)
actions := make([]riav2.RestoreItemAction, 0, len(list))
for i := range list {
id := list[i]
r, err := m.GetRestoreItemActionV2(id.Name)
if err != nil {
return nil, err
}
actions = append(actions, r)
}
return actions, nil
}
// GetRestoreItemActionV2 returns a v2 restartableRestoreItemAction for name.
func (m *manager) GetRestoreItemActionV2(name string) (riav2.RestoreItemAction, error) {
name = sanitizeName(name)
for _, adaptedRestoreItemAction := range riav2cli.AdaptedRestoreItemActions() {
restartableProcess, err := m.getRestartableProcess(adaptedRestoreItemAction.Kind, name)
// Check if plugin was not found
if errors.As(err, &pluginNotFoundErrType) {
continue
}
if err != nil {
return nil, err
}
return adaptedRestoreItemAction.GetRestartable(name, restartableProcess), nil
}
return nil, fmt.Errorf("unable to get valid RestoreItemActionV2 for %q", name)
}
// GetDeleteItemActions returns all delete item actions as restartableDeleteItemActions.
func (m *manager) GetDeleteItemActions() ([]velero.DeleteItemAction, error) {
list := m.registry.List(common.PluginKindDeleteItemAction)

View File

@ -31,6 +31,7 @@ import (
biav2cli "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/backupitemaction/v2"
"github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/process"
riav1cli "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/restoreitemaction/v1"
riav2cli "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/restoreitemaction/v2"
vsv1cli "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/volumesnapshotter/v1"
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
"github.com/vmware-tanzu/velero/pkg/plugin/framework/common"
@ -238,6 +239,23 @@ func TestGetRestoreItemAction(t *testing.T) {
)
}
func TestGetRestoreItemActionV2(t *testing.T) {
getPluginTest(t,
common.PluginKindRestoreItemActionV2,
"velero.io/pod",
func(m Manager, name string) (interface{}, error) {
return m.GetRestoreItemActionV2(name)
},
func(name string, sharedPluginProcess process.RestartableProcess) interface{} {
return &riav2cli.RestartableRestoreItemAction{
Key: process.KindAndName{Kind: common.PluginKindRestoreItemActionV2, Name: name},
SharedPluginProcess: sharedPluginProcess,
}
},
false,
)
}
func getPluginTest(
t *testing.T,
kind common.PluginKind,
@ -565,6 +583,98 @@ func TestGetRestoreItemActions(t *testing.T) {
}
}
func TestGetRestoreItemActionsV2(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.PluginKindRestoreItemActionV2
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 := &restartabletest.MockRestartableProcess{}
defer restartableProcess.AssertExpectations(t)
expected := &riav2cli.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.GetRestoreItemActionsV2()
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,

View File

@ -29,6 +29,7 @@ import (
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
biav2 "github.com/vmware-tanzu/velero/pkg/plugin/framework/backupitemaction/v2"
"github.com/vmware-tanzu/velero/pkg/plugin/framework/common"
riav2 "github.com/vmware-tanzu/velero/pkg/plugin/framework/restoreitemaction/v2"
)
// clientBuilder builds go-plugin Clients.
@ -69,14 +70,15 @@ func (b *clientBuilder) clientConfig() *hcplugin.ClientConfig {
HandshakeConfig: framework.Handshake(),
AllowedProtocols: []hcplugin.Protocol{hcplugin.ProtocolGRPC},
Plugins: map[string]hcplugin.Plugin{
string(common.PluginKindBackupItemAction): framework.NewBackupItemActionPlugin(common.ClientLogger(b.clientLogger)),
string(common.PluginKindBackupItemActionV2): biav2.NewBackupItemActionPlugin(common.ClientLogger(b.clientLogger)),
string(common.PluginKindVolumeSnapshotter): framework.NewVolumeSnapshotterPlugin(common.ClientLogger(b.clientLogger)),
string(common.PluginKindObjectStore): framework.NewObjectStorePlugin(common.ClientLogger(b.clientLogger)),
string(common.PluginKindPluginLister): &framework.PluginListerPlugin{},
string(common.PluginKindRestoreItemAction): framework.NewRestoreItemActionPlugin(common.ClientLogger(b.clientLogger)),
string(common.PluginKindDeleteItemAction): framework.NewDeleteItemActionPlugin(common.ClientLogger(b.clientLogger)),
string(common.PluginKindItemSnapshotter): framework.NewItemSnapshotterPlugin(common.ClientLogger(b.clientLogger)),
string(common.PluginKindBackupItemAction): framework.NewBackupItemActionPlugin(common.ClientLogger(b.clientLogger)),
string(common.PluginKindBackupItemActionV2): biav2.NewBackupItemActionPlugin(common.ClientLogger(b.clientLogger)),
string(common.PluginKindVolumeSnapshotter): framework.NewVolumeSnapshotterPlugin(common.ClientLogger(b.clientLogger)),
string(common.PluginKindObjectStore): framework.NewObjectStorePlugin(common.ClientLogger(b.clientLogger)),
string(common.PluginKindPluginLister): &framework.PluginListerPlugin{},
string(common.PluginKindRestoreItemAction): framework.NewRestoreItemActionPlugin(common.ClientLogger(b.clientLogger)),
string(common.PluginKindRestoreItemActionV2): riav2.NewRestoreItemActionPlugin(common.ClientLogger(b.clientLogger)),
string(common.PluginKindDeleteItemAction): framework.NewDeleteItemActionPlugin(common.ClientLogger(b.clientLogger)),
string(common.PluginKindItemSnapshotter): framework.NewItemSnapshotterPlugin(common.ClientLogger(b.clientLogger)),
},
Logger: b.pluginLogger,
Cmd: exec.Command(b.commandName, b.commandArgs...), //nolint

View File

@ -29,6 +29,7 @@ import (
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
biav2 "github.com/vmware-tanzu/velero/pkg/plugin/framework/backupitemaction/v2"
"github.com/vmware-tanzu/velero/pkg/plugin/framework/common"
riav2 "github.com/vmware-tanzu/velero/pkg/plugin/framework/restoreitemaction/v2"
"github.com/vmware-tanzu/velero/pkg/test"
)
@ -62,14 +63,15 @@ func TestClientConfig(t *testing.T) {
HandshakeConfig: framework.Handshake(),
AllowedProtocols: []hcplugin.Protocol{hcplugin.ProtocolGRPC},
Plugins: map[string]hcplugin.Plugin{
string(common.PluginKindBackupItemAction): framework.NewBackupItemActionPlugin(common.ClientLogger(logger)),
string(common.PluginKindBackupItemActionV2): biav2.NewBackupItemActionPlugin(common.ClientLogger(logger)),
string(common.PluginKindVolumeSnapshotter): framework.NewVolumeSnapshotterPlugin(common.ClientLogger(logger)),
string(common.PluginKindObjectStore): framework.NewObjectStorePlugin(common.ClientLogger(logger)),
string(common.PluginKindPluginLister): &framework.PluginListerPlugin{},
string(common.PluginKindRestoreItemAction): framework.NewRestoreItemActionPlugin(common.ClientLogger(logger)),
string(common.PluginKindDeleteItemAction): framework.NewDeleteItemActionPlugin(common.ClientLogger(logger)),
string(common.PluginKindItemSnapshotter): framework.NewItemSnapshotterPlugin(common.ClientLogger(logger)),
string(common.PluginKindBackupItemAction): framework.NewBackupItemActionPlugin(common.ClientLogger(logger)),
string(common.PluginKindBackupItemActionV2): biav2.NewBackupItemActionPlugin(common.ClientLogger(logger)),
string(common.PluginKindVolumeSnapshotter): framework.NewVolumeSnapshotterPlugin(common.ClientLogger(logger)),
string(common.PluginKindObjectStore): framework.NewObjectStorePlugin(common.ClientLogger(logger)),
string(common.PluginKindPluginLister): &framework.PluginListerPlugin{},
string(common.PluginKindRestoreItemAction): framework.NewRestoreItemActionPlugin(common.ClientLogger(logger)),
string(common.PluginKindRestoreItemActionV2): riav2.NewRestoreItemActionPlugin(common.ClientLogger(logger)),
string(common.PluginKindDeleteItemAction): framework.NewDeleteItemActionPlugin(common.ClientLogger(logger)),
string(common.PluginKindItemSnapshotter): framework.NewItemSnapshotterPlugin(common.ClientLogger(logger)),
},
Logger: cb.pluginLogger,
Cmd: exec.Command(cb.commandName, cb.commandArgs...),

View File

@ -0,0 +1,185 @@
/*
Copyright 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 v2
import (
"github.com/pkg/errors"
api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/process"
riav1cli "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/restoreitemaction/v1"
"github.com/vmware-tanzu/velero/pkg/plugin/framework/common"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
riav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/restoreitemaction/v2"
)
// AdaptedRestoreItemAction is a v1 RestoreItemAction adapted to implement the v2 API
type AdaptedRestoreItemAction struct {
Kind common.PluginKind
// Get returns a restartable RestoreItemAction for the given name and process, wrapping if necessary
GetRestartable func(name string, restartableProcess process.RestartableProcess) riav2.RestoreItemAction
}
func AdaptedRestoreItemActions() []AdaptedRestoreItemAction {
return []AdaptedRestoreItemAction{
{
Kind: common.PluginKindRestoreItemActionV2,
GetRestartable: func(name string, restartableProcess process.RestartableProcess) riav2.RestoreItemAction {
return NewRestartableRestoreItemAction(name, restartableProcess)
},
},
{
Kind: common.PluginKindRestoreItemAction,
GetRestartable: func(name string, restartableProcess process.RestartableProcess) riav2.RestoreItemAction {
return NewAdaptedV1RestartableRestoreItemAction(riav1cli.NewRestartableRestoreItemAction(name, restartableProcess))
},
},
}
}
// RestartableRestoreItemAction is a restore item action for a given implementation (such as "pod"). It is associated with
// a restartableProcess, which may be shared and used to run multiple plugins. At the beginning of each method
// call, the RestartableRestoreItemAction asks its restartableProcess to restart itself if needed (e.g. if the
// process terminated for any reason), then it proceeds with the actual call.
type RestartableRestoreItemAction struct {
Key process.KindAndName
SharedPluginProcess process.RestartableProcess
config map[string]string
}
// NewRestartableRestoreItemAction returns a new RestartableRestoreItemAction.
func NewRestartableRestoreItemAction(name string, sharedPluginProcess process.RestartableProcess) *RestartableRestoreItemAction {
r := &RestartableRestoreItemAction{
Key: process.KindAndName{Kind: common.PluginKindRestoreItemActionV2, Name: name},
SharedPluginProcess: sharedPluginProcess,
}
return r
}
// getRestoreItemAction returns the restore item action for this RestartableRestoreItemAction. It does *not* restart the
// plugin process.
func (r *RestartableRestoreItemAction) getRestoreItemAction() (riav2.RestoreItemAction, error) {
plugin, err := r.SharedPluginProcess.GetByKindAndName(r.Key)
if err != nil {
return nil, err
}
restoreItemAction, ok := plugin.(riav2.RestoreItemAction)
if !ok {
return nil, errors.Errorf("%T is not a RestoreItemActionV2!", plugin)
}
return restoreItemAction, nil
}
// getDelegate restarts the plugin process (if needed) and returns the restore item action for this RestartableRestoreItemAction.
func (r *RestartableRestoreItemAction) getDelegate() (riav2.RestoreItemAction, error) {
if err := r.SharedPluginProcess.ResetIfNeeded(); err != nil {
return nil, err
}
return r.getRestoreItemAction()
}
// AppliesTo restarts the plugin's process if needed, then delegates the call.
func (r RestartableRestoreItemAction) AppliesTo() (velero.ResourceSelector, error) {
delegate, err := r.getDelegate()
if err != nil {
return velero.ResourceSelector{}, err
}
return delegate.AppliesTo()
}
// Execute restarts the plugin's process if needed, then delegates the call.
func (r *RestartableRestoreItemAction) Execute(input *velero.RestoreItemActionExecuteInput) (*velero.RestoreItemActionExecuteOutput, error) {
delegate, err := r.getDelegate()
if err != nil {
return nil, err
}
return delegate.Execute(input)
}
// Progress restarts the plugin's process if needed, then delegates the call.
func (r *RestartableRestoreItemAction) Progress(operationID string, restore *api.Restore) (velero.OperationProgress, error) {
delegate, err := r.getDelegate()
if err != nil {
return velero.OperationProgress{}, err
}
return delegate.Progress(operationID, restore)
}
// Cancel restarts the plugin's process if needed, then delegates the call.
func (r *RestartableRestoreItemAction) Cancel(operationID string, restore *api.Restore) error {
delegate, err := r.getDelegate()
if err != nil {
return err
}
return delegate.Cancel(operationID, restore)
}
// AreAdditionalItemsReady restarts the plugin's process if needed, then delegates the call.
func (r *RestartableRestoreItemAction) AreAdditionalItemsReady(additionalItems []velero.ResourceIdentifier, restore *api.Restore) (bool, error) {
delegate, err := r.getDelegate()
if err != nil {
return false, err
}
return delegate.AreAdditionalItemsReady(additionalItems, restore)
}
type AdaptedV1RestartableRestoreItemAction struct {
V1Restartable *riav1cli.RestartableRestoreItemAction
}
// NewAdaptedV1RestartableRestoreItemAction returns a new v1 RestartableRestoreItemAction adapted to v2
func NewAdaptedV1RestartableRestoreItemAction(v1Restartable *riav1cli.RestartableRestoreItemAction) *AdaptedV1RestartableRestoreItemAction {
r := &AdaptedV1RestartableRestoreItemAction{
V1Restartable: v1Restartable,
}
return r
}
// AppliesTo delegates to the v1 AppliesTo call.
func (r *AdaptedV1RestartableRestoreItemAction) AppliesTo() (velero.ResourceSelector, error) {
return r.V1Restartable.AppliesTo()
}
// Execute delegates to the v1 Execute call, returning an empty operationID.
func (r *AdaptedV1RestartableRestoreItemAction) Execute(input *velero.RestoreItemActionExecuteInput) (*velero.RestoreItemActionExecuteOutput, error) {
return r.V1Restartable.Execute(input)
}
// Progress returns with an error since v1 plugins will never return an operationID, which means that
// any operationID passed in here will be invalid.
func (r *AdaptedV1RestartableRestoreItemAction) Progress(operationID string, restore *api.Restore) (velero.OperationProgress, error) {
return velero.OperationProgress{}, riav2.AsyncOperationsNotSupportedError()
}
// Cancel just returns without error since v1 plugins don't implement it.
func (r *AdaptedV1RestartableRestoreItemAction) Cancel(operationID string, restore *api.Restore) error {
return nil
}
// AreAdditionalItemsReady just returns true since v1 plugins don't wait for items.
func (r *AdaptedV1RestartableRestoreItemAction) AreAdditionalItemsReady(additionalItems []velero.ResourceIdentifier, restore *api.Restore) (bool, error) {
return true, nil
}

View File

@ -0,0 +1,170 @@
/*
Copyright 2018, 2019 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 v2
import (
"testing"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"github.com/vmware-tanzu/velero/internal/restartabletest"
v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/process"
"github.com/vmware-tanzu/velero/pkg/plugin/framework/common"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
mocks "github.com/vmware-tanzu/velero/pkg/plugin/velero/mocks/restoreitemaction/v2"
)
func TestRestartableGetRestoreItemAction(t *testing.T) {
tests := []struct {
name string
plugin interface{}
getError error
expectedError string
}{
{
name: "error getting by kind and name",
getError: errors.Errorf("get error"),
expectedError: "get error",
},
{
name: "wrong type",
plugin: 3,
expectedError: "int is not a RestoreItemActionV2!",
},
{
name: "happy path",
plugin: new(mocks.RestoreItemAction),
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
p := new(restartabletest.MockRestartableProcess)
defer p.AssertExpectations(t)
name := "pod"
key := process.KindAndName{Kind: common.PluginKindRestoreItemActionV2, Name: name}
p.On("GetByKindAndName", key).Return(tc.plugin, tc.getError)
r := NewRestartableRestoreItemAction(name, p)
a, err := r.getRestoreItemAction()
if tc.expectedError != "" {
assert.EqualError(t, err, tc.expectedError)
return
}
require.NoError(t, err)
assert.Equal(t, tc.plugin, a)
})
}
}
func TestRestartableRestoreItemActionGetDelegate(t *testing.T) {
p := new(restartabletest.MockRestartableProcess)
defer p.AssertExpectations(t)
// Reset error
p.On("ResetIfNeeded").Return(errors.Errorf("reset error")).Once()
name := "pod"
r := NewRestartableRestoreItemAction(name, p)
a, err := r.getDelegate()
assert.Nil(t, a)
assert.EqualError(t, err, "reset error")
// Happy path
p.On("ResetIfNeeded").Return(nil)
expected := new(mocks.RestoreItemAction)
key := process.KindAndName{Kind: common.PluginKindRestoreItemActionV2, Name: name}
p.On("GetByKindAndName", key).Return(expected, nil)
a, err = r.getDelegate()
assert.NoError(t, err)
assert.Equal(t, expected, a)
}
func TestRestartableRestoreItemActionDelegatedFunctions(t *testing.T) {
pv := &unstructured.Unstructured{
Object: map[string]interface{}{
"color": "blue",
},
}
input := &velero.RestoreItemActionExecuteInput{
Item: pv,
ItemFromBackup: pv,
Restore: new(v1.Restore),
}
output := &velero.RestoreItemActionExecuteOutput{
UpdatedItem: &unstructured.Unstructured{
Object: map[string]interface{}{
"color": "green",
},
},
}
r := new(v1.Restore)
oid := "operation1"
additionalItems := []velero.ResourceIdentifier{}
restartabletest.RunRestartableDelegateTests(
t,
common.PluginKindRestoreItemActionV2,
func(key process.KindAndName, p process.RestartableProcess) interface{} {
return &RestartableRestoreItemAction{
Key: key,
SharedPluginProcess: p,
}
},
func() restartabletest.Mockable {
return new(mocks.RestoreItemAction)
},
restartabletest.RestartableDelegateTest{
Function: "AppliesTo",
Inputs: []interface{}{},
ExpectedErrorOutputs: []interface{}{velero.ResourceSelector{}, errors.Errorf("reset error")},
ExpectedDelegateOutputs: []interface{}{velero.ResourceSelector{IncludedNamespaces: []string{"a"}}, errors.Errorf("delegate error")},
},
restartabletest.RestartableDelegateTest{
Function: "Execute",
Inputs: []interface{}{input},
ExpectedErrorOutputs: []interface{}{nil, errors.Errorf("reset error")},
ExpectedDelegateOutputs: []interface{}{output, errors.Errorf("delegate error")},
},
restartabletest.RestartableDelegateTest{
Function: "Progress",
Inputs: []interface{}{oid, r},
ExpectedErrorOutputs: []interface{}{velero.OperationProgress{}, errors.Errorf("reset error")},
ExpectedDelegateOutputs: []interface{}{velero.OperationProgress{}, errors.Errorf("delegate error")},
},
restartabletest.RestartableDelegateTest{
Function: "Cancel",
Inputs: []interface{}{oid, r},
ExpectedErrorOutputs: []interface{}{errors.Errorf("reset error")},
ExpectedDelegateOutputs: []interface{}{errors.Errorf("delegate error")},
},
restartabletest.RestartableDelegateTest{
Function: "AreAdditionalItemsReady",
Inputs: []interface{}{additionalItems, r},
ExpectedErrorOutputs: []interface{}{false, errors.Errorf("reset error")},
ExpectedDelegateOutputs: []interface{}{true, errors.Errorf("delegate error")},
},
)
}

View File

@ -29,6 +29,7 @@ import (
biav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v2"
isv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/item_snapshotter/v1"
riav1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/restoreitemaction/v1"
riav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/restoreitemaction/v2"
"github.com/vmware-tanzu/velero/pkg/util/collections"
)
@ -128,6 +129,12 @@ func NewRestoreItemActionResolver(actions []riav1.RestoreItemAction) RestoreItem
}
}
func NewRestoreItemActionResolverV2(actions []riav2.RestoreItemAction) RestoreItemActionResolverV2 {
return RestoreItemActionResolverV2{
actions: actions,
}
}
func NewDeleteItemActionResolver(actions []velero.DeleteItemAction) DeleteItemActionResolver {
return DeleteItemActionResolver{
actions: actions,
@ -199,6 +206,11 @@ type RestoreItemResolvedAction struct {
resolvedAction
}
type RestoreItemResolvedActionV2 struct {
riav2.RestoreItemAction
resolvedAction
}
type RestoreItemActionResolver struct {
actions []riav1.RestoreItemAction
}
@ -223,6 +235,30 @@ func (recv RestoreItemActionResolver) ResolveActions(helper discovery.Helper, lo
return resolved, nil
}
type RestoreItemActionResolverV2 struct {
actions []riav2.RestoreItemAction
}
func (recv RestoreItemActionResolverV2) ResolveActions(helper discovery.Helper, log logrus.FieldLogger) ([]RestoreItemResolvedActionV2, error) {
var resolved []RestoreItemResolvedActionV2
for _, action := range recv.actions {
resources, namespaces, selector, err := resolveAction(helper, action)
if err != nil {
return nil, err
}
res := RestoreItemResolvedActionV2{
RestoreItemAction: action,
resolvedAction: resolvedAction{
ResourceIncludesExcludes: resources,
NamespaceIncludesExcludes: namespaces,
Selector: selector,
},
}
resolved = append(resolved, res)
}
return resolved, nil
}
type DeleteItemResolvedAction struct {
velero.DeleteItemAction
resolvedAction

View File

@ -41,6 +41,9 @@ const (
// PluginKindRestoreItemAction represents a restore item action plugin.
PluginKindRestoreItemAction PluginKind = "RestoreItemAction"
// PluginKindRestoreItemAction represents a v2 restore item action plugin.
PluginKindRestoreItemActionV2 PluginKind = "RestoreItemActionV2"
// PluginKindDeleteItemAction represents a delete item action plugin.
PluginKindDeleteItemAction PluginKind = "DeleteItemAction"
@ -55,7 +58,8 @@ const (
// The older (adaptable) version is the key, and the value is the full list of newer
// plugin kinds that are capable of adapting it.
var PluginKindsAdaptableTo = map[PluginKind][]PluginKind{
PluginKindBackupItemAction: {PluginKindBackupItemActionV2},
PluginKindBackupItemAction: {PluginKindBackupItemActionV2},
PluginKindRestoreItemAction: {PluginKindRestoreItemActionV2},
}
// AllPluginKinds contains all the valid plugin kinds that Velero supports, excluding PluginLister because that is not a
@ -67,6 +71,7 @@ func AllPluginKinds() map[string]PluginKind {
allPluginKinds[PluginKindBackupItemAction.String()] = PluginKindBackupItemAction
allPluginKinds[PluginKindBackupItemActionV2.String()] = PluginKindBackupItemActionV2
allPluginKinds[PluginKindRestoreItemAction.String()] = PluginKindRestoreItemAction
allPluginKinds[PluginKindRestoreItemActionV2.String()] = PluginKindRestoreItemActionV2
allPluginKinds[PluginKindDeleteItemAction.String()] = PluginKindDeleteItemAction
allPluginKinds[PluginKindItemSnapshotter.String()] = PluginKindItemSnapshotter
return allPluginKinds

View File

@ -0,0 +1,45 @@
/*
Copyright 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 v2
import (
plugin "github.com/hashicorp/go-plugin"
"golang.org/x/net/context"
"google.golang.org/grpc"
"github.com/vmware-tanzu/velero/pkg/plugin/framework/common"
protoriav2 "github.com/vmware-tanzu/velero/pkg/plugin/generated/restoreitemaction/v2"
)
// RestoreItemActionPlugin is an implementation of go-plugin's Plugin
// interface with support for gRPC for the restore/ItemAction
// interface.
type RestoreItemActionPlugin struct {
plugin.NetRPCUnsupportedPlugin
*common.PluginBase
}
// GRPCClient returns a RestoreItemAction gRPC client.
func (p *RestoreItemActionPlugin) GRPCClient(_ context.Context, _ *plugin.GRPCBroker, clientConn *grpc.ClientConn) (interface{}, error) {
return common.NewClientDispenser(p.ClientLogger, clientConn, newRestoreItemActionGRPCClient), nil
}
// GRPCServer registers a RestoreItemAction gRPC server.
func (p *RestoreItemActionPlugin) GRPCServer(_ *plugin.GRPCBroker, server *grpc.Server) error {
protoriav2.RegisterRestoreItemActionServer(server, &RestoreItemActionGRPCServer{mux: p.ServerMux})
return nil
}

View File

@ -0,0 +1,201 @@
/*
Copyright 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 v2
import (
"encoding/json"
"github.com/pkg/errors"
"golang.org/x/net/context"
"google.golang.org/grpc"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/plugin/framework/common"
protoriav2 "github.com/vmware-tanzu/velero/pkg/plugin/generated/restoreitemaction/v2"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
riav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/restoreitemaction/v2"
)
var _ riav2.RestoreItemAction = &RestoreItemActionGRPCClient{}
// NewRestoreItemActionPlugin constructs a RestoreItemActionPlugin.
func NewRestoreItemActionPlugin(options ...common.PluginOption) *RestoreItemActionPlugin {
return &RestoreItemActionPlugin{
PluginBase: common.NewPluginBase(options...),
}
}
// RestoreItemActionGRPCClient implements the backup/ItemAction interface and uses a
// gRPC client to make calls to the plugin server.
type RestoreItemActionGRPCClient struct {
*common.ClientBase
grpcClient protoriav2.RestoreItemActionClient
}
func newRestoreItemActionGRPCClient(base *common.ClientBase, clientConn *grpc.ClientConn) interface{} {
return &RestoreItemActionGRPCClient{
ClientBase: base,
grpcClient: protoriav2.NewRestoreItemActionClient(clientConn),
}
}
func (c *RestoreItemActionGRPCClient) AppliesTo() (velero.ResourceSelector, error) {
res, err := c.grpcClient.AppliesTo(context.Background(), &protoriav2.RestoreItemActionAppliesToRequest{Plugin: c.Plugin})
if err != nil {
return velero.ResourceSelector{}, common.FromGRPCError(err)
}
if res.ResourceSelector == nil {
return velero.ResourceSelector{}, nil
}
return velero.ResourceSelector{
IncludedNamespaces: res.ResourceSelector.IncludedNamespaces,
ExcludedNamespaces: res.ResourceSelector.ExcludedNamespaces,
IncludedResources: res.ResourceSelector.IncludedResources,
ExcludedResources: res.ResourceSelector.ExcludedResources,
LabelSelector: res.ResourceSelector.Selector,
}, nil
}
func (c *RestoreItemActionGRPCClient) Execute(input *velero.RestoreItemActionExecuteInput) (*velero.RestoreItemActionExecuteOutput, error) {
itemJSON, err := json.Marshal(input.Item.UnstructuredContent())
if err != nil {
return nil, errors.WithStack(err)
}
itemFromBackupJSON, err := json.Marshal(input.ItemFromBackup.UnstructuredContent())
if err != nil {
return nil, errors.WithStack(err)
}
restoreJSON, err := json.Marshal(input.Restore)
if err != nil {
return nil, errors.WithStack(err)
}
req := &protoriav2.RestoreItemActionExecuteRequest{
Plugin: c.Plugin,
Item: itemJSON,
ItemFromBackup: itemFromBackupJSON,
Restore: restoreJSON,
}
res, err := c.grpcClient.Execute(context.Background(), req)
if err != nil {
return nil, common.FromGRPCError(err)
}
var updatedItem unstructured.Unstructured
if err := json.Unmarshal(res.Item, &updatedItem); err != nil {
return nil, errors.WithStack(err)
}
var additionalItems []velero.ResourceIdentifier
for _, itm := range res.AdditionalItems {
newItem := velero.ResourceIdentifier{
GroupResource: schema.GroupResource{
Group: itm.Group,
Resource: itm.Resource,
},
Namespace: itm.Namespace,
Name: itm.Name,
}
additionalItems = append(additionalItems, newItem)
}
return &velero.RestoreItemActionExecuteOutput{
UpdatedItem: &updatedItem,
AdditionalItems: additionalItems,
SkipRestore: res.SkipRestore,
OperationID: res.OperationID,
WaitForAdditionalItems: res.WaitForAdditionalItems,
AdditionalItemsReadyTimeout: res.AdditionalItemsReadyTimeout.AsDuration(),
}, nil
}
func (c *RestoreItemActionGRPCClient) Progress(operationID string, restore *api.Restore) (velero.OperationProgress, error) {
restoreJSON, err := json.Marshal(restore)
if err != nil {
return velero.OperationProgress{}, errors.WithStack(err)
}
req := &protoriav2.RestoreItemActionProgressRequest{
Plugin: c.Plugin,
OperationID: operationID,
Restore: restoreJSON,
}
res, err := c.grpcClient.Progress(context.Background(), req)
if err != nil {
return velero.OperationProgress{}, common.FromGRPCError(err)
}
return velero.OperationProgress{
Completed: res.Progress.Completed,
Err: res.Progress.Err,
NCompleted: res.Progress.NCompleted,
NTotal: res.Progress.NTotal,
OperationUnits: res.Progress.OperationUnits,
Description: res.Progress.Description,
Started: res.Progress.Started.AsTime(),
Updated: res.Progress.Updated.AsTime(),
}, nil
}
func (c *RestoreItemActionGRPCClient) Cancel(operationID string, restore *api.Restore) error {
restoreJSON, err := json.Marshal(restore)
if err != nil {
return errors.WithStack(err)
}
req := &protoriav2.RestoreItemActionCancelRequest{
Plugin: c.Plugin,
OperationID: operationID,
Restore: restoreJSON,
}
_, err = c.grpcClient.Cancel(context.Background(), req)
if err != nil {
return common.FromGRPCError(err)
}
return nil
}
func (c *RestoreItemActionGRPCClient) AreAdditionalItemsReady(additionalItems []velero.ResourceIdentifier, restore *api.Restore) (bool, error) {
restoreJSON, err := json.Marshal(restore)
if err != nil {
return false, errors.WithStack(err)
}
req := &protoriav2.RestoreItemActionItemsReadyRequest{
Plugin: c.Plugin,
Restore: restoreJSON,
}
for _, item := range additionalItems {
req.AdditionalItems = append(req.AdditionalItems, restoreResourceIdentifierToProto(item))
}
res, err := c.grpcClient.AreAdditionalItemsReady(context.Background(), req)
if err != nil {
return false, common.FromGRPCError(err)
}
return res.Ready, nil
}

View File

@ -0,0 +1,265 @@
/*
Copyright 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 v2
import (
"encoding/json"
"github.com/pkg/errors"
"golang.org/x/net/context"
"google.golang.org/protobuf/types/known/durationpb"
"google.golang.org/protobuf/types/known/emptypb"
"google.golang.org/protobuf/types/known/timestamppb"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/plugin/framework/common"
proto "github.com/vmware-tanzu/velero/pkg/plugin/generated"
protoriav2 "github.com/vmware-tanzu/velero/pkg/plugin/generated/restoreitemaction/v2"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
riav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/restoreitemaction/v2"
)
// RestoreItemActionGRPCServer implements the proto-generated RestoreItemActionServer interface, and accepts
// gRPC calls and forwards them to an implementation of the pluggable interface.
type RestoreItemActionGRPCServer struct {
mux *common.ServerMux
}
func (s *RestoreItemActionGRPCServer) getImpl(name string) (riav2.RestoreItemAction, error) {
impl, err := s.mux.GetHandler(name)
if err != nil {
return nil, err
}
itemAction, ok := impl.(riav2.RestoreItemAction)
if !ok {
return nil, errors.Errorf("%T is not a restore item action (v2)", impl)
}
return itemAction, nil
}
func (s *RestoreItemActionGRPCServer) AppliesTo(ctx context.Context, req *protoriav2.RestoreItemActionAppliesToRequest) (response *protoriav2.RestoreItemActionAppliesToResponse, err error) {
defer func() {
if recoveredErr := common.HandlePanic(recover()); recoveredErr != nil {
err = recoveredErr
}
}()
impl, err := s.getImpl(req.Plugin)
if err != nil {
return nil, common.NewGRPCError(err)
}
resourceSelector, err := impl.AppliesTo()
if err != nil {
return nil, common.NewGRPCError(err)
}
return &protoriav2.RestoreItemActionAppliesToResponse{
ResourceSelector: &proto.ResourceSelector{
IncludedNamespaces: resourceSelector.IncludedNamespaces,
ExcludedNamespaces: resourceSelector.ExcludedNamespaces,
IncludedResources: resourceSelector.IncludedResources,
ExcludedResources: resourceSelector.ExcludedResources,
Selector: resourceSelector.LabelSelector,
},
}, nil
}
func (s *RestoreItemActionGRPCServer) Execute(ctx context.Context, req *protoriav2.RestoreItemActionExecuteRequest) (response *protoriav2.RestoreItemActionExecuteResponse, err error) {
defer func() {
if recoveredErr := common.HandlePanic(recover()); recoveredErr != nil {
err = recoveredErr
}
}()
impl, err := s.getImpl(req.Plugin)
if err != nil {
return nil, common.NewGRPCError(err)
}
var (
item unstructured.Unstructured
itemFromBackup unstructured.Unstructured
restoreObj api.Restore
)
if err := json.Unmarshal(req.Item, &item); err != nil {
return nil, common.NewGRPCError(errors.WithStack(err))
}
if err := json.Unmarshal(req.ItemFromBackup, &itemFromBackup); err != nil {
return nil, common.NewGRPCError(errors.WithStack(err))
}
if err := json.Unmarshal(req.Restore, &restoreObj); err != nil {
return nil, common.NewGRPCError(errors.WithStack(err))
}
executeOutput, err := impl.Execute(&velero.RestoreItemActionExecuteInput{
Item: &item,
ItemFromBackup: &itemFromBackup,
Restore: &restoreObj,
})
if err != nil {
return nil, common.NewGRPCError(err)
}
// If the plugin implementation returned a nil updateItem (meaning no modifications), reset updatedItem to the
// original item.
var updatedItemJSON []byte
if executeOutput.UpdatedItem == nil {
updatedItemJSON = req.Item
} else {
updatedItemJSON, err = json.Marshal(executeOutput.UpdatedItem.UnstructuredContent())
if err != nil {
return nil, common.NewGRPCError(errors.WithStack(err))
}
}
res := &protoriav2.RestoreItemActionExecuteResponse{
Item: updatedItemJSON,
SkipRestore: executeOutput.SkipRestore,
OperationID: executeOutput.OperationID,
WaitForAdditionalItems: executeOutput.WaitForAdditionalItems,
AdditionalItemsReadyTimeout: durationpb.New(executeOutput.AdditionalItemsReadyTimeout),
}
for _, item := range executeOutput.AdditionalItems {
res.AdditionalItems = append(res.AdditionalItems, restoreResourceIdentifierToProto(item))
}
return res, nil
}
func (s *RestoreItemActionGRPCServer) Progress(ctx context.Context, req *protoriav2.RestoreItemActionProgressRequest) (
response *protoriav2.RestoreItemActionProgressResponse, err error) {
defer func() {
if recoveredErr := common.HandlePanic(recover()); recoveredErr != nil {
err = recoveredErr
}
}()
impl, err := s.getImpl(req.Plugin)
if err != nil {
return nil, common.NewGRPCError(err)
}
var restore api.Restore
if err := json.Unmarshal(req.Restore, &restore); err != nil {
return nil, common.NewGRPCError(errors.WithStack(err))
}
progress, err := impl.Progress(req.OperationID, &restore)
if err != nil {
return nil, common.NewGRPCError(err)
}
res := &protoriav2.RestoreItemActionProgressResponse{
Progress: &proto.OperationProgress{
Completed: progress.Completed,
Err: progress.Err,
NCompleted: progress.NCompleted,
NTotal: progress.NTotal,
OperationUnits: progress.OperationUnits,
Description: progress.Description,
Started: timestamppb.New(progress.Started),
Updated: timestamppb.New(progress.Updated),
},
}
return res, nil
}
func (s *RestoreItemActionGRPCServer) Cancel(
ctx context.Context, req *protoriav2.RestoreItemActionCancelRequest) (
response *emptypb.Empty, err error) {
defer func() {
if recoveredErr := common.HandlePanic(recover()); recoveredErr != nil {
err = recoveredErr
}
}()
impl, err := s.getImpl(req.Plugin)
if err != nil {
return nil, common.NewGRPCError(err)
}
var restore api.Restore
if err := json.Unmarshal(req.Restore, &restore); err != nil {
return nil, common.NewGRPCError(errors.WithStack(err))
}
err = impl.Cancel(req.OperationID, &restore)
if err != nil {
return nil, common.NewGRPCError(err)
}
return &emptypb.Empty{}, nil
}
func (s *RestoreItemActionGRPCServer) AreAdditionalItemsReady(ctx context.Context, req *protoriav2.RestoreItemActionItemsReadyRequest) (
response *protoriav2.RestoreItemActionItemsReadyResponse, err error) {
defer func() {
if recoveredErr := common.HandlePanic(recover()); recoveredErr != nil {
err = recoveredErr
}
}()
impl, err := s.getImpl(req.Plugin)
if err != nil {
return nil, common.NewGRPCError(err)
}
var restore api.Restore
if err := json.Unmarshal(req.Restore, &restore); err != nil {
return nil, common.NewGRPCError(errors.WithStack(err))
}
var additionalItems []velero.ResourceIdentifier
for _, itm := range req.AdditionalItems {
newItem := velero.ResourceIdentifier{
GroupResource: schema.GroupResource{
Group: itm.Group,
Resource: itm.Resource,
},
Namespace: itm.Namespace,
Name: itm.Name,
}
additionalItems = append(additionalItems, newItem)
}
ready, err := impl.AreAdditionalItemsReady(additionalItems, &restore)
if err != nil {
return nil, common.NewGRPCError(err)
}
res := &protoriav2.RestoreItemActionItemsReadyResponse{
Ready: ready,
}
return res, nil
}
func restoreResourceIdentifierToProto(id velero.ResourceIdentifier) *proto.ResourceIdentifier {
return &proto.ResourceIdentifier{
Group: id.Group,
Resource: id.Resource,
Namespace: id.Namespace,
Name: id.Name,
}
}

View File

@ -27,6 +27,7 @@ import (
biav2 "github.com/vmware-tanzu/velero/pkg/plugin/framework/backupitemaction/v2"
"github.com/vmware-tanzu/velero/pkg/plugin/framework/common"
riav2 "github.com/vmware-tanzu/velero/pkg/plugin/framework/restoreitemaction/v2"
"github.com/vmware-tanzu/velero/pkg/util/logging"
)
@ -75,6 +76,13 @@ type Server interface {
// RegisterRestoreItemActions registers multiple restore item actions.
RegisterRestoreItemActions(map[string]common.HandlerInitializer) Server
// RegisterRestoreItemActionV2 registers a v2 restore item action. Accepted format
// for the plugin name is <DNS subdomain>/<non-empty name>.
RegisterRestoreItemActionV2(pluginName string, initializer common.HandlerInitializer) Server
// RegisterRestoreItemActionsV2 registers multiple v2 restore item actions.
RegisterRestoreItemActionsV2(map[string]common.HandlerInitializer) Server
// RegisterDeleteItemAction registers a delete item action. Accepted format
// for the plugin name is <DNS subdomain>/<non-empty name>.
RegisterDeleteItemAction(pluginName string, initializer common.HandlerInitializer) Server
@ -93,16 +101,17 @@ type Server interface {
// server implements Server.
type server struct {
log *logrus.Logger
logLevelFlag *logging.LevelFlag
flagSet *pflag.FlagSet
backupItemAction *BackupItemActionPlugin
backupItemActionV2 *biav2.BackupItemActionPlugin
volumeSnapshotter *VolumeSnapshotterPlugin
objectStore *ObjectStorePlugin
restoreItemAction *RestoreItemActionPlugin
deleteItemAction *DeleteItemActionPlugin
itemSnapshotter *ItemSnapshotterPlugin
log *logrus.Logger
logLevelFlag *logging.LevelFlag
flagSet *pflag.FlagSet
backupItemAction *BackupItemActionPlugin
backupItemActionV2 *biav2.BackupItemActionPlugin
volumeSnapshotter *VolumeSnapshotterPlugin
objectStore *ObjectStorePlugin
restoreItemAction *RestoreItemActionPlugin
restoreItemActionV2 *riav2.RestoreItemActionPlugin
deleteItemAction *DeleteItemActionPlugin
itemSnapshotter *ItemSnapshotterPlugin
}
// NewServer returns a new Server
@ -110,15 +119,16 @@ func NewServer() Server {
log := newLogger()
return &server{
log: log,
logLevelFlag: logging.LogLevelFlag(log.Level),
backupItemAction: NewBackupItemActionPlugin(common.ServerLogger(log)),
backupItemActionV2: biav2.NewBackupItemActionPlugin(common.ServerLogger(log)),
volumeSnapshotter: NewVolumeSnapshotterPlugin(common.ServerLogger(log)),
objectStore: NewObjectStorePlugin(common.ServerLogger(log)),
restoreItemAction: NewRestoreItemActionPlugin(common.ServerLogger(log)),
deleteItemAction: NewDeleteItemActionPlugin(common.ServerLogger(log)),
itemSnapshotter: NewItemSnapshotterPlugin(common.ServerLogger(log)),
log: log,
logLevelFlag: logging.LogLevelFlag(log.Level),
backupItemAction: NewBackupItemActionPlugin(common.ServerLogger(log)),
backupItemActionV2: biav2.NewBackupItemActionPlugin(common.ServerLogger(log)),
volumeSnapshotter: NewVolumeSnapshotterPlugin(common.ServerLogger(log)),
objectStore: NewObjectStorePlugin(common.ServerLogger(log)),
restoreItemAction: NewRestoreItemActionPlugin(common.ServerLogger(log)),
restoreItemActionV2: riav2.NewRestoreItemActionPlugin(common.ServerLogger(log)),
deleteItemAction: NewDeleteItemActionPlugin(common.ServerLogger(log)),
itemSnapshotter: NewItemSnapshotterPlugin(common.ServerLogger(log)),
}
}
@ -190,6 +200,18 @@ func (s *server) RegisterRestoreItemActions(m map[string]common.HandlerInitializ
return s
}
func (s *server) RegisterRestoreItemActionV2(name string, initializer common.HandlerInitializer) Server {
s.restoreItemActionV2.Register(name, initializer)
return s
}
func (s *server) RegisterRestoreItemActionsV2(m map[string]common.HandlerInitializer) Server {
for name := range m {
s.RegisterRestoreItemActionV2(name, m[name])
}
return s
}
func (s *server) RegisterDeleteItemAction(name string, initializer common.HandlerInitializer) Server {
s.deleteItemAction.Register(name, initializer)
return s
@ -242,6 +264,7 @@ func (s *server) Serve() {
pluginIdentifiers = append(pluginIdentifiers, getNames(command, common.PluginKindVolumeSnapshotter, s.volumeSnapshotter)...)
pluginIdentifiers = append(pluginIdentifiers, getNames(command, common.PluginKindObjectStore, s.objectStore)...)
pluginIdentifiers = append(pluginIdentifiers, getNames(command, common.PluginKindRestoreItemAction, s.restoreItemAction)...)
pluginIdentifiers = append(pluginIdentifiers, getNames(command, common.PluginKindRestoreItemActionV2, s.restoreItemActionV2)...)
pluginIdentifiers = append(pluginIdentifiers, getNames(command, common.PluginKindDeleteItemAction, s.deleteItemAction)...)
pluginIdentifiers = append(pluginIdentifiers, getNames(command, common.PluginKindItemSnapshotter, s.itemSnapshotter)...)
@ -250,14 +273,15 @@ func (s *server) Serve() {
plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: Handshake(),
Plugins: map[string]plugin.Plugin{
string(common.PluginKindBackupItemAction): s.backupItemAction,
string(common.PluginKindBackupItemActionV2): s.backupItemActionV2,
string(common.PluginKindVolumeSnapshotter): s.volumeSnapshotter,
string(common.PluginKindObjectStore): s.objectStore,
string(common.PluginKindPluginLister): NewPluginListerPlugin(pluginLister),
string(common.PluginKindRestoreItemAction): s.restoreItemAction,
string(common.PluginKindDeleteItemAction): s.deleteItemAction,
string(common.PluginKindItemSnapshotter): s.itemSnapshotter,
string(common.PluginKindBackupItemAction): s.backupItemAction,
string(common.PluginKindBackupItemActionV2): s.backupItemActionV2,
string(common.PluginKindVolumeSnapshotter): s.volumeSnapshotter,
string(common.PluginKindObjectStore): s.objectStore,
string(common.PluginKindPluginLister): NewPluginListerPlugin(pluginLister),
string(common.PluginKindRestoreItemAction): s.restoreItemAction,
string(common.PluginKindRestoreItemActionV2): s.restoreItemActionV2,
string(common.PluginKindDeleteItemAction): s.deleteItemAction,
string(common.PluginKindItemSnapshotter): s.itemSnapshotter,
},
GRPCServer: plugin.DefaultGRPCServer,
})

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,8 @@ import (
restoreitemactionv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/restoreitemaction/v1"
restoreitemactionv2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/restoreitemaction/v2"
v1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v1"
v2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v2"
@ -257,6 +259,29 @@ func (_m *Manager) GetRestoreItemAction(name string) (restoreitemactionv1.Restor
return r0, r1
}
// GetRestoreItemActionV2 provides a mock function with given fields: name
func (_m *Manager) GetRestoreItemActionV2(name string) (restoreitemactionv2.RestoreItemAction, error) {
ret := _m.Called(name)
var r0 restoreitemactionv2.RestoreItemAction
if rf, ok := ret.Get(0).(func(string) restoreitemactionv2.RestoreItemAction); ok {
r0 = rf(name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(restoreitemactionv2.RestoreItemAction)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetRestoreItemActions provides a mock function with given fields:
func (_m *Manager) GetRestoreItemActions() ([]restoreitemactionv1.RestoreItemAction, error) {
ret := _m.Called()
@ -280,6 +305,29 @@ func (_m *Manager) GetRestoreItemActions() ([]restoreitemactionv1.RestoreItemAct
return r0, r1
}
// GetRestoreItemActionsV2 provides a mock function with given fields:
func (_m *Manager) GetRestoreItemActionsV2() ([]restoreitemactionv2.RestoreItemAction, error) {
ret := _m.Called()
var r0 []restoreitemactionv2.RestoreItemAction
if rf, ok := ret.Get(0).(func() []restoreitemactionv2.RestoreItemAction); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]restoreitemactionv2.RestoreItemAction)
}
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetVolumeSnapshotter provides a mock function with given fields: name
func (_m *Manager) GetVolumeSnapshotter(name string) (volumesnapshotterv1.VolumeSnapshotter, error) {
ret := _m.Called(name)

View File

@ -0,0 +1,61 @@
syntax = "proto3";
package v2;
option go_package = "github.com/vmware-tanzu/velero/pkg/plugin/generated/restoreitemaction/v2";
import "Shared.proto";
import "google/protobuf/empty.proto";
import "google/protobuf/duration.proto";
message RestoreItemActionExecuteRequest {
string plugin = 1;
bytes item = 2;
bytes restore = 3;
bytes itemFromBackup = 4;
}
message RestoreItemActionExecuteResponse {
bytes item = 1;
repeated generated.ResourceIdentifier additionalItems = 2;
bool skipRestore = 3;
string operationID = 4;
bool waitForAdditionalItems = 5;
google.protobuf.Duration additionalItemsReadyTimeout = 6;
}
service RestoreItemAction {
rpc AppliesTo(RestoreItemActionAppliesToRequest) returns (RestoreItemActionAppliesToResponse);
rpc Execute(RestoreItemActionExecuteRequest) returns (RestoreItemActionExecuteResponse);
rpc Progress(RestoreItemActionProgressRequest) returns (RestoreItemActionProgressResponse);
rpc Cancel(RestoreItemActionCancelRequest) returns (google.protobuf.Empty);
rpc AreAdditionalItemsReady(RestoreItemActionItemsReadyRequest) returns (RestoreItemActionItemsReadyResponse);
}
message RestoreItemActionAppliesToRequest {
string plugin = 1;
}
message RestoreItemActionAppliesToResponse {
generated.ResourceSelector ResourceSelector = 1;
}
message RestoreItemActionProgressRequest {
string plugin = 1;
string operationID = 2;
bytes restore = 3;
}
message RestoreItemActionProgressResponse {
generated.OperationProgress progress = 1;
}
message RestoreItemActionCancelRequest {
string plugin = 1;
string operationID = 2;
bytes restore = 3;
}
message RestoreItemActionItemsReadyRequest {
string plugin = 1;
bytes restore = 2;
repeated generated.ResourceIdentifier additionalItems = 3;
}
message RestoreItemActionItemsReadyResponse {
bool ready = 1;
}

View File

@ -0,0 +1,130 @@
/*
Copyright 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.
*/
// Code generated by mockery v1.0.0. DO NOT EDIT.
package v2
import (
mock "github.com/stretchr/testify/mock"
v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
velero "github.com/vmware-tanzu/velero/pkg/plugin/velero"
)
// RestoreItemAction is an autogenerated mock type for the RestoreItemAction type
type RestoreItemAction struct {
mock.Mock
}
// AppliesTo provides a mock function with given fields:
func (_m *RestoreItemAction) AppliesTo() (velero.ResourceSelector, error) {
ret := _m.Called()
var r0 velero.ResourceSelector
if rf, ok := ret.Get(0).(func() velero.ResourceSelector); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(velero.ResourceSelector)
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// AreAdditionalItemsReady provides a mock function with given fields: AdditionalItems, restore
func (_m *RestoreItemAction) AreAdditionalItemsReady(AdditionalItems []velero.ResourceIdentifier, restore *v1.Restore) (bool, error) {
ret := _m.Called(AdditionalItems, restore)
var r0 bool
if rf, ok := ret.Get(0).(func([]velero.ResourceIdentifier, *v1.Restore) bool); ok {
r0 = rf(AdditionalItems, restore)
} else {
r0 = ret.Get(0).(bool)
}
var r1 error
if rf, ok := ret.Get(1).(func([]velero.ResourceIdentifier, *v1.Restore) error); ok {
r1 = rf(AdditionalItems, restore)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Cancel provides a mock function with given fields: operationID, restore
func (_m *RestoreItemAction) Cancel(operationID string, restore *v1.Restore) error {
ret := _m.Called(operationID, restore)
var r0 error
if rf, ok := ret.Get(0).(func(string, *v1.Restore) error); ok {
r0 = rf(operationID, restore)
} else {
r0 = ret.Error(0)
}
return r0
}
// Execute provides a mock function with given fields: input
func (_m *RestoreItemAction) Execute(input *velero.RestoreItemActionExecuteInput) (*velero.RestoreItemActionExecuteOutput, error) {
ret := _m.Called(input)
var r0 *velero.RestoreItemActionExecuteOutput
if rf, ok := ret.Get(0).(func(*velero.RestoreItemActionExecuteInput) *velero.RestoreItemActionExecuteOutput); ok {
r0 = rf(input)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*velero.RestoreItemActionExecuteOutput)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*velero.RestoreItemActionExecuteInput) error); ok {
r1 = rf(input)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Progress provides a mock function with given fields: operationID, restore
func (_m *RestoreItemAction) Progress(operationID string, restore *v1.Restore) (velero.OperationProgress, error) {
ret := _m.Called(operationID, restore)
var r0 velero.OperationProgress
if rf, ok := ret.Get(0).(func(string, *v1.Restore) velero.OperationProgress); ok {
r0 = rf(operationID, restore)
} else {
r0 = ret.Get(0).(velero.OperationProgress)
}
var r1 error
if rf, ok := ret.Get(1).(func(string, *v1.Restore) error); ok {
r1 = rf(operationID, restore)
} else {
r1 = ret.Error(1)
}
return r0, r1
}

View File

@ -17,6 +17,8 @@ limitations under the License.
package velero
import (
"time"
"k8s.io/apimachinery/pkg/runtime"
api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
@ -46,6 +48,26 @@ type RestoreItemActionExecuteOutput struct {
// on this item, and skip the restore step. When this field's
// value is true, AdditionalItems will be ignored.
SkipRestore bool
// v2 and later
// OperationID is an identifier which indicates an ongoing asynchronous action which Velero will
// continue to monitor after restoring this item. If left blank, then there is no ongoing operation.
OperationID string
// v2 and later
// WaitForAdditionalItems determines whether velero will wait
// until AreAdditionalItemsReady returns true before restoring
// this item. If this field's value is true, then after restoring
// the returned AdditionalItems, velero will not restore this item
// until AreAdditionalItemsReady returns true or the timeout is
// reached. Otherwise, AreAdditionalItemsReady is not called.
WaitForAdditionalItems bool
// v2 and later
// AdditionalItemsReadyTimeout will override serverConfig.additionalItemsReadyTimeout
// if specified. This value specifies how long velero will wait
// for additional items to be ready before moving on.
AdditionalItemsReadyTimeout time.Duration
}
// NewRestoreItemActionExecuteOutput creates a new RestoreItemActionExecuteOutput
@ -60,3 +82,15 @@ func (r *RestoreItemActionExecuteOutput) WithoutRestore() *RestoreItemActionExec
r.SkipRestore = true
return r
}
// WithOperationID returns RestoreItemActionExecuteOutput with OperationID set.
func (r *RestoreItemActionExecuteOutput) WithOperationID(operationID string) *RestoreItemActionExecuteOutput {
r.OperationID = operationID
return r
}
// WithItemsWait returns RestoreItemActionExecuteOutput with WaitForAdditionalItems set to true.
func (r *RestoreItemActionExecuteOutput) WithItemsWait() *RestoreItemActionExecuteOutput {
r.WaitForAdditionalItems = true
return r
}

View File

@ -0,0 +1,70 @@
/*
Copyright 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 v2
import (
"fmt"
"github.com/pkg/errors"
api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
)
// RestoreItemAction is an actor that performs an operation on an individual item being restored.
type RestoreItemAction interface {
// AppliesTo returns information about which resources this action should be invoked for.
// A RestoreItemAction's Execute function will only be invoked on items that match the returned
// selector. A zero-valued ResourceSelector matches all resources.
AppliesTo() (velero.ResourceSelector, error)
// Execute allows the ItemAction to perform arbitrary logic with the item being restored,
// including mutating the item itself prior to restore. The return struct includes:
// The item (unmodified or modified), an optional slice of ResourceIdentifiers
// specifying additional related items that should be restored, an optional OperationID,
// a bool (waitForAdditionalItems) specifying whether Velero should wait until restored additional
// items are ready before restoring this resource, and an optional timeout for the additional items
// wait period. An error is returned if the action fails.
Execute(input *velero.RestoreItemActionExecuteInput) (*velero.RestoreItemActionExecuteOutput, error)
// Progress allows the RestoreItemAction to report on progress of an asynchronous action.
// For the passed-in operation, the plugin will return an OperationProgress struct, indicating
// whether the operation has completed, whether there were any errors, a plugin-specific
// indication of how much of the operation is done (items completed out of items-to-complete),
// and started/updated timestamps
Progress(operationID string, restore *api.Restore) (velero.OperationProgress, error)
// Cancel allows the RestoreItemAction to cancel an asynchronous action (if possible).
// Velero will call this if the wait timeout for asynchronous actions has been reached.
// If operation cancel is not supported, then the plugin just needs to return. No error
// return is expected in this case, since cancellation is optional here.
Cancel(operationID string, restore *api.Restore) error
// AreAdditionalItemsReady allows the ItemAction to communicate whether the passed-in
// slice of AdditionalItems (previously returned by Execute())
// are ready. Returns true if all items are ready, and false
// otherwise. The second return value is to report errors
AreAdditionalItemsReady(AdditionalItems []velero.ResourceIdentifier, restore *api.Restore) (bool, error)
}
func AsyncOperationsNotSupportedError() error {
return errors.New("Plugin does not support asynchronous operations")
}
func InvalidOperationIDError(operationID string) error {
return errors.New(fmt.Sprintf("Operation ID %v is invalid.", operationID))
}

View File

@ -59,7 +59,7 @@ import (
"github.com/vmware-tanzu/velero/pkg/label"
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
riav1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/restoreitemaction/v1"
riav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/restoreitemaction/v2"
vsv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/volumesnapshotter/v1"
"github.com/vmware-tanzu/velero/pkg/podexec"
"github.com/vmware-tanzu/velero/pkg/podvolume"
@ -88,13 +88,13 @@ type Request struct {
type Restorer interface {
// Restore restores the backup data from backupReader, returning warnings and errors.
Restore(req Request,
actions []riav1.RestoreItemAction,
actions []riav2.RestoreItemAction,
snapshotLocationLister listers.VolumeSnapshotLocationLister,
volumeSnapshotterGetter VolumeSnapshotterGetter,
) (Result, Result)
RestoreWithResolvers(
req Request,
restoreItemActionResolver framework.RestoreItemActionResolver,
restoreItemActionResolver framework.RestoreItemActionResolverV2,
itemSnapshotterResolver framework.ItemSnapshotterResolver,
snapshotLocationLister listers.VolumeSnapshotLocationLister,
volumeSnapshotterGetter VolumeSnapshotterGetter,
@ -164,18 +164,18 @@ func NewKubernetesRestorer(
// respectively, summarizing info about the restore.
func (kr *kubernetesRestorer) Restore(
req Request,
actions []riav1.RestoreItemAction,
actions []riav2.RestoreItemAction,
snapshotLocationLister listers.VolumeSnapshotLocationLister,
volumeSnapshotterGetter VolumeSnapshotterGetter,
) (Result, Result) {
resolver := framework.NewRestoreItemActionResolver(actions)
resolver := framework.NewRestoreItemActionResolverV2(actions)
snapshotItemResolver := framework.NewItemSnapshotterResolver(nil)
return kr.RestoreWithResolvers(req, resolver, snapshotItemResolver, snapshotLocationLister, volumeSnapshotterGetter)
}
func (kr *kubernetesRestorer) RestoreWithResolvers(
req Request,
restoreItemActionResolver framework.RestoreItemActionResolver,
restoreItemActionResolver framework.RestoreItemActionResolverV2,
itemSnapshotterResolver framework.ItemSnapshotterResolver,
snapshotLocationLister listers.VolumeSnapshotLocationLister,
volumeSnapshotterGetter VolumeSnapshotterGetter,
@ -342,7 +342,7 @@ type restoreContext struct {
dynamicFactory client.DynamicFactory
fileSystem filesystem.Interface
namespaceClient corev1.NamespaceInterface
restoreItemActions []framework.RestoreItemResolvedAction
restoreItemActions []framework.RestoreItemResolvedActionV2
itemSnapshotterActions []framework.ItemSnapshotterResolvedAction
volumeSnapshotterGetter VolumeSnapshotterGetter
podVolumeRestorer podvolume.Restorer
@ -743,8 +743,8 @@ func getNamespace(logger logrus.FieldLogger, path, remappedName string) *v1.Name
}
}
func (ctx *restoreContext) getApplicableActions(groupResource schema.GroupResource, namespace string) []framework.RestoreItemResolvedAction {
var actions []framework.RestoreItemResolvedAction
func (ctx *restoreContext) getApplicableActions(groupResource schema.GroupResource, namespace string) []framework.RestoreItemResolvedActionV2 {
var actions []framework.RestoreItemResolvedActionV2
for _, action := range ctx.restoreItemActions {
if action.ShouldUse(groupResource, namespace, nil, ctx.log) {
actions = append(actions, action)

View File

@ -48,7 +48,7 @@ import (
velerov1informers "github.com/vmware-tanzu/velero/pkg/generated/informers/externalversions"
"github.com/vmware-tanzu/velero/pkg/kuberesource"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
riav1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/restoreitemaction/v1"
riav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/restoreitemaction/v2"
vsv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/volumesnapshotter/v1"
"github.com/vmware-tanzu/velero/pkg/podvolume"
uploadermocks "github.com/vmware-tanzu/velero/pkg/podvolume/mocks"
@ -1142,9 +1142,12 @@ func TestRestoreItems(t *testing.T) {
// to run for specific resources/namespaces and simply records the items
// that it is executed for.
type recordResourcesAction struct {
selector velero.ResourceSelector
ids []string
additionalItems []velero.ResourceIdentifier
selector velero.ResourceSelector
ids []string
additionalItems []velero.ResourceIdentifier
operationID string
waitForAdditionalItems bool
additionalItemsReadyTimeout time.Duration
}
func (a *recordResourcesAction) AppliesTo() (velero.ResourceSelector, error) {
@ -1155,18 +1158,36 @@ func (a *recordResourcesAction) Execute(input *velero.RestoreItemActionExecuteIn
metadata, err := meta.Accessor(input.Item)
if err != nil {
return &velero.RestoreItemActionExecuteOutput{
UpdatedItem: input.Item,
AdditionalItems: a.additionalItems,
UpdatedItem: input.Item,
AdditionalItems: a.additionalItems,
OperationID: a.operationID,
WaitForAdditionalItems: a.waitForAdditionalItems,
AdditionalItemsReadyTimeout: a.additionalItemsReadyTimeout,
}, err
}
a.ids = append(a.ids, kubeutil.NamespaceAndName(metadata))
return &velero.RestoreItemActionExecuteOutput{
UpdatedItem: input.Item,
AdditionalItems: a.additionalItems,
UpdatedItem: input.Item,
AdditionalItems: a.additionalItems,
OperationID: a.operationID,
WaitForAdditionalItems: a.waitForAdditionalItems,
AdditionalItemsReadyTimeout: a.additionalItemsReadyTimeout,
}, nil
}
func (a *recordResourcesAction) Progress(operationID string, restore *velerov1api.Restore) (velero.OperationProgress, error) {
return velero.OperationProgress{}, nil
}
func (a *recordResourcesAction) Cancel(operationID string, restore *velerov1api.Restore) error {
return nil
}
func (a *recordResourcesAction) AreAdditionalItemsReady(additionalItems []velero.ResourceIdentifier, restore *velerov1api.Restore) (bool, error) {
return true, nil
}
func (a *recordResourcesAction) ForResource(resource string) *recordResourcesAction {
a.selector.IncludedResources = append(a.selector.IncludedResources, resource)
return a
@ -1322,7 +1343,7 @@ func TestRestoreActionsRunForCorrectItems(t *testing.T) {
h.AddItems(t, r)
}
actions := []riav1.RestoreItemAction{}
actions := []riav2.RestoreItemAction{}
for action := range tc.actions {
actions = append(actions, action)
}
@ -1374,11 +1395,23 @@ func (a *pluggableAction) AppliesTo() (velero.ResourceSelector, error) {
return a.selector, nil
}
func (a *pluggableAction) Progress(operationID string, restore *velerov1api.Restore) (velero.OperationProgress, error) {
return velero.OperationProgress{}, nil
}
func (a *pluggableAction) Cancel(operationID string, restore *velerov1api.Restore) error {
return nil
}
func (a *pluggableAction) addSelector(selector velero.ResourceSelector) *pluggableAction {
a.selector = selector
return a
}
func (a *pluggableAction) AreAdditionalItemsReady(additionalItems []velero.ResourceIdentifier, restore *velerov1api.Restore) (bool, error) {
return true, nil
}
// TestRestoreActionModifications runs restores with restore item actions that modify resources, and
// verifies that that the modified item is correctly created in the API. Verification is done by looking
// at the full object in the API.
@ -1409,7 +1442,7 @@ func TestRestoreActionModifications(t *testing.T) {
backup *velerov1api.Backup
apiResources []*test.APIResource
tarball io.Reader
actions []riav1.RestoreItemAction
actions []riav2.RestoreItemAction
want []*test.APIResource
}{
{
@ -1418,7 +1451,7 @@ func TestRestoreActionModifications(t *testing.T) {
backup: defaultBackup().Result(),
tarball: test.NewTarWriter(t).AddItems("pods", builder.ForPod("ns-1", "pod-1").Result()).Done(),
apiResources: []*test.APIResource{test.Pods()},
actions: []riav1.RestoreItemAction{
actions: []riav2.RestoreItemAction{
modifyingActionGetter(func(item *unstructured.Unstructured) {
item.SetLabels(map[string]string{"updated": "true"})
}),
@ -1435,7 +1468,7 @@ func TestRestoreActionModifications(t *testing.T) {
backup: defaultBackup().Result(),
tarball: test.NewTarWriter(t).AddItems("pods", builder.ForPod("ns-1", "pod-1").ObjectMeta(builder.WithLabels("should-be-removed", "true")).Result()).Done(),
apiResources: []*test.APIResource{test.Pods()},
actions: []riav1.RestoreItemAction{
actions: []riav2.RestoreItemAction{
modifyingActionGetter(func(item *unstructured.Unstructured) {
item.SetLabels(nil)
}),
@ -1450,7 +1483,7 @@ func TestRestoreActionModifications(t *testing.T) {
backup: defaultBackup().Result(),
tarball: test.NewTarWriter(t).AddItems("pods", builder.ForPod("ns-1", "pod-1").Result()).Done(),
apiResources: []*test.APIResource{test.Pods()},
actions: []riav1.RestoreItemAction{
actions: []riav2.RestoreItemAction{
modifyingActionGetter(func(item *unstructured.Unstructured) {
item.SetLabels(map[string]string{"updated": "true"})
}).addSelector(velero.ResourceSelector{
@ -1522,7 +1555,7 @@ func TestRestoreActionAdditionalItems(t *testing.T) {
backup *velerov1api.Backup
tarball io.Reader
apiResources []*test.APIResource
actions []riav1.RestoreItemAction
actions []riav2.RestoreItemAction
want map[*test.APIResource][]string
}{
{
@ -1531,7 +1564,7 @@ func TestRestoreActionAdditionalItems(t *testing.T) {
backup: defaultBackup().Result(),
tarball: test.NewTarWriter(t).AddItems("pods", builder.ForPod("ns-1", "pod-1").Result(), builder.ForPod("ns-2", "pod-2").Result()).Done(),
apiResources: []*test.APIResource{test.Pods()},
actions: []riav1.RestoreItemAction{
actions: []riav2.RestoreItemAction{
&pluggableAction{
selector: velero.ResourceSelector{IncludedNamespaces: []string{"ns-1"}},
executeFunc: func(input *velero.RestoreItemActionExecuteInput) (*velero.RestoreItemActionExecuteOutput, error) {
@ -1554,7 +1587,7 @@ func TestRestoreActionAdditionalItems(t *testing.T) {
backup: defaultBackup().Result(),
tarball: test.NewTarWriter(t).AddItems("pods", builder.ForPod("ns-1", "pod-1").Result(), builder.ForPod("ns-2", "pod-2").Result()).Done(),
apiResources: []*test.APIResource{test.Pods()},
actions: []riav1.RestoreItemAction{
actions: []riav2.RestoreItemAction{
&pluggableAction{
executeFunc: func(input *velero.RestoreItemActionExecuteInput) (*velero.RestoreItemActionExecuteOutput, error) {
return &velero.RestoreItemActionExecuteOutput{
@ -1579,7 +1612,7 @@ func TestRestoreActionAdditionalItems(t *testing.T) {
AddItems("persistentvolumes", builder.ForPersistentVolume("pv-1").Result()).
Done(),
apiResources: []*test.APIResource{test.Pods(), test.PVs()},
actions: []riav1.RestoreItemAction{
actions: []riav2.RestoreItemAction{
&pluggableAction{
executeFunc: func(input *velero.RestoreItemActionExecuteInput) (*velero.RestoreItemActionExecuteOutput, error) {
return &velero.RestoreItemActionExecuteOutput{
@ -1605,7 +1638,7 @@ func TestRestoreActionAdditionalItems(t *testing.T) {
AddItems("persistentvolumes", builder.ForPersistentVolume("pv-1").Result()).
Done(),
apiResources: []*test.APIResource{test.Pods(), test.PVs()},
actions: []riav1.RestoreItemAction{
actions: []riav2.RestoreItemAction{
&pluggableAction{
executeFunc: func(input *velero.RestoreItemActionExecuteInput) (*velero.RestoreItemActionExecuteOutput, error) {
return &velero.RestoreItemActionExecuteOutput{
@ -1631,7 +1664,7 @@ func TestRestoreActionAdditionalItems(t *testing.T) {
AddItems("persistentvolumes", builder.ForPersistentVolume("pv-1").Result()).
Done(),
apiResources: []*test.APIResource{test.Pods(), test.PVs()},
actions: []riav1.RestoreItemAction{
actions: []riav2.RestoreItemAction{
&pluggableAction{
executeFunc: func(input *velero.RestoreItemActionExecuteInput) (*velero.RestoreItemActionExecuteOutput, error) {
return &velero.RestoreItemActionExecuteOutput{