From 4a3b3be7ae60c89e80327d9b6c6565301051f573 Mon Sep 17 00:00:00 2001 From: Scott Seago Date: Thu, 1 Sep 2022 14:36:35 -0400 Subject: [PATCH] BackupItemAction v2 API implementation Signed-off-by: Scott Seago --- changelogs/unreleased/5442-sseago | 1 + hack/build-image/Dockerfile | 4 + hack/update-2proto.sh | 2 +- pkg/backup/backup.go | 12 +- pkg/backup/backup_test.go | 109 ++- pkg/backup/item_backupper.go | 4 +- pkg/backup/request.go | 2 +- pkg/controller/backup_controller.go | 4 +- pkg/controller/backup_controller_test.go | 12 +- .../v2/restartable_backup_item_action.go | 171 ++++ .../v2/restartable_backup_item_action_test.go | 164 ++++ pkg/plugin/clientmgmt/manager.go | 46 + pkg/plugin/clientmgmt/manager_test.go | 110 +++ .../clientmgmt/process/client_builder.go | 16 +- .../clientmgmt/process/client_builder_test.go | 16 +- pkg/plugin/framework/action_resolver.go | 39 + .../backupitemaction/v2/backup_item_action.go | 45 + .../v2/backup_item_action_client.go | 167 ++++ .../v2/backup_item_action_server.go | 210 +++++ .../v2/backup_item_action_test.go | 202 +++++ pkg/plugin/framework/common/plugin_kinds.go | 8 +- pkg/plugin/framework/server.go | 72 +- pkg/plugin/generated/Shared.pb.go | 220 ++++- .../v2/BackupItemAction.pb.go | 851 ++++++++++++++++++ pkg/plugin/mocks/manager.go | 48 + pkg/plugin/proto/Shared.proto | 13 +- .../v2/BackupItemAction.proto | 50 + .../backupitemaction/v2/backup_item_action.go | 63 ++ .../backupitemaction/v2/BackupItemAction.go | 127 +++ pkg/plugin/velero/shared.go | 25 +- 30 files changed, 2664 insertions(+), 149 deletions(-) create mode 100644 changelogs/unreleased/5442-sseago create mode 100644 pkg/plugin/clientmgmt/backupitemaction/v2/restartable_backup_item_action.go create mode 100644 pkg/plugin/clientmgmt/backupitemaction/v2/restartable_backup_item_action_test.go create mode 100644 pkg/plugin/framework/backupitemaction/v2/backup_item_action.go create mode 100644 pkg/plugin/framework/backupitemaction/v2/backup_item_action_client.go create mode 100644 pkg/plugin/framework/backupitemaction/v2/backup_item_action_server.go create mode 100644 pkg/plugin/framework/backupitemaction/v2/backup_item_action_test.go create mode 100644 pkg/plugin/generated/backupitemaction/v2/BackupItemAction.pb.go create mode 100644 pkg/plugin/proto/backupitemaction/v2/BackupItemAction.proto create mode 100644 pkg/plugin/velero/backupitemaction/v2/backup_item_action.go create mode 100644 pkg/plugin/velero/mocks/backupitemaction/v2/BackupItemAction.go diff --git a/changelogs/unreleased/5442-sseago b/changelogs/unreleased/5442-sseago new file mode 100644 index 00000000000..ce6c9a01f54 --- /dev/null +++ b/changelogs/unreleased/5442-sseago @@ -0,0 +1 @@ +BackupItemAction v2 API implementation diff --git a/hack/build-image/Dockerfile b/hack/build-image/Dockerfile index 5baa4365e27..669ce2dbd6f 100644 --- a/hack/build-image/Dockerfile +++ b/hack/build-image/Dockerfile @@ -48,6 +48,10 @@ RUN apt-get update && apt-get install -y unzip RUN wget --quiet https://github.com/protocolbuffers/protobuf/releases/download/v3.14.0/protoc-3.14.0-linux-x86_64.zip && \ unzip protoc-3.14.0-linux-x86_64.zip && \ mv bin/protoc /usr/bin/protoc && \ + mv include/google /usr/include && \ + chmod a+x /usr/include/google && \ + chmod a+x /usr/include/google/protobuf && \ + chmod a+r -R /usr/include/google && \ chmod +x /usr/bin/protoc RUN go get github.com/golang/protobuf/protoc-gen-go@v1.4.3 diff --git a/hack/update-2proto.sh b/hack/update-2proto.sh index 43afa8d4cee..8ad696e1634 100755 --- a/hack/update-2proto.sh +++ b/hack/update-2proto.sh @@ -19,6 +19,6 @@ HACK_DIR=$(dirname "${BASH_SOURCE}") echo "Updating plugin proto" echo protoc --version -protoc pkg/plugin/proto/*.proto --go_out=plugins=grpc:pkg/plugin/generated/ --go_opt=module=github.com/vmware-tanzu/velero/pkg/plugin/generated -I pkg/plugin/proto/ +protoc pkg/plugin/proto/*.proto pkg/plugin/proto/*/*/*.proto --go_out=plugins=grpc:pkg/plugin/generated/ --go_opt=module=github.com/vmware-tanzu/velero/pkg/plugin/generated -I pkg/plugin/proto/ -I /usr/include echo "Updating plugin proto - done!" diff --git a/pkg/backup/backup.go b/pkg/backup/backup.go index ba20ed0d4ab..3123c419d8f 100644 --- a/pkg/backup/backup.go +++ b/pkg/backup/backup.go @@ -44,7 +44,7 @@ import ( velerov1client "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/typed/velero/v1" "github.com/vmware-tanzu/velero/pkg/kuberesource" "github.com/vmware-tanzu/velero/pkg/plugin/framework" - biav1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v1" + biav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/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" @@ -63,9 +63,9 @@ const BackupFormatVersion = "1.1.0" type Backupper interface { // Backup takes a backup using the specification in the velerov1api.Backup and writes backup and log data // to the given writers. - Backup(logger logrus.FieldLogger, backup *Request, backupFile io.Writer, actions []biav1.BackupItemAction, volumeSnapshotterGetter VolumeSnapshotterGetter) error + Backup(logger logrus.FieldLogger, backup *Request, backupFile io.Writer, actions []biav2.BackupItemAction, volumeSnapshotterGetter VolumeSnapshotterGetter) error BackupWithResolvers(log logrus.FieldLogger, backupRequest *Request, backupFile io.Writer, - backupItemActionResolver framework.BackupItemActionResolver, itemSnapshotterResolver framework.ItemSnapshotterResolver, + backupItemActionResolver framework.BackupItemActionResolverV2, itemSnapshotterResolver framework.ItemSnapshotterResolver, volumeSnapshotterGetter VolumeSnapshotterGetter) error } @@ -174,8 +174,8 @@ type VolumeSnapshotterGetter interface { // back up individual resources that don't prevent the backup from continuing to be processed) are logged // to the backup log. func (kb *kubernetesBackupper) Backup(log logrus.FieldLogger, backupRequest *Request, backupFile io.Writer, - actions []biav1.BackupItemAction, volumeSnapshotterGetter VolumeSnapshotterGetter) error { - backupItemActions := framework.NewBackupItemActionResolver(actions) + actions []biav2.BackupItemAction, volumeSnapshotterGetter VolumeSnapshotterGetter) error { + backupItemActions := framework.NewBackupItemActionResolverV2(actions) itemSnapshotters := framework.NewItemSnapshotterResolver(nil) return kb.BackupWithResolvers(log, backupRequest, backupFile, backupItemActions, itemSnapshotters, volumeSnapshotterGetter) @@ -184,7 +184,7 @@ func (kb *kubernetesBackupper) Backup(log logrus.FieldLogger, backupRequest *Req func (kb *kubernetesBackupper) BackupWithResolvers(log logrus.FieldLogger, backupRequest *Request, backupFile io.Writer, - backupItemActionResolver framework.BackupItemActionResolver, + backupItemActionResolver framework.BackupItemActionResolverV2, itemSnapshotterResolver framework.ItemSnapshotterResolver, volumeSnapshotterGetter VolumeSnapshotterGetter) error { gzippedData := gzip.NewWriter(backupFile) diff --git a/pkg/backup/backup_test.go b/pkg/backup/backup_test.go index 387c91c4acc..f0bde8a268e 100644 --- a/pkg/backup/backup_test.go +++ b/pkg/backup/backup_test.go @@ -47,7 +47,7 @@ import ( "github.com/vmware-tanzu/velero/pkg/discovery" "github.com/vmware-tanzu/velero/pkg/kuberesource" "github.com/vmware-tanzu/velero/pkg/plugin/velero" - biav1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v1" + biav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v2" vsv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/volumesnapshotter/v1" "github.com/vmware-tanzu/velero/pkg/podvolume" "github.com/vmware-tanzu/velero/pkg/test" @@ -1139,23 +1139,32 @@ type recordResourcesAction struct { ids []string backups []velerov1.Backup additionalItems []velero.ResourceIdentifier + operationID string } -func (a *recordResourcesAction) Execute(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) { +func (a *recordResourcesAction) Execute(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { metadata, err := meta.Accessor(item) if err != nil { - return item, a.additionalItems, err + return item, a.additionalItems, a.operationID, err } a.ids = append(a.ids, kubeutil.NamespaceAndName(metadata)) a.backups = append(a.backups, *backup) - return item, a.additionalItems, nil + return item, a.additionalItems, a.operationID, nil } func (a *recordResourcesAction) AppliesTo() (velero.ResourceSelector, error) { return a.selector, nil } +func (a *recordResourcesAction) Progress(operationID string, backup *velerov1.Backup) (velero.OperationProgress, error) { + return velero.OperationProgress{}, nil +} + +func (a *recordResourcesAction) Cancel(operationID string, backup *velerov1.Backup) error { + return nil +} + func (a *recordResourcesAction) ForResource(resource string) *recordResourcesAction { a.selector.IncludedResources = append(a.selector.IncludedResources, resource) return a @@ -1362,7 +1371,7 @@ func TestBackupActionsRunForCorrectItems(t *testing.T) { h.addItems(t, resource) } - actions := []biav1.BackupItemAction{} + actions := []biav2.BackupItemAction{} for action := range tc.actions { actions = append(actions, action) } @@ -1388,7 +1397,7 @@ func TestBackupWithInvalidActions(t *testing.T) { name string backup *velerov1.Backup apiResources []*test.APIResource - actions []biav1.BackupItemAction + actions []biav2.BackupItemAction }{ { name: "action with invalid label selector results in an error", @@ -1404,7 +1413,7 @@ func TestBackupWithInvalidActions(t *testing.T) { builder.ForPersistentVolume("baz").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ new(recordResourcesAction).ForLabelSelector("=invalid-selector"), }, }, @@ -1422,7 +1431,7 @@ func TestBackupWithInvalidActions(t *testing.T) { builder.ForPersistentVolume("baz").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ &appliesToErrorAction{}, }, }, @@ -1453,7 +1462,15 @@ func (a *appliesToErrorAction) AppliesTo() (velero.ResourceSelector, error) { return velero.ResourceSelector{}, errors.New("error calling AppliesTo") } -func (a *appliesToErrorAction) Execute(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) { +func (a *appliesToErrorAction) Execute(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { + panic("not implemented") +} + +func (a *appliesToErrorAction) Progress(operationID string, backup *velerov1.Backup) (velero.OperationProgress, error) { + panic("not implemented") +} + +func (a *appliesToErrorAction) Cancel(operationID string, backup *velerov1.Backup) error { panic("not implemented") } @@ -1466,16 +1483,16 @@ func TestBackupActionModifications(t *testing.T) { // method modifies the item being passed in by calling the 'modify' function on it. modifyingActionGetter := func(modify func(*unstructured.Unstructured)) *pluggableAction { return &pluggableAction{ - executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) { + executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { obj, ok := item.(*unstructured.Unstructured) if !ok { - return nil, nil, errors.Errorf("unexpected type %T", item) + return nil, nil, "", errors.Errorf("unexpected type %T", item) } res := obj.DeepCopy() modify(res) - return res, nil, nil + return res, nil, "", nil }, } } @@ -1484,7 +1501,7 @@ func TestBackupActionModifications(t *testing.T) { name string backup *velerov1.Backup apiResources []*test.APIResource - actions []biav1.BackupItemAction + actions []biav2.BackupItemAction want map[string]unstructuredObject }{ { @@ -1495,7 +1512,7 @@ func TestBackupActionModifications(t *testing.T) { builder.ForPod("ns-1", "pod-1").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ modifyingActionGetter(func(item *unstructured.Unstructured) { item.SetLabels(map[string]string{"updated": "true"}) }), @@ -1512,7 +1529,7 @@ func TestBackupActionModifications(t *testing.T) { builder.ForPod("ns-1", "pod-1").ObjectMeta(builder.WithLabels("should-be-removed", "true")).Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ modifyingActionGetter(func(item *unstructured.Unstructured) { item.SetLabels(nil) }), @@ -1529,7 +1546,7 @@ func TestBackupActionModifications(t *testing.T) { builder.ForPod("ns-1", "pod-1").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ modifyingActionGetter(func(item *unstructured.Unstructured) { item.Object["spec"].(map[string]interface{})["nodeName"] = "foo" }), @@ -1547,7 +1564,7 @@ func TestBackupActionModifications(t *testing.T) { builder.ForPod("ns-1", "pod-1").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ modifyingActionGetter(func(item *unstructured.Unstructured) { item.SetName(item.GetName() + "-updated") item.SetNamespace(item.GetNamespace() + "-updated") @@ -1588,7 +1605,7 @@ func TestBackupActionAdditionalItems(t *testing.T) { name string backup *velerov1.Backup apiResources []*test.APIResource - actions []biav1.BackupItemAction + actions []biav2.BackupItemAction want []string }{ { @@ -1601,16 +1618,16 @@ func TestBackupActionAdditionalItems(t *testing.T) { builder.ForPod("ns-3", "pod-3").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ &pluggableAction{ selector: velero.ResourceSelector{IncludedNamespaces: []string{"ns-1"}}, - executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) { + executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { additionalItems := []velero.ResourceIdentifier{ {GroupResource: kuberesource.Pods, Namespace: "ns-2", Name: "pod-2"}, {GroupResource: kuberesource.Pods, Namespace: "ns-3", Name: "pod-3"}, } - return item, additionalItems, nil + return item, additionalItems, "", nil }, }, }, @@ -1633,15 +1650,15 @@ func TestBackupActionAdditionalItems(t *testing.T) { builder.ForPod("ns-3", "pod-3").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ &pluggableAction{ - executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) { + executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { additionalItems := []velero.ResourceIdentifier{ {GroupResource: kuberesource.Pods, Namespace: "ns-2", Name: "pod-2"}, {GroupResource: kuberesource.Pods, Namespace: "ns-3", Name: "pod-3"}, } - return item, additionalItems, nil + return item, additionalItems, "", nil }, }, }, @@ -1663,15 +1680,15 @@ func TestBackupActionAdditionalItems(t *testing.T) { builder.ForPersistentVolume("pv-2").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ &pluggableAction{ - executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) { + executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { additionalItems := []velero.ResourceIdentifier{ {GroupResource: kuberesource.PersistentVolumes, Name: "pv-1"}, {GroupResource: kuberesource.PersistentVolumes, Name: "pv-2"}, } - return item, additionalItems, nil + return item, additionalItems, "", nil }, }, }, @@ -1696,15 +1713,15 @@ func TestBackupActionAdditionalItems(t *testing.T) { builder.ForPersistentVolume("pv-2").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ &pluggableAction{ - executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) { + executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { additionalItems := []velero.ResourceIdentifier{ {GroupResource: kuberesource.PersistentVolumes, Name: "pv-1"}, {GroupResource: kuberesource.PersistentVolumes, Name: "pv-2"}, } - return item, additionalItems, nil + return item, additionalItems, "", nil }, }, }, @@ -1726,15 +1743,15 @@ func TestBackupActionAdditionalItems(t *testing.T) { builder.ForPersistentVolume("pv-2").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ &pluggableAction{ - executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) { + executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { additionalItems := []velero.ResourceIdentifier{ {GroupResource: kuberesource.PersistentVolumes, Name: "pv-1"}, {GroupResource: kuberesource.PersistentVolumes, Name: "pv-2"}, } - return item, additionalItems, nil + return item, additionalItems, "", nil }, }, }, @@ -1757,15 +1774,15 @@ func TestBackupActionAdditionalItems(t *testing.T) { builder.ForPersistentVolume("pv-2").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ &pluggableAction{ - executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) { + executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { additionalItems := []velero.ResourceIdentifier{ {GroupResource: kuberesource.PersistentVolumes, Name: "pv-1"}, {GroupResource: kuberesource.PersistentVolumes, Name: "pv-2"}, } - return item, additionalItems, nil + return item, additionalItems, "", nil }, }, }, @@ -1787,16 +1804,16 @@ func TestBackupActionAdditionalItems(t *testing.T) { builder.ForPod("ns-3", "pod-3").Result(), ), }, - actions: []biav1.BackupItemAction{ + actions: []biav2.BackupItemAction{ &pluggableAction{ selector: velero.ResourceSelector{IncludedNamespaces: []string{"ns-1"}}, - executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) { + executeFunc: func(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { additionalItems := []velero.ResourceIdentifier{ {GroupResource: kuberesource.Pods, Namespace: "ns-4", Name: "pod-4"}, {GroupResource: kuberesource.Pods, Namespace: "ns-5", Name: "pod-5"}, } - return item, additionalItems, nil + return item, additionalItems, "", nil }, }, }, @@ -2727,12 +2744,12 @@ func TestBackupWithRestic(t *testing.T) { // function body at runtime. type pluggableAction struct { selector velero.ResourceSelector - executeFunc func(runtime.Unstructured, *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) + executeFunc func(runtime.Unstructured, *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) } -func (a *pluggableAction) Execute(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) { +func (a *pluggableAction) Execute(item runtime.Unstructured, backup *velerov1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { if a.executeFunc == nil { - return item, nil, nil + return item, nil, "", nil } return a.executeFunc(item, backup) @@ -2742,6 +2759,14 @@ func (a *pluggableAction) AppliesTo() (velero.ResourceSelector, error) { return a.selector, nil } +func (a *pluggableAction) Progress(operationID string, backup *velerov1.Backup) (velero.OperationProgress, error) { + return velero.OperationProgress{}, nil +} + +func (a *pluggableAction) Cancel(operationID string, backup *velerov1.Backup) error { + return nil +} + type harness struct { *test.APIServer backupper *kubernetesBackupper diff --git a/pkg/backup/item_backupper.go b/pkg/backup/item_backupper.go index 0e24fb32cf9..034aa2e59c8 100644 --- a/pkg/backup/item_backupper.go +++ b/pkg/backup/item_backupper.go @@ -320,7 +320,9 @@ func (ib *itemBackupper) executeActions( } log.Info("Executing custom action") - updatedItem, additionalItemIdentifiers, err := action.Execute(obj, ib.backupRequest.Backup) + // Note: we're ignoring the operationID returned from Execute for now, it will be used + // with the async plugin action implementation + updatedItem, additionalItemIdentifiers, _, err := action.Execute(obj, ib.backupRequest.Backup) if err != nil { return nil, errors.Wrapf(err, "error executing custom action (groupResource=%s, namespace=%s, name=%s)", groupResource.String(), namespace, name) } diff --git a/pkg/backup/request.go b/pkg/backup/request.go index 69dbeca704e..a94faa2d77c 100644 --- a/pkg/backup/request.go +++ b/pkg/backup/request.go @@ -45,7 +45,7 @@ type Request struct { NamespaceIncludesExcludes *collections.IncludesExcludes ResourceIncludesExcludes *collections.IncludesExcludes ResourceHooks []hook.ResourceHook - ResolvedActions []framework.BackupItemResolvedAction + ResolvedActions []framework.BackupItemResolvedActionV2 ResolvedItemSnapshotters []framework.ItemSnapshotterResolvedAction VolumeSnapshots []*volume.Snapshot PodVolumeBackups []*velerov1api.PodVolumeBackup diff --git a/pkg/controller/backup_controller.go b/pkg/controller/backup_controller.go index 39444459e4b..77e61b36e26 100644 --- a/pkg/controller/backup_controller.go +++ b/pkg/controller/backup_controller.go @@ -612,7 +612,7 @@ func (c *backupController) runBackup(backup *pkgbackup.Request) error { defer pluginManager.CleanupClients() backupLog.Info("Getting backup item actions") - actions, err := pluginManager.GetBackupItemActions() + actions, err := pluginManager.GetBackupItemActionsV2() if err != nil { return err } @@ -637,7 +637,7 @@ func (c *backupController) runBackup(backup *pkgbackup.Request) error { return errors.Errorf("backup already exists in object storage") } - backupItemActionsResolver := framework.NewBackupItemActionResolver(actions) + backupItemActionsResolver := framework.NewBackupItemActionResolverV2(actions) itemSnapshottersResolver := framework.NewItemSnapshotterResolver(itemSnapshotters) var fatalErrs []error diff --git a/pkg/controller/backup_controller_test.go b/pkg/controller/backup_controller_test.go index 4617975681a..46d0c8a3814 100644 --- a/pkg/controller/backup_controller_test.go +++ b/pkg/controller/backup_controller_test.go @@ -50,7 +50,7 @@ import ( "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt" "github.com/vmware-tanzu/velero/pkg/plugin/framework" pluginmocks "github.com/vmware-tanzu/velero/pkg/plugin/mocks" - biav1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v1" + biav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v2" velerotest "github.com/vmware-tanzu/velero/pkg/test" "github.com/vmware-tanzu/velero/pkg/util/boolptr" "github.com/vmware-tanzu/velero/pkg/util/logging" @@ -60,13 +60,13 @@ type fakeBackupper struct { mock.Mock } -func (b *fakeBackupper) Backup(logger logrus.FieldLogger, backup *pkgbackup.Request, backupFile io.Writer, actions []biav1.BackupItemAction, volumeSnapshotterGetter pkgbackup.VolumeSnapshotterGetter) error { +func (b *fakeBackupper) Backup(logger logrus.FieldLogger, backup *pkgbackup.Request, backupFile io.Writer, actions []biav2.BackupItemAction, volumeSnapshotterGetter pkgbackup.VolumeSnapshotterGetter) error { args := b.Called(logger, backup, backupFile, actions, volumeSnapshotterGetter) return args.Error(0) } func (b *fakeBackupper) BackupWithResolvers(logger logrus.FieldLogger, backup *pkgbackup.Request, backupFile io.Writer, - backupItemActionResolver framework.BackupItemActionResolver, itemSnapshotterResolver framework.ItemSnapshotterResolver, + backupItemActionResolver framework.BackupItemActionResolverV2, itemSnapshotterResolver framework.ItemSnapshotterResolver, volumeSnapshotterGetter pkgbackup.VolumeSnapshotterGetter) error { args := b.Called(logger, backup, backupFile, backupItemActionResolver, itemSnapshotterResolver, volumeSnapshotterGetter) return args.Error(0) @@ -1067,11 +1067,11 @@ func TestProcessBackupCompletions(t *testing.T) { formatFlag: formatFlag, } - pluginManager.On("GetBackupItemActions").Return(nil, nil) + pluginManager.On("GetBackupItemActionsV2").Return(nil, nil) pluginManager.On("CleanupClients").Return(nil) pluginManager.On("GetItemSnapshotters").Return(nil, nil) - backupper.On("Backup", mock.Anything, mock.Anything, mock.Anything, []biav1.BackupItemAction(nil), pluginManager).Return(nil) - backupper.On("BackupWithResolvers", mock.Anything, mock.Anything, mock.Anything, framework.BackupItemActionResolver{}, framework.ItemSnapshotterResolver{}, pluginManager).Return(nil) + backupper.On("Backup", mock.Anything, mock.Anything, mock.Anything, []biav2.BackupItemAction(nil), pluginManager).Return(nil) + backupper.On("BackupWithResolvers", mock.Anything, mock.Anything, mock.Anything, framework.BackupItemActionResolverV2{}, framework.ItemSnapshotterResolver{}, pluginManager).Return(nil) backupStore.On("BackupExists", test.backupLocation.Spec.StorageType.ObjectStorage.Bucket, test.backup.Name).Return(test.backupExists, test.existenceCheckError) // Ensure we have a CompletionTimestamp when uploading and that the backup name matches the backup in the object store. diff --git a/pkg/plugin/clientmgmt/backupitemaction/v2/restartable_backup_item_action.go b/pkg/plugin/clientmgmt/backupitemaction/v2/restartable_backup_item_action.go new file mode 100644 index 00000000000..4835c9096bc --- /dev/null +++ b/pkg/plugin/clientmgmt/backupitemaction/v2/restartable_backup_item_action.go @@ -0,0 +1,171 @@ +/* +Copyright 2018 the Velero contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2 + +import ( + "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/runtime" + + api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + 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/common" + "github.com/vmware-tanzu/velero/pkg/plugin/velero" + biav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v2" +) + +// AdaptedBackupItemAction is a backup item action adapted to the v1 BackupItemAction API +type AdaptedBackupItemAction struct { + Kind common.PluginKind + + // Get returns a restartable BackupItemAction for the given name and process, wrapping if necessary + GetRestartable func(name string, restartableProcess process.RestartableProcess) biav2.BackupItemAction +} + +func AdaptedBackupItemActions() []AdaptedBackupItemAction { + return []AdaptedBackupItemAction{ + { + Kind: common.PluginKindBackupItemActionV2, + GetRestartable: func(name string, restartableProcess process.RestartableProcess) biav2.BackupItemAction { + return NewRestartableBackupItemAction(name, restartableProcess) + }, + }, + { + Kind: common.PluginKindBackupItemAction, + GetRestartable: func(name string, restartableProcess process.RestartableProcess) biav2.BackupItemAction { + return NewAdaptedV1RestartableBackupItemAction(biav1cli.NewRestartableBackupItemAction(name, restartableProcess)) + }, + }, + } +} + +// restartableBackupItemAction is a backup 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 restartableBackupItemAction 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 RestartableBackupItemAction struct { + Key process.KindAndName + SharedPluginProcess process.RestartableProcess +} + +// NewRestartableBackupItemAction returns a new RestartableBackupItemAction. +func NewRestartableBackupItemAction(name string, sharedPluginProcess process.RestartableProcess) *RestartableBackupItemAction { + r := &RestartableBackupItemAction{ + Key: process.KindAndName{Kind: common.PluginKindBackupItemActionV2, Name: name}, + SharedPluginProcess: sharedPluginProcess, + } + return r +} + +// getBackupItemAction returns the backup item action for this restartableBackupItemAction. It does *not* restart the +// plugin process. +func (r *RestartableBackupItemAction) getBackupItemAction() (biav2.BackupItemAction, error) { + plugin, err := r.SharedPluginProcess.GetByKindAndName(r.Key) + if err != nil { + return nil, err + } + + backupItemAction, ok := plugin.(biav2.BackupItemAction) + if !ok { + return nil, errors.Errorf("%T (returned for %v) is not a BackupItemActionV2!", plugin, r.Key) + } + + return backupItemAction, nil +} + +// getDelegate restarts the plugin process (if needed) and returns the backup item action for this restartableBackupItemAction. +func (r *RestartableBackupItemAction) getDelegate() (biav2.BackupItemAction, error) { + if err := r.SharedPluginProcess.ResetIfNeeded(); err != nil { + return nil, err + } + + return r.getBackupItemAction() +} + +// AppliesTo restarts the plugin's process if needed, then delegates the call. +func (r *RestartableBackupItemAction) 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 *RestartableBackupItemAction) Execute(item runtime.Unstructured, backup *api.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { + delegate, err := r.getDelegate() + if err != nil { + return nil, nil, "", err + } + + return delegate.Execute(item, backup) +} + +// Progress restarts the plugin's process if needed, then delegates the call. +func (r *RestartableBackupItemAction) Progress(operationID string, backup *api.Backup) (velero.OperationProgress, error) { + delegate, err := r.getDelegate() + if err != nil { + return velero.OperationProgress{}, err + } + + return delegate.Progress(operationID, backup) +} + +// Cancel restarts the plugin's process if needed, then delegates the call. +func (r *RestartableBackupItemAction) Cancel(operationID string, backup *api.Backup) error { + delegate, err := r.getDelegate() + if err != nil { + return err + } + + return delegate.Cancel(operationID, backup) +} + +type AdaptedV1RestartableBackupItemAction struct { + V1Restartable *biav1cli.RestartableBackupItemAction +} + +// NewAdaptedV1RestartableBackupItemAction returns a new v1 RestartableBackupItemAction adapted to v2 +func NewAdaptedV1RestartableBackupItemAction(v1Restartable *biav1cli.RestartableBackupItemAction) *AdaptedV1RestartableBackupItemAction { + r := &AdaptedV1RestartableBackupItemAction{ + V1Restartable: v1Restartable, + } + return r +} + +// AppliesTo delegates to the v1 AppliesTo call. +func (r *AdaptedV1RestartableBackupItemAction) AppliesTo() (velero.ResourceSelector, error) { + return r.V1Restartable.AppliesTo() +} + +// Execute delegates to the v1 Execute call, returning an empty operationID. +func (r *AdaptedV1RestartableBackupItemAction) Execute(item runtime.Unstructured, backup *api.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { + updatedItem, additionalItems, err := r.V1Restartable.Execute(item, backup) + return updatedItem, additionalItems, "", err +} + +// 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 *AdaptedV1RestartableBackupItemAction) Progress(operationID string, backup *api.Backup) (velero.OperationProgress, error) { + return velero.OperationProgress{}, biav2.AsyncOperationsNotSupportedError() +} + +// Cancel just returns without error since v1 plugins don't implement it. +func (r *AdaptedV1RestartableBackupItemAction) Cancel(operationID string, backup *api.Backup) error { + return nil +} diff --git a/pkg/plugin/clientmgmt/backupitemaction/v2/restartable_backup_item_action_test.go b/pkg/plugin/clientmgmt/backupitemaction/v2/restartable_backup_item_action_test.go new file mode 100644 index 00000000000..24e3c12a602 --- /dev/null +++ b/pkg/plugin/clientmgmt/backupitemaction/v2/restartable_backup_item_action_test.go @@ -0,0 +1,164 @@ +/* +Copyright 2018 the Velero contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package 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" + "k8s.io/apimachinery/pkg/runtime/schema" + + "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" + mocksv2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/mocks/backupitemaction/v2" +) + +func TestRestartableGetBackupItemAction(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 (returned for {BackupItemActionV2 pod}) is not a BackupItemActionV2!", + }, + { + name: "happy path", + plugin: new(mocksv2.BackupItemAction), + }, + } + + 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.PluginKindBackupItemActionV2, Name: name} + p.On("GetByKindAndName", key).Return(tc.plugin, tc.getError) + + r := NewRestartableBackupItemAction(name, p) + a, err := r.getBackupItemAction() + if tc.expectedError != "" { + assert.EqualError(t, err, tc.expectedError) + return + } + require.NoError(t, err) + + assert.Equal(t, tc.plugin, a) + }) + } +} + +func TestRestartableBackupItemActionGetDelegate(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 := NewRestartableBackupItemAction(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(mocksv2.BackupItemAction) + key := process.KindAndName{Kind: common.PluginKindBackupItemActionV2, Name: name} + p.On("GetByKindAndName", key).Return(expected, nil) + + a, err = r.getDelegate() + assert.NoError(t, err) + assert.Equal(t, expected, a) +} + +func TestRestartableBackupItemActionDelegatedFunctions(t *testing.T) { + b := new(v1.Backup) + + pv := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "color": "blue", + }, + } + + oid := "operation1" + + pvToReturn := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "color": "green", + }, + } + + additionalItems := []velero.ResourceIdentifier{ + { + GroupResource: schema.GroupResource{Group: "velero.io", Resource: "backups"}, + }, + } + + restartabletest.RunRestartableDelegateTests( + t, + common.PluginKindBackupItemAction, + func(key process.KindAndName, p process.RestartableProcess) interface{} { + return &RestartableBackupItemAction{ + Key: key, + SharedPluginProcess: p, + } + }, + func() restartabletest.Mockable { + return new(mocksv2.BackupItemAction) + }, + 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{}{pv, b}, + ExpectedErrorOutputs: []interface{}{nil, ([]velero.ResourceIdentifier)(nil), "", errors.Errorf("reset error")}, + ExpectedDelegateOutputs: []interface{}{pvToReturn, additionalItems, "", errors.Errorf("delegate error")}, + }, + restartabletest.RestartableDelegateTest{ + Function: "Progress", + Inputs: []interface{}{oid, b}, + ExpectedErrorOutputs: []interface{}{velero.OperationProgress{}, errors.Errorf("reset error")}, + ExpectedDelegateOutputs: []interface{}{velero.OperationProgress{}, errors.Errorf("delegate error")}, + }, + restartabletest.RestartableDelegateTest{ + Function: "Cancel", + Inputs: []interface{}{oid, b}, + ExpectedErrorOutputs: []interface{}{errors.Errorf("reset error")}, + ExpectedDelegateOutputs: []interface{}{errors.Errorf("delegate error")}, + }, + ) +} diff --git a/pkg/plugin/clientmgmt/manager.go b/pkg/plugin/clientmgmt/manager.go index b94d986fa65..4e3343d6a45 100644 --- a/pkg/plugin/clientmgmt/manager.go +++ b/pkg/plugin/clientmgmt/manager.go @@ -25,12 +25,14 @@ import ( "github.com/sirupsen/logrus" biav1cli "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/backupitemaction/v1" + 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" 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" biav1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v1" + 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" vsv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/volumesnapshotter/v1" @@ -50,6 +52,12 @@ type Manager interface { // GetBackupItemAction returns the backup item action plugin for name. GetBackupItemAction(name string) (biav1.BackupItemAction, error) + // GetBackupItemActionsV2 returns all v2 backup item action plugins (including those adapted from v1). + GetBackupItemActionsV2() ([]biav2.BackupItemAction, error) + + // GetBackupItemActionV2 returns the backup item action plugin for name. + GetBackupItemActionV2(name string) (biav2.BackupItemAction, error) + // GetRestoreItemActions returns all restore item action plugins. GetRestoreItemActions() ([]riav1.RestoreItemAction, error) @@ -218,6 +226,44 @@ func (m *manager) GetBackupItemAction(name string) (biav1.BackupItemAction, erro return nil, fmt.Errorf("unable to get valid BackupItemAction for %q", name) } +// GetBackupItemActionsV2 returns all v2 backup item actions as RestartableBackupItemActions. +func (m *manager) GetBackupItemActionsV2() ([]biav2.BackupItemAction, error) { + list := m.registry.List(common.PluginKindBackupItemActionV2) + + actions := make([]biav2.BackupItemAction, 0, len(list)) + + for i := range list { + id := list[i] + + r, err := m.GetBackupItemActionV2(id.Name) + if err != nil { + return nil, err + } + + actions = append(actions, r) + } + + return actions, nil +} + +// GetBackupItemActionV2 returns a v2 restartableBackupItemAction for name. +func (m *manager) GetBackupItemActionV2(name string) (biav2.BackupItemAction, error) { + name = sanitizeName(name) + + for _, adaptedBackupItemAction := range biav2cli.AdaptedBackupItemActions() { + restartableProcess, err := m.getRestartableProcess(adaptedBackupItemAction.Kind, name) + // Check if plugin was not found + if errors.As(err, &pluginNotFoundErrType) { + continue + } + if err != nil { + return nil, err + } + return adaptedBackupItemAction.GetRestartable(name, restartableProcess), nil + } + return nil, fmt.Errorf("unable to get valid BackupItemActionV2 for %q", name) +} + // GetRestoreItemActions returns all restore item actions as restartableRestoreItemActions. func (m *manager) GetRestoreItemActions() ([]riav1.RestoreItemAction, error) { list := m.registry.List(common.PluginKindRestoreItemAction) diff --git a/pkg/plugin/clientmgmt/manager_test.go b/pkg/plugin/clientmgmt/manager_test.go index 17b51a52b20..541613c73b2 100644 --- a/pkg/plugin/clientmgmt/manager_test.go +++ b/pkg/plugin/clientmgmt/manager_test.go @@ -28,6 +28,7 @@ import ( "github.com/vmware-tanzu/velero/internal/restartabletest" biav1cli "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/backupitemaction/v1" + 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" vsv1cli "github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt/volumesnapshotter/v1" @@ -203,6 +204,23 @@ func TestGetBackupItemAction(t *testing.T) { ) } +func TestGetBackupItemActionV2(t *testing.T) { + getPluginTest(t, + common.PluginKindBackupItemActionV2, + "velero.io/pod", + func(m Manager, name string) (interface{}, error) { + return m.GetBackupItemActionV2(name) + }, + func(name string, sharedPluginProcess process.RestartableProcess) interface{} { + return &biav2cli.RestartableBackupItemAction{ + Key: process.KindAndName{Kind: common.PluginKindBackupItemActionV2, Name: name}, + SharedPluginProcess: sharedPluginProcess, + } + }, + false, + ) +} + func TestGetRestoreItemAction(t *testing.T) { getPluginTest(t, common.PluginKindRestoreItemAction, @@ -363,6 +381,98 @@ func TestGetBackupItemActions(t *testing.T) { } } +func TestGetBackupItemActionsV2(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.PluginKindBackupItemActionV2 + 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 := &biav2cli.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.GetBackupItemActionsV2() + 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 diff --git a/pkg/plugin/clientmgmt/process/client_builder.go b/pkg/plugin/clientmgmt/process/client_builder.go index 4b6c27731ff..0c5810d6228 100644 --- a/pkg/plugin/clientmgmt/process/client_builder.go +++ b/pkg/plugin/clientmgmt/process/client_builder.go @@ -27,6 +27,7 @@ import ( "github.com/vmware-tanzu/velero/pkg/features" "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" ) @@ -68,13 +69,14 @@ 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.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.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...), diff --git a/pkg/plugin/clientmgmt/process/client_builder_test.go b/pkg/plugin/clientmgmt/process/client_builder_test.go index 2a3f6df6153..c70cf29bfa2 100644 --- a/pkg/plugin/clientmgmt/process/client_builder_test.go +++ b/pkg/plugin/clientmgmt/process/client_builder_test.go @@ -27,6 +27,7 @@ import ( "github.com/vmware-tanzu/velero/pkg/features" "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" "github.com/vmware-tanzu/velero/pkg/test" ) @@ -61,13 +62,14 @@ 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.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.PluginKindDeleteItemAction): framework.NewDeleteItemActionPlugin(common.ClientLogger(logger)), + string(common.PluginKindItemSnapshotter): framework.NewItemSnapshotterPlugin(common.ClientLogger(logger)), }, Logger: cb.pluginLogger, Cmd: exec.Command(cb.commandName, cb.commandArgs...), diff --git a/pkg/plugin/framework/action_resolver.go b/pkg/plugin/framework/action_resolver.go index 5664d08c2c1..f9ad62ab5d0 100644 --- a/pkg/plugin/framework/action_resolver.go +++ b/pkg/plugin/framework/action_resolver.go @@ -17,6 +17,7 @@ limitations under the License. package framework import ( + "github.com/pkg/errors" "github.com/sirupsen/logrus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" @@ -25,6 +26,7 @@ import ( "github.com/vmware-tanzu/velero/pkg/discovery" "github.com/vmware-tanzu/velero/pkg/plugin/velero" biav1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v1" + 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" "github.com/vmware-tanzu/velero/pkg/util/collections" @@ -109,6 +111,17 @@ func NewBackupItemActionResolver(actions []biav1.BackupItemAction) BackupItemAct } } +type BackupItemResolvedActionV2 struct { + biav2.BackupItemAction + resolvedAction +} + +func NewBackupItemActionResolverV2(actions []biav2.BackupItemAction) BackupItemActionResolverV2 { + return BackupItemActionResolverV2{ + actions: actions, + } +} + func NewRestoreItemActionResolver(actions []riav1.RestoreItemAction) RestoreItemActionResolver { return RestoreItemActionResolver{ actions: actions, @@ -155,6 +168,32 @@ func (recv BackupItemActionResolver) ResolveActions(helper discovery.Helper, log return resolved, nil } +type BackupItemActionResolverV2 struct { + actions []biav2.BackupItemAction +} + +func (recv BackupItemActionResolverV2) ResolveActions(helper discovery.Helper, log logrus.FieldLogger) ([]BackupItemResolvedActionV2, error) { + var resolved []BackupItemResolvedActionV2 + for _, action := range recv.actions { + log.Debugf("resolving BackupItemAction for: %v", action) + resources, namespaces, selector, err := resolveAction(helper, action) + if err != nil { + log.WithError(errors.WithStack(err)).Debugf("resolveAction error, action: %v", action) + return nil, err + } + res := BackupItemResolvedActionV2{ + BackupItemAction: action, + resolvedAction: resolvedAction{ + ResourceIncludesExcludes: resources, + NamespaceIncludesExcludes: namespaces, + Selector: selector, + }, + } + resolved = append(resolved, res) + } + return resolved, nil +} + type RestoreItemResolvedAction struct { riav1.RestoreItemAction resolvedAction diff --git a/pkg/plugin/framework/backupitemaction/v2/backup_item_action.go b/pkg/plugin/framework/backupitemaction/v2/backup_item_action.go new file mode 100644 index 00000000000..a4efa9a2e1f --- /dev/null +++ b/pkg/plugin/framework/backupitemaction/v2/backup_item_action.go @@ -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" + protobiav2 "github.com/vmware-tanzu/velero/pkg/plugin/generated/backupitemaction/v2" +) + +// BackupItemActionPlugin is an implementation of go-plugin's Plugin +// interface with support for gRPC for the backup/ItemAction +// interface. +type BackupItemActionPlugin struct { + plugin.NetRPCUnsupportedPlugin + *common.PluginBase +} + +// GRPCClient returns a clientDispenser for BackupItemAction gRPC clients. +func (p *BackupItemActionPlugin) GRPCClient(_ context.Context, _ *plugin.GRPCBroker, clientConn *grpc.ClientConn) (interface{}, error) { + return common.NewClientDispenser(p.ClientLogger, clientConn, newBackupItemActionGRPCClient), nil +} + +// GRPCServer registers a BackupItemAction gRPC server. +func (p *BackupItemActionPlugin) GRPCServer(_ *plugin.GRPCBroker, server *grpc.Server) error { + protobiav2.RegisterBackupItemActionServer(server, &BackupItemActionGRPCServer{mux: p.ServerMux}) + return nil +} diff --git a/pkg/plugin/framework/backupitemaction/v2/backup_item_action_client.go b/pkg/plugin/framework/backupitemaction/v2/backup_item_action_client.go new file mode 100644 index 00000000000..66b7a379f73 --- /dev/null +++ b/pkg/plugin/framework/backupitemaction/v2/backup_item_action_client.go @@ -0,0 +1,167 @@ +/* +Copyright 2017, 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 ( + "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" + "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" + protobiav2 "github.com/vmware-tanzu/velero/pkg/plugin/generated/backupitemaction/v2" + "github.com/vmware-tanzu/velero/pkg/plugin/velero" +) + +// NewBackupItemActionPlugin constructs a BackupItemActionPlugin. +func NewBackupItemActionPlugin(options ...common.PluginOption) *BackupItemActionPlugin { + return &BackupItemActionPlugin{ + PluginBase: common.NewPluginBase(options...), + } +} + +// BackupItemActionGRPCClient implements the backup/ItemAction interface and uses a +// gRPC client to make calls to the plugin server. +type BackupItemActionGRPCClient struct { + *common.ClientBase + grpcClient protobiav2.BackupItemActionClient +} + +func newBackupItemActionGRPCClient(base *common.ClientBase, clientConn *grpc.ClientConn) interface{} { + return &BackupItemActionGRPCClient{ + ClientBase: base, + grpcClient: protobiav2.NewBackupItemActionClient(clientConn), + } +} + +func (c *BackupItemActionGRPCClient) AppliesTo() (velero.ResourceSelector, error) { + req := &protobiav2.BackupItemActionAppliesToRequest{ + Plugin: c.Plugin, + } + + res, err := c.grpcClient.AppliesTo(context.Background(), req) + 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 *BackupItemActionGRPCClient) Execute(item runtime.Unstructured, backup *api.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, error) { + itemJSON, err := json.Marshal(item.UnstructuredContent()) + if err != nil { + return nil, nil, errors.WithStack(err) + } + + backupJSON, err := json.Marshal(backup) + if err != nil { + return nil, nil, errors.WithStack(err) + } + + req := &protobiav2.ExecuteRequest{ + Plugin: c.Plugin, + Item: itemJSON, + Backup: backupJSON, + } + + res, err := c.grpcClient.Execute(context.Background(), req) + if err != nil { + return nil, nil, common.FromGRPCError(err) + } + + var updatedItem unstructured.Unstructured + if err := json.Unmarshal(res.Item, &updatedItem); err != nil { + return nil, 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 &updatedItem, additionalItems, nil +} + +func (c *BackupItemActionGRPCClient) Progress(operationID string, backup *api.Backup) (velero.OperationProgress, error) { + backupJSON, err := json.Marshal(backup) + if err != nil { + return velero.OperationProgress{}, errors.WithStack(err) + } + req := &protobiav2.BackupItemActionProgressRequest{ + Plugin: c.Plugin, + OperationID: operationID, + Backup: backupJSON, + } + + 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, + ItemsCompleted: res.Progress.ItemsCompleted, + ItemsToComplete: res.Progress.ItemsToComplete, + Started: res.Progress.Started.AsTime(), + Updated: res.Progress.Updated.AsTime(), + }, nil +} + +func (c *BackupItemActionGRPCClient) Cancel(operationID string, backup *api.Backup) error { + backupJSON, err := json.Marshal(backup) + if err != nil { + return errors.WithStack(err) + } + req := &protobiav2.BackupItemActionCancelRequest{ + Plugin: c.Plugin, + OperationID: operationID, + Backup: backupJSON, + } + + _, err = c.grpcClient.Cancel(context.Background(), req) + if err != nil { + return common.FromGRPCError(err) + } + + return nil +} diff --git a/pkg/plugin/framework/backupitemaction/v2/backup_item_action_server.go b/pkg/plugin/framework/backupitemaction/v2/backup_item_action_server.go new file mode 100644 index 00000000000..43f4a21cfad --- /dev/null +++ b/pkg/plugin/framework/backupitemaction/v2/backup_item_action_server.go @@ -0,0 +1,210 @@ +/* +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/emptypb" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + + 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" + protobiav2 "github.com/vmware-tanzu/velero/pkg/plugin/generated/backupitemaction/v2" + "github.com/vmware-tanzu/velero/pkg/plugin/velero" + biav2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v2" +) + +// BackupItemActionGRPCServer implements the proto-generated BackupItemAction interface, and accepts +// gRPC calls and forwards them to an implementation of the pluggable interface. +type BackupItemActionGRPCServer struct { + mux *common.ServerMux +} + +func (s *BackupItemActionGRPCServer) getImpl(name string) (biav2.BackupItemAction, error) { + impl, err := s.mux.GetHandler(name) + if err != nil { + return nil, err + } + + itemAction, ok := impl.(biav2.BackupItemAction) + if !ok { + return nil, errors.Errorf("%T is not a backup item action", impl) + } + + return itemAction, nil +} + +func (s *BackupItemActionGRPCServer) AppliesTo( + ctx context.Context, req *protobiav2.BackupItemActionAppliesToRequest) ( + response *protobiav2.BackupItemActionAppliesToResponse, 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 &protobiav2.BackupItemActionAppliesToResponse{ + ResourceSelector: &proto.ResourceSelector{ + IncludedNamespaces: resourceSelector.IncludedNamespaces, + ExcludedNamespaces: resourceSelector.ExcludedNamespaces, + IncludedResources: resourceSelector.IncludedResources, + ExcludedResources: resourceSelector.ExcludedResources, + Selector: resourceSelector.LabelSelector, + }, + }, nil +} + +func (s *BackupItemActionGRPCServer) Execute( + ctx context.Context, req *protobiav2.ExecuteRequest) (response *protobiav2.ExecuteResponse, 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 + var backup api.Backup + + if err := json.Unmarshal(req.Item, &item); err != nil { + return nil, common.NewGRPCError(errors.WithStack(err)) + } + if err := json.Unmarshal(req.Backup, &backup); err != nil { + return nil, common.NewGRPCError(errors.WithStack(err)) + } + + updatedItem, additionalItems, operationID, err := impl.Execute(&item, &backup) + if err != nil { + return nil, common.NewGRPCError(err) + } + + // If the plugin implementation returned a nil updatedItem (meaning no modifications), reset updatedItem to the + // original item. + var updatedItemJSON []byte + if updatedItem == nil { + updatedItemJSON = req.Item + } else { + updatedItemJSON, err = json.Marshal(updatedItem.UnstructuredContent()) + if err != nil { + return nil, common.NewGRPCError(errors.WithStack(err)) + } + } + + res := &protobiav2.ExecuteResponse{ + Item: updatedItemJSON, + OperationID: operationID, + } + + for _, item := range additionalItems { + res.AdditionalItems = append(res.AdditionalItems, backupResourceIdentifierToProto(item)) + } + + return res, nil +} + +func (s *BackupItemActionGRPCServer) Progress( + ctx context.Context, req *protobiav2.BackupItemActionProgressRequest) ( + response *protobiav2.BackupItemActionProgressResponse, 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 backup api.Backup + if err := json.Unmarshal(req.Backup, &backup); err != nil { + return nil, common.NewGRPCError(errors.WithStack(err)) + } + + progress, err := impl.Progress(req.OperationID, &backup) + if err != nil { + return nil, common.NewGRPCError(err) + } + + res := &protobiav2.BackupItemActionProgressResponse{ + Progress: &proto.OperationProgress{ + Completed: progress.Completed, + Err: progress.Err, + ItemsCompleted: progress.ItemsCompleted, + ItemsToComplete: progress.ItemsToComplete, + Started: timestamppb.New(progress.Started), + Updated: timestamppb.New(progress.Updated), + }, + } + return res, nil +} + +func (s *BackupItemActionGRPCServer) Cancel( + ctx context.Context, req *protobiav2.BackupItemActionCancelRequest) ( + 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 backup api.Backup + if err := json.Unmarshal(req.Backup, &backup); err != nil { + return nil, common.NewGRPCError(errors.WithStack(err)) + } + + err = impl.Cancel(req.OperationID, &backup) + if err != nil { + return nil, common.NewGRPCError(err) + } + + return &emptypb.Empty{}, nil +} + +func backupResourceIdentifierToProto(id velero.ResourceIdentifier) *proto.ResourceIdentifier { + return &proto.ResourceIdentifier{ + Group: id.Group, + Resource: id.Resource, + Namespace: id.Namespace, + Name: id.Name, + } +} diff --git a/pkg/plugin/framework/backupitemaction/v2/backup_item_action_test.go b/pkg/plugin/framework/backupitemaction/v2/backup_item_action_test.go new file mode 100644 index 00000000000..d9e2d2cea1d --- /dev/null +++ b/pkg/plugin/framework/backupitemaction/v2/backup_item_action_test.go @@ -0,0 +1,202 @@ +/* +Copyright 2018 the Velero contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v2 + +import ( + "encoding/json" + "testing" + + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/net/context" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + + v1 "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" + protobiav2 "github.com/vmware-tanzu/velero/pkg/plugin/generated/backupitemaction/v2" + "github.com/vmware-tanzu/velero/pkg/plugin/velero" + mocks "github.com/vmware-tanzu/velero/pkg/plugin/velero/mocks/backupitemaction/v2" + velerotest "github.com/vmware-tanzu/velero/pkg/test" +) + +func TestBackupItemActionGRPCServerExecute(t *testing.T) { + invalidItem := []byte("this is gibberish json") + validItem := []byte(` + { + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "namespace": "myns", + "name": "myconfigmap" + }, + "data": { + "key": "value" + } + }`) + var validItemObject unstructured.Unstructured + err := json.Unmarshal(validItem, &validItemObject) + require.NoError(t, err) + + updatedItem := []byte(` + { + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": { + "namespace": "myns", + "name": "myconfigmap" + }, + "data": { + "key": "changed!" + } + }`) + var updatedItemObject unstructured.Unstructured + err = json.Unmarshal(updatedItem, &updatedItemObject) + require.NoError(t, err) + + invalidBackup := []byte("this is gibberish json") + validBackup := []byte(` + { + "apiVersion": "velero.io/v1", + "kind": "Backup", + "metadata": { + "namespace": "myns", + "name": "mybackup" + }, + "spec": { + "includedNamespaces": ["*"], + "includedResources": ["*"], + "ttl": "60m" + } + }`) + var validBackupObject v1.Backup + err = json.Unmarshal(validBackup, &validBackupObject) + require.NoError(t, err) + + tests := []struct { + name string + backup []byte + item []byte + implUpdatedItem runtime.Unstructured + implAdditionalItems []velero.ResourceIdentifier + implOperationID string + implError error + expectError bool + skipMock bool + }{ + { + name: "error unmarshaling item", + item: invalidItem, + backup: validBackup, + expectError: true, + skipMock: true, + }, + { + name: "error unmarshaling backup", + item: validItem, + backup: invalidBackup, + expectError: true, + skipMock: true, + }, + { + name: "error running impl", + item: validItem, + backup: validBackup, + implError: errors.New("impl error"), + expectError: true, + }, + { + name: "nil updatedItem / no additionalItems", + item: validItem, + backup: validBackup, + }, + { + name: "same updatedItem / some additionalItems", + item: validItem, + backup: validBackup, + implUpdatedItem: &validItemObject, + implAdditionalItems: []velero.ResourceIdentifier{ + { + GroupResource: schema.GroupResource{Group: "v1", Resource: "pods"}, + Namespace: "myns", + Name: "mypod", + }, + }, + }, + { + name: "different updatedItem", + item: validItem, + backup: validBackup, + implUpdatedItem: &updatedItemObject, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + itemAction := &mocks.BackupItemAction{} + defer itemAction.AssertExpectations(t) + + if !test.skipMock { + itemAction.On("Execute", &validItemObject, &validBackupObject).Return(test.implUpdatedItem, test.implAdditionalItems, test.implOperationID, test.implError) + } + + s := &BackupItemActionGRPCServer{mux: &common.ServerMux{ + ServerLog: velerotest.NewLogger(), + Handlers: map[string]interface{}{ + "xyz": itemAction, + }, + }} + + req := &protobiav2.ExecuteRequest{ + Plugin: "xyz", + Item: test.item, + Backup: test.backup, + } + + resp, err := s.Execute(context.Background(), req) + + // Verify error + assert.Equal(t, test.expectError, err != nil) + if err != nil { + return + } + require.NotNil(t, resp) + + // Verify updated item + updatedItem := test.implUpdatedItem + if updatedItem == nil { + // If the impl returned nil for its updatedItem, we should expect the plugin to return the original item + updatedItem = &validItemObject + } + + var respItem unstructured.Unstructured + err = json.Unmarshal(resp.Item, &respItem) + require.NoError(t, err) + + assert.Equal(t, updatedItem, &respItem) + + // Verify additional items + var expectedAdditionalItems []*proto.ResourceIdentifier + for _, item := range test.implAdditionalItems { + expectedAdditionalItems = append(expectedAdditionalItems, backupResourceIdentifierToProto(item)) + } + assert.Equal(t, expectedAdditionalItems, resp.AdditionalItems) + }) + } +} diff --git a/pkg/plugin/framework/common/plugin_kinds.go b/pkg/plugin/framework/common/plugin_kinds.go index 2e0119e2fd4..c6e25e5b6cd 100644 --- a/pkg/plugin/framework/common/plugin_kinds.go +++ b/pkg/plugin/framework/common/plugin_kinds.go @@ -35,6 +35,9 @@ const ( // PluginKindBackupItemAction represents a backup item action plugin. PluginKindBackupItemAction PluginKind = "BackupItemAction" + // PluginKindBackupItemActionV2 represents a v2 backup item action plugin. + PluginKindBackupItemActionV2 PluginKind = "BackupItemActionV2" + // PluginKindRestoreItemAction represents a restore item action plugin. PluginKindRestoreItemAction PluginKind = "RestoreItemAction" @@ -51,7 +54,9 @@ const ( // If there are plugin kinds that are adaptable to newer API versions, list them here. // 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{} +var PluginKindsAdaptableTo = map[PluginKind][]PluginKind{ + PluginKindBackupItemAction: {PluginKindBackupItemActionV2}, +} // AllPluginKinds contains all the valid plugin kinds that Velero supports, excluding PluginLister because that is not a // kind that a developer would ever need to implement (it's handled by Velero and the Velero plugin library code). @@ -60,6 +65,7 @@ func AllPluginKinds() map[string]PluginKind { allPluginKinds[PluginKindObjectStore.String()] = PluginKindObjectStore allPluginKinds[PluginKindVolumeSnapshotter.String()] = PluginKindVolumeSnapshotter allPluginKinds[PluginKindBackupItemAction.String()] = PluginKindBackupItemAction + allPluginKinds[PluginKindBackupItemActionV2.String()] = PluginKindBackupItemActionV2 allPluginKinds[PluginKindRestoreItemAction.String()] = PluginKindRestoreItemAction allPluginKinds[PluginKindDeleteItemAction.String()] = PluginKindDeleteItemAction allPluginKinds[PluginKindItemSnapshotter.String()] = PluginKindItemSnapshotter diff --git a/pkg/plugin/framework/server.go b/pkg/plugin/framework/server.go index 20c5e48e00f..d9a1af38723 100644 --- a/pkg/plugin/framework/server.go +++ b/pkg/plugin/framework/server.go @@ -25,6 +25,7 @@ import ( "github.com/sirupsen/logrus" "github.com/spf13/pflag" + biav2 "github.com/vmware-tanzu/velero/pkg/plugin/framework/backupitemaction/v2" "github.com/vmware-tanzu/velero/pkg/plugin/framework/common" "github.com/vmware-tanzu/velero/pkg/util/logging" ) @@ -46,6 +47,13 @@ type Server interface { // RegisterBackupItemActions registers multiple backup item actions. RegisterBackupItemActions(map[string]common.HandlerInitializer) Server + // RegisterBackupItemActionV2 registers a v2 backup item action. Accepted format + // for the plugin name is /. + RegisterBackupItemActionV2(pluginName string, initializer common.HandlerInitializer) Server + + // RegisterBackupItemActionsV2 registers multiple v2 backup item actions. + RegisterBackupItemActionsV2(map[string]common.HandlerInitializer) Server + // RegisterVolumeSnapshotter registers a volume snapshotter. Accepted format // for the plugin name is /. RegisterVolumeSnapshotter(pluginName string, initializer common.HandlerInitializer) Server @@ -85,15 +93,16 @@ type Server interface { // server implements Server. type server struct { - log *logrus.Logger - logLevelFlag *logging.LevelFlag - flagSet *pflag.FlagSet - backupItemAction *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 + deleteItemAction *DeleteItemActionPlugin + itemSnapshotter *ItemSnapshotterPlugin } // NewServer returns a new Server @@ -101,14 +110,15 @@ func NewServer() Server { log := newLogger() return &server{ - log: log, - logLevelFlag: logging.LogLevelFlag(log.Level), - backupItemAction: 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)), + deleteItemAction: NewDeleteItemActionPlugin(common.ServerLogger(log)), + itemSnapshotter: NewItemSnapshotterPlugin(common.ServerLogger(log)), } } @@ -132,6 +142,18 @@ func (s *server) RegisterBackupItemActions(m map[string]common.HandlerInitialize return s } +func (s *server) RegisterBackupItemActionV2(name string, initializer common.HandlerInitializer) Server { + s.backupItemActionV2.Register(name, initializer) + return s +} + +func (s *server) RegisterBackupItemActionsV2(m map[string]common.HandlerInitializer) Server { + for name := range m { + s.RegisterBackupItemActionV2(name, m[name]) + } + return s +} + func (s *server) RegisterVolumeSnapshotter(name string, initializer common.HandlerInitializer) Server { s.volumeSnapshotter.Register(name, initializer) return s @@ -216,6 +238,7 @@ func (s *server) Serve() { var pluginIdentifiers []PluginIdentifier pluginIdentifiers = append(pluginIdentifiers, getNames(command, common.PluginKindBackupItemAction, s.backupItemAction)...) + pluginIdentifiers = append(pluginIdentifiers, getNames(command, common.PluginKindBackupItemActionV2, s.backupItemActionV2)...) 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)...) @@ -227,13 +250,14 @@ func (s *server) Serve() { plugin.Serve(&plugin.ServeConfig{ HandshakeConfig: Handshake(), Plugins: map[string]plugin.Plugin{ - string(common.PluginKindBackupItemAction): s.backupItemAction, - 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.PluginKindDeleteItemAction): s.deleteItemAction, + string(common.PluginKindItemSnapshotter): s.itemSnapshotter, }, GRPCServer: plugin.DefaultGRPCServer, }) diff --git a/pkg/plugin/generated/Shared.pb.go b/pkg/plugin/generated/Shared.pb.go index 1c84177dc83..752c3a6d066 100644 --- a/pkg/plugin/generated/Shared.pb.go +++ b/pkg/plugin/generated/Shared.pb.go @@ -10,6 +10,7 @@ import ( proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" ) @@ -323,47 +324,152 @@ func (x *ResourceSelector) GetSelector() string { return "" } +type OperationProgress struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Completed bool `protobuf:"varint,1,opt,name=completed,proto3" json:"completed,omitempty"` + Err string `protobuf:"bytes,2,opt,name=err,proto3" json:"err,omitempty"` + ItemsCompleted int64 `protobuf:"varint,3,opt,name=itemsCompleted,proto3" json:"itemsCompleted,omitempty"` + ItemsToComplete int64 `protobuf:"varint,4,opt,name=itemsToComplete,proto3" json:"itemsToComplete,omitempty"` + Started *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=started,proto3" json:"started,omitempty"` + Updated *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=updated,proto3" json:"updated,omitempty"` +} + +func (x *OperationProgress) Reset() { + *x = OperationProgress{} + if protoimpl.UnsafeEnabled { + mi := &file_Shared_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OperationProgress) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OperationProgress) ProtoMessage() {} + +func (x *OperationProgress) ProtoReflect() protoreflect.Message { + mi := &file_Shared_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OperationProgress.ProtoReflect.Descriptor instead. +func (*OperationProgress) Descriptor() ([]byte, []int) { + return file_Shared_proto_rawDescGZIP(), []int{5} +} + +func (x *OperationProgress) GetCompleted() bool { + if x != nil { + return x.Completed + } + return false +} + +func (x *OperationProgress) GetErr() string { + if x != nil { + return x.Err + } + return "" +} + +func (x *OperationProgress) GetItemsCompleted() int64 { + if x != nil { + return x.ItemsCompleted + } + return 0 +} + +func (x *OperationProgress) GetItemsToComplete() int64 { + if x != nil { + return x.ItemsToComplete + } + return 0 +} + +func (x *OperationProgress) GetStarted() *timestamppb.Timestamp { + if x != nil { + return x.Started + } + return nil +} + +func (x *OperationProgress) GetUpdated() *timestamppb.Timestamp { + if x != nil { + return x.Updated + } + return nil +} + var File_Shared_proto protoreflect.FileDescriptor var file_Shared_proto_rawDesc = []byte{ 0x0a, 0x0c, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x09, - 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x22, 0x36, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x12, 0x2d, 0x0a, 0x06, 0x66, - 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x67, 0x65, - 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2e, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x46, 0x72, 0x61, - 0x6d, 0x65, 0x52, 0x06, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x50, 0x0a, 0x0a, 0x53, 0x74, - 0x61, 0x63, 0x6b, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, - 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x65, - 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x78, 0x0a, 0x12, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, - 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0xea, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x2e, 0x0a, 0x12, 0x69, - 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, - 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x12, 0x2e, 0x0a, 0x12, 0x65, - 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, - 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x12, 0x2c, 0x0a, 0x11, 0x69, - 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, - 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x2c, 0x0a, 0x11, 0x65, 0x78, 0x63, - 0x6c, 0x75, 0x64, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x04, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, - 0x74, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, - 0x74, 0x6f, 0x72, 0x42, 0x35, 0x5a, 0x33, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x76, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x2d, 0x74, 0x61, 0x6e, 0x7a, 0x75, 0x2f, 0x76, - 0x65, 0x6c, 0x65, 0x72, 0x6f, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, - 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x22, 0x36, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x12, 0x2d, 0x0a, 0x06, + 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x67, + 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2e, 0x53, 0x74, 0x61, 0x63, 0x6b, 0x46, 0x72, + 0x61, 0x6d, 0x65, 0x52, 0x06, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x50, 0x0a, 0x0a, 0x53, + 0x74, 0x61, 0x63, 0x6b, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x12, 0x0a, + 0x04, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x6c, 0x69, 0x6e, + 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x78, 0x0a, + 0x12, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, + 0x69, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0xea, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x2e, 0x0a, 0x12, + 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, + 0x65, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x12, 0x2e, 0x0a, 0x12, + 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, + 0x65, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x12, 0x2c, 0x0a, 0x11, + 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, + 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x2c, 0x0a, 0x11, 0x65, 0x78, + 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, + 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x6c, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x65, 0x6c, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x22, 0x81, 0x02, 0x0a, 0x11, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, + 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, + 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x72, 0x72, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x65, 0x72, 0x72, 0x12, 0x26, 0x0a, 0x0e, 0x69, 0x74, + 0x65, 0x6d, 0x73, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0e, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, + 0x65, 0x64, 0x12, 0x28, 0x0a, 0x0f, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x54, 0x6f, 0x43, 0x6f, 0x6d, + 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x69, 0x74, 0x65, + 0x6d, 0x73, 0x54, 0x6f, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x34, 0x0a, 0x07, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x73, 0x74, 0x61, 0x72, 0x74, + 0x65, 0x64, 0x12, 0x34, 0x0a, 0x07, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, + 0x07, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x42, 0x35, 0x5a, 0x33, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x2d, 0x74, 0x61, + 0x6e, 0x7a, 0x75, 0x2f, 0x76, 0x65, 0x6c, 0x65, 0x72, 0x6f, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, + 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -378,21 +484,25 @@ func file_Shared_proto_rawDescGZIP() []byte { return file_Shared_proto_rawDescData } -var file_Shared_proto_msgTypes = make([]protoimpl.MessageInfo, 5) +var file_Shared_proto_msgTypes = make([]protoimpl.MessageInfo, 6) var file_Shared_proto_goTypes = []interface{}{ - (*Empty)(nil), // 0: generated.Empty - (*Stack)(nil), // 1: generated.Stack - (*StackFrame)(nil), // 2: generated.StackFrame - (*ResourceIdentifier)(nil), // 3: generated.ResourceIdentifier - (*ResourceSelector)(nil), // 4: generated.ResourceSelector + (*Empty)(nil), // 0: generated.Empty + (*Stack)(nil), // 1: generated.Stack + (*StackFrame)(nil), // 2: generated.StackFrame + (*ResourceIdentifier)(nil), // 3: generated.ResourceIdentifier + (*ResourceSelector)(nil), // 4: generated.ResourceSelector + (*OperationProgress)(nil), // 5: generated.OperationProgress + (*timestamppb.Timestamp)(nil), // 6: google.protobuf.Timestamp } var file_Shared_proto_depIdxs = []int32{ 2, // 0: generated.Stack.frames:type_name -> generated.StackFrame - 1, // [1:1] is the sub-list for method output_type - 1, // [1:1] is the sub-list for method input_type - 1, // [1:1] is the sub-list for extension type_name - 1, // [1:1] is the sub-list for extension extendee - 0, // [0:1] is the sub-list for field type_name + 6, // 1: generated.OperationProgress.started:type_name -> google.protobuf.Timestamp + 6, // 2: generated.OperationProgress.updated:type_name -> google.protobuf.Timestamp + 3, // [3:3] is the sub-list for method output_type + 3, // [3:3] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name } func init() { file_Shared_proto_init() } @@ -461,6 +571,18 @@ func file_Shared_proto_init() { return nil } } + file_Shared_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OperationProgress); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -468,7 +590,7 @@ func file_Shared_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_Shared_proto_rawDesc, NumEnums: 0, - NumMessages: 5, + NumMessages: 6, NumExtensions: 0, NumServices: 0, }, diff --git a/pkg/plugin/generated/backupitemaction/v2/BackupItemAction.pb.go b/pkg/plugin/generated/backupitemaction/v2/BackupItemAction.pb.go new file mode 100644 index 00000000000..bbcaa50e6de --- /dev/null +++ b/pkg/plugin/generated/backupitemaction/v2/BackupItemAction.pb.go @@ -0,0 +1,851 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.23.0 +// protoc v3.14.0 +// source: backupitemaction/v2/BackupItemAction.proto + +package v2 + +import ( + context "context" + proto "github.com/golang/protobuf/proto" + generated "github.com/vmware-tanzu/velero/pkg/plugin/generated" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type ExecuteRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Plugin string `protobuf:"bytes,1,opt,name=plugin,proto3" json:"plugin,omitempty"` + Item []byte `protobuf:"bytes,2,opt,name=item,proto3" json:"item,omitempty"` + Backup []byte `protobuf:"bytes,3,opt,name=backup,proto3" json:"backup,omitempty"` +} + +func (x *ExecuteRequest) Reset() { + *x = ExecuteRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ExecuteRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExecuteRequest) ProtoMessage() {} + +func (x *ExecuteRequest) ProtoReflect() protoreflect.Message { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExecuteRequest.ProtoReflect.Descriptor instead. +func (*ExecuteRequest) Descriptor() ([]byte, []int) { + return file_backupitemaction_v2_BackupItemAction_proto_rawDescGZIP(), []int{0} +} + +func (x *ExecuteRequest) GetPlugin() string { + if x != nil { + return x.Plugin + } + return "" +} + +func (x *ExecuteRequest) GetItem() []byte { + if x != nil { + return x.Item + } + return nil +} + +func (x *ExecuteRequest) GetBackup() []byte { + if x != nil { + return x.Backup + } + return nil +} + +type ExecuteResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Item []byte `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` + AdditionalItems []*generated.ResourceIdentifier `protobuf:"bytes,2,rep,name=additionalItems,proto3" json:"additionalItems,omitempty"` + OperationID string `protobuf:"bytes,3,opt,name=operationID,proto3" json:"operationID,omitempty"` +} + +func (x *ExecuteResponse) Reset() { + *x = ExecuteResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ExecuteResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExecuteResponse) ProtoMessage() {} + +func (x *ExecuteResponse) ProtoReflect() protoreflect.Message { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExecuteResponse.ProtoReflect.Descriptor instead. +func (*ExecuteResponse) Descriptor() ([]byte, []int) { + return file_backupitemaction_v2_BackupItemAction_proto_rawDescGZIP(), []int{1} +} + +func (x *ExecuteResponse) GetItem() []byte { + if x != nil { + return x.Item + } + return nil +} + +func (x *ExecuteResponse) GetAdditionalItems() []*generated.ResourceIdentifier { + if x != nil { + return x.AdditionalItems + } + return nil +} + +func (x *ExecuteResponse) GetOperationID() string { + if x != nil { + return x.OperationID + } + return "" +} + +type BackupItemActionAppliesToRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Plugin string `protobuf:"bytes,1,opt,name=plugin,proto3" json:"plugin,omitempty"` +} + +func (x *BackupItemActionAppliesToRequest) Reset() { + *x = BackupItemActionAppliesToRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BackupItemActionAppliesToRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BackupItemActionAppliesToRequest) ProtoMessage() {} + +func (x *BackupItemActionAppliesToRequest) ProtoReflect() protoreflect.Message { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BackupItemActionAppliesToRequest.ProtoReflect.Descriptor instead. +func (*BackupItemActionAppliesToRequest) Descriptor() ([]byte, []int) { + return file_backupitemaction_v2_BackupItemAction_proto_rawDescGZIP(), []int{2} +} + +func (x *BackupItemActionAppliesToRequest) GetPlugin() string { + if x != nil { + return x.Plugin + } + return "" +} + +type BackupItemActionAppliesToResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ResourceSelector *generated.ResourceSelector `protobuf:"bytes,1,opt,name=ResourceSelector,proto3" json:"ResourceSelector,omitempty"` +} + +func (x *BackupItemActionAppliesToResponse) Reset() { + *x = BackupItemActionAppliesToResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BackupItemActionAppliesToResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BackupItemActionAppliesToResponse) ProtoMessage() {} + +func (x *BackupItemActionAppliesToResponse) ProtoReflect() protoreflect.Message { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BackupItemActionAppliesToResponse.ProtoReflect.Descriptor instead. +func (*BackupItemActionAppliesToResponse) Descriptor() ([]byte, []int) { + return file_backupitemaction_v2_BackupItemAction_proto_rawDescGZIP(), []int{3} +} + +func (x *BackupItemActionAppliesToResponse) GetResourceSelector() *generated.ResourceSelector { + if x != nil { + return x.ResourceSelector + } + return nil +} + +type BackupItemActionProgressRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Plugin string `protobuf:"bytes,1,opt,name=plugin,proto3" json:"plugin,omitempty"` + OperationID string `protobuf:"bytes,2,opt,name=operationID,proto3" json:"operationID,omitempty"` + Backup []byte `protobuf:"bytes,3,opt,name=backup,proto3" json:"backup,omitempty"` +} + +func (x *BackupItemActionProgressRequest) Reset() { + *x = BackupItemActionProgressRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BackupItemActionProgressRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BackupItemActionProgressRequest) ProtoMessage() {} + +func (x *BackupItemActionProgressRequest) ProtoReflect() protoreflect.Message { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BackupItemActionProgressRequest.ProtoReflect.Descriptor instead. +func (*BackupItemActionProgressRequest) Descriptor() ([]byte, []int) { + return file_backupitemaction_v2_BackupItemAction_proto_rawDescGZIP(), []int{4} +} + +func (x *BackupItemActionProgressRequest) GetPlugin() string { + if x != nil { + return x.Plugin + } + return "" +} + +func (x *BackupItemActionProgressRequest) GetOperationID() string { + if x != nil { + return x.OperationID + } + return "" +} + +func (x *BackupItemActionProgressRequest) GetBackup() []byte { + if x != nil { + return x.Backup + } + return nil +} + +type BackupItemActionProgressResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Progress *generated.OperationProgress `protobuf:"bytes,1,opt,name=progress,proto3" json:"progress,omitempty"` +} + +func (x *BackupItemActionProgressResponse) Reset() { + *x = BackupItemActionProgressResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BackupItemActionProgressResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BackupItemActionProgressResponse) ProtoMessage() {} + +func (x *BackupItemActionProgressResponse) ProtoReflect() protoreflect.Message { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BackupItemActionProgressResponse.ProtoReflect.Descriptor instead. +func (*BackupItemActionProgressResponse) Descriptor() ([]byte, []int) { + return file_backupitemaction_v2_BackupItemAction_proto_rawDescGZIP(), []int{5} +} + +func (x *BackupItemActionProgressResponse) GetProgress() *generated.OperationProgress { + if x != nil { + return x.Progress + } + return nil +} + +type BackupItemActionCancelRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Plugin string `protobuf:"bytes,1,opt,name=plugin,proto3" json:"plugin,omitempty"` + OperationID string `protobuf:"bytes,2,opt,name=operationID,proto3" json:"operationID,omitempty"` + Backup []byte `protobuf:"bytes,3,opt,name=backup,proto3" json:"backup,omitempty"` +} + +func (x *BackupItemActionCancelRequest) Reset() { + *x = BackupItemActionCancelRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BackupItemActionCancelRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BackupItemActionCancelRequest) ProtoMessage() {} + +func (x *BackupItemActionCancelRequest) ProtoReflect() protoreflect.Message { + mi := &file_backupitemaction_v2_BackupItemAction_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BackupItemActionCancelRequest.ProtoReflect.Descriptor instead. +func (*BackupItemActionCancelRequest) Descriptor() ([]byte, []int) { + return file_backupitemaction_v2_BackupItemAction_proto_rawDescGZIP(), []int{6} +} + +func (x *BackupItemActionCancelRequest) GetPlugin() string { + if x != nil { + return x.Plugin + } + return "" +} + +func (x *BackupItemActionCancelRequest) GetOperationID() string { + if x != nil { + return x.OperationID + } + return "" +} + +func (x *BackupItemActionCancelRequest) GetBackup() []byte { + if x != nil { + return x.Backup + } + return nil +} + +var File_backupitemaction_v2_BackupItemAction_proto protoreflect.FileDescriptor + +var file_backupitemaction_v2_BackupItemAction_proto_rawDesc = []byte{ + 0x0a, 0x2a, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x69, 0x74, 0x65, 0x6d, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x2f, 0x76, 0x32, 0x2f, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, + 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x02, 0x76, 0x32, + 0x1a, 0x0c, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, + 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x54, 0x0a, 0x0e, 0x45, + 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, + 0x06, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, + 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x61, 0x63, + 0x6b, 0x75, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x62, 0x61, 0x63, 0x6b, 0x75, + 0x70, 0x22, 0x90, 0x01, 0x0a, 0x0f, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x47, 0x0a, 0x0f, 0x61, 0x64, 0x64, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2e, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, + 0x72, 0x52, 0x0f, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x49, 0x74, 0x65, + 0x6d, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, + 0x44, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x49, 0x44, 0x22, 0x3a, 0x0a, 0x20, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x49, 0x74, + 0x65, 0x6d, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x73, 0x54, + 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6c, 0x75, 0x67, + 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, + 0x22, 0x6c, 0x0a, 0x21, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x41, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x73, 0x54, 0x6f, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x10, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1b, 0x2e, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2e, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x10, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x73, + 0x0a, 0x1f, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x41, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x6f, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, + 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x16, 0x0a, 0x06, 0x62, + 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x62, 0x61, 0x63, + 0x6b, 0x75, 0x70, 0x22, 0x5c, 0x0a, 0x20, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x49, 0x74, 0x65, + 0x6d, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, + 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x65, 0x6e, 0x65, + 0x72, 0x61, 0x74, 0x65, 0x64, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, + 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, + 0x73, 0x22, 0x71, 0x0a, 0x1d, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x41, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x6f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x16, 0x0a, 0x06, + 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x62, 0x61, + 0x63, 0x6b, 0x75, 0x70, 0x32, 0xbc, 0x02, 0x0a, 0x10, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x49, + 0x74, 0x65, 0x6d, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x58, 0x0a, 0x09, 0x41, 0x70, 0x70, + 0x6c, 0x69, 0x65, 0x73, 0x54, 0x6f, 0x12, 0x24, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x63, 0x6b, + 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x70, 0x70, 0x6c, + 0x69, 0x65, 0x73, 0x54, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x76, + 0x32, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x41, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x73, 0x54, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x07, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x12, 0x12, + 0x2e, 0x76, 0x32, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x76, 0x32, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x55, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x67, 0x72, + 0x65, 0x73, 0x73, 0x12, 0x23, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x49, + 0x74, 0x65, 0x6d, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, + 0x63, 0x6b, 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, + 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, + 0x0a, 0x06, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x12, 0x21, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, + 0x63, 0x6b, 0x75, 0x70, 0x49, 0x74, 0x65, 0x6d, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x61, + 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x42, 0x49, 0x5a, 0x47, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x76, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x2d, 0x74, 0x61, 0x6e, 0x7a, 0x75, 0x2f, 0x76, + 0x65, 0x6c, 0x65, 0x72, 0x6f, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, + 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x75, + 0x70, 0x69, 0x74, 0x65, 0x6d, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x76, 0x32, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_backupitemaction_v2_BackupItemAction_proto_rawDescOnce sync.Once + file_backupitemaction_v2_BackupItemAction_proto_rawDescData = file_backupitemaction_v2_BackupItemAction_proto_rawDesc +) + +func file_backupitemaction_v2_BackupItemAction_proto_rawDescGZIP() []byte { + file_backupitemaction_v2_BackupItemAction_proto_rawDescOnce.Do(func() { + file_backupitemaction_v2_BackupItemAction_proto_rawDescData = protoimpl.X.CompressGZIP(file_backupitemaction_v2_BackupItemAction_proto_rawDescData) + }) + return file_backupitemaction_v2_BackupItemAction_proto_rawDescData +} + +var file_backupitemaction_v2_BackupItemAction_proto_msgTypes = make([]protoimpl.MessageInfo, 7) +var file_backupitemaction_v2_BackupItemAction_proto_goTypes = []interface{}{ + (*ExecuteRequest)(nil), // 0: v2.ExecuteRequest + (*ExecuteResponse)(nil), // 1: v2.ExecuteResponse + (*BackupItemActionAppliesToRequest)(nil), // 2: v2.BackupItemActionAppliesToRequest + (*BackupItemActionAppliesToResponse)(nil), // 3: v2.BackupItemActionAppliesToResponse + (*BackupItemActionProgressRequest)(nil), // 4: v2.BackupItemActionProgressRequest + (*BackupItemActionProgressResponse)(nil), // 5: v2.BackupItemActionProgressResponse + (*BackupItemActionCancelRequest)(nil), // 6: v2.BackupItemActionCancelRequest + (*generated.ResourceIdentifier)(nil), // 7: generated.ResourceIdentifier + (*generated.ResourceSelector)(nil), // 8: generated.ResourceSelector + (*generated.OperationProgress)(nil), // 9: generated.OperationProgress + (*emptypb.Empty)(nil), // 10: google.protobuf.Empty +} +var file_backupitemaction_v2_BackupItemAction_proto_depIdxs = []int32{ + 7, // 0: v2.ExecuteResponse.additionalItems:type_name -> generated.ResourceIdentifier + 8, // 1: v2.BackupItemActionAppliesToResponse.ResourceSelector:type_name -> generated.ResourceSelector + 9, // 2: v2.BackupItemActionProgressResponse.progress:type_name -> generated.OperationProgress + 2, // 3: v2.BackupItemAction.AppliesTo:input_type -> v2.BackupItemActionAppliesToRequest + 0, // 4: v2.BackupItemAction.Execute:input_type -> v2.ExecuteRequest + 4, // 5: v2.BackupItemAction.Progress:input_type -> v2.BackupItemActionProgressRequest + 6, // 6: v2.BackupItemAction.Cancel:input_type -> v2.BackupItemActionCancelRequest + 3, // 7: v2.BackupItemAction.AppliesTo:output_type -> v2.BackupItemActionAppliesToResponse + 1, // 8: v2.BackupItemAction.Execute:output_type -> v2.ExecuteResponse + 5, // 9: v2.BackupItemAction.Progress:output_type -> v2.BackupItemActionProgressResponse + 10, // 10: v2.BackupItemAction.Cancel:output_type -> google.protobuf.Empty + 7, // [7:11] is the sub-list for method output_type + 3, // [3:7] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name +} + +func init() { file_backupitemaction_v2_BackupItemAction_proto_init() } +func file_backupitemaction_v2_BackupItemAction_proto_init() { + if File_backupitemaction_v2_BackupItemAction_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_backupitemaction_v2_BackupItemAction_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ExecuteRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backupitemaction_v2_BackupItemAction_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ExecuteResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backupitemaction_v2_BackupItemAction_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BackupItemActionAppliesToRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backupitemaction_v2_BackupItemAction_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BackupItemActionAppliesToResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backupitemaction_v2_BackupItemAction_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BackupItemActionProgressRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backupitemaction_v2_BackupItemAction_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BackupItemActionProgressResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backupitemaction_v2_BackupItemAction_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BackupItemActionCancelRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_backupitemaction_v2_BackupItemAction_proto_rawDesc, + NumEnums: 0, + NumMessages: 7, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_backupitemaction_v2_BackupItemAction_proto_goTypes, + DependencyIndexes: file_backupitemaction_v2_BackupItemAction_proto_depIdxs, + MessageInfos: file_backupitemaction_v2_BackupItemAction_proto_msgTypes, + }.Build() + File_backupitemaction_v2_BackupItemAction_proto = out.File + file_backupitemaction_v2_BackupItemAction_proto_rawDesc = nil + file_backupitemaction_v2_BackupItemAction_proto_goTypes = nil + file_backupitemaction_v2_BackupItemAction_proto_depIdxs = nil +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConnInterface + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion6 + +// BackupItemActionClient is the client API for BackupItemAction service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type BackupItemActionClient interface { + AppliesTo(ctx context.Context, in *BackupItemActionAppliesToRequest, opts ...grpc.CallOption) (*BackupItemActionAppliesToResponse, error) + Execute(ctx context.Context, in *ExecuteRequest, opts ...grpc.CallOption) (*ExecuteResponse, error) + Progress(ctx context.Context, in *BackupItemActionProgressRequest, opts ...grpc.CallOption) (*BackupItemActionProgressResponse, error) + Cancel(ctx context.Context, in *BackupItemActionCancelRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) +} + +type backupItemActionClient struct { + cc grpc.ClientConnInterface +} + +func NewBackupItemActionClient(cc grpc.ClientConnInterface) BackupItemActionClient { + return &backupItemActionClient{cc} +} + +func (c *backupItemActionClient) AppliesTo(ctx context.Context, in *BackupItemActionAppliesToRequest, opts ...grpc.CallOption) (*BackupItemActionAppliesToResponse, error) { + out := new(BackupItemActionAppliesToResponse) + err := c.cc.Invoke(ctx, "/v2.BackupItemAction/AppliesTo", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *backupItemActionClient) Execute(ctx context.Context, in *ExecuteRequest, opts ...grpc.CallOption) (*ExecuteResponse, error) { + out := new(ExecuteResponse) + err := c.cc.Invoke(ctx, "/v2.BackupItemAction/Execute", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *backupItemActionClient) Progress(ctx context.Context, in *BackupItemActionProgressRequest, opts ...grpc.CallOption) (*BackupItemActionProgressResponse, error) { + out := new(BackupItemActionProgressResponse) + err := c.cc.Invoke(ctx, "/v2.BackupItemAction/Progress", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *backupItemActionClient) Cancel(ctx context.Context, in *BackupItemActionCancelRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, "/v2.BackupItemAction/Cancel", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// BackupItemActionServer is the server API for BackupItemAction service. +type BackupItemActionServer interface { + AppliesTo(context.Context, *BackupItemActionAppliesToRequest) (*BackupItemActionAppliesToResponse, error) + Execute(context.Context, *ExecuteRequest) (*ExecuteResponse, error) + Progress(context.Context, *BackupItemActionProgressRequest) (*BackupItemActionProgressResponse, error) + Cancel(context.Context, *BackupItemActionCancelRequest) (*emptypb.Empty, error) +} + +// UnimplementedBackupItemActionServer can be embedded to have forward compatible implementations. +type UnimplementedBackupItemActionServer struct { +} + +func (*UnimplementedBackupItemActionServer) AppliesTo(context.Context, *BackupItemActionAppliesToRequest) (*BackupItemActionAppliesToResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AppliesTo not implemented") +} +func (*UnimplementedBackupItemActionServer) Execute(context.Context, *ExecuteRequest) (*ExecuteResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Execute not implemented") +} +func (*UnimplementedBackupItemActionServer) Progress(context.Context, *BackupItemActionProgressRequest) (*BackupItemActionProgressResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Progress not implemented") +} +func (*UnimplementedBackupItemActionServer) Cancel(context.Context, *BackupItemActionCancelRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method Cancel not implemented") +} + +func RegisterBackupItemActionServer(s *grpc.Server, srv BackupItemActionServer) { + s.RegisterService(&_BackupItemAction_serviceDesc, srv) +} + +func _BackupItemAction_AppliesTo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(BackupItemActionAppliesToRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BackupItemActionServer).AppliesTo(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/v2.BackupItemAction/AppliesTo", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BackupItemActionServer).AppliesTo(ctx, req.(*BackupItemActionAppliesToRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _BackupItemAction_Execute_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ExecuteRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BackupItemActionServer).Execute(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/v2.BackupItemAction/Execute", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BackupItemActionServer).Execute(ctx, req.(*ExecuteRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _BackupItemAction_Progress_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(BackupItemActionProgressRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BackupItemActionServer).Progress(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/v2.BackupItemAction/Progress", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BackupItemActionServer).Progress(ctx, req.(*BackupItemActionProgressRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _BackupItemAction_Cancel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(BackupItemActionCancelRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BackupItemActionServer).Cancel(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/v2.BackupItemAction/Cancel", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BackupItemActionServer).Cancel(ctx, req.(*BackupItemActionCancelRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _BackupItemAction_serviceDesc = grpc.ServiceDesc{ + ServiceName: "v2.BackupItemAction", + HandlerType: (*BackupItemActionServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "AppliesTo", + Handler: _BackupItemAction_AppliesTo_Handler, + }, + { + MethodName: "Execute", + Handler: _BackupItemAction_Execute_Handler, + }, + { + MethodName: "Progress", + Handler: _BackupItemAction_Progress_Handler, + }, + { + MethodName: "Cancel", + Handler: _BackupItemAction_Cancel_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "backupitemaction/v2/BackupItemAction.proto", +} diff --git a/pkg/plugin/mocks/manager.go b/pkg/plugin/mocks/manager.go index c99e1bf288a..5031729a1c1 100644 --- a/pkg/plugin/mocks/manager.go +++ b/pkg/plugin/mocks/manager.go @@ -10,6 +10,8 @@ import ( v1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v1" + v2 "github.com/vmware-tanzu/velero/pkg/plugin/velero/backupitemaction/v2" + velero "github.com/vmware-tanzu/velero/pkg/plugin/velero" volumesnapshotterv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/volumesnapshotter/v1" @@ -48,6 +50,29 @@ func (_m *Manager) GetBackupItemAction(name string) (v1.BackupItemAction, error) return r0, r1 } +// GetBackupItemActionV2 provides a mock function with given fields: name +func (_m *Manager) GetBackupItemActionV2(name string) (v2.BackupItemAction, error) { + ret := _m.Called(name) + + var r0 v2.BackupItemAction + if rf, ok := ret.Get(0).(func(string) v2.BackupItemAction); ok { + r0 = rf(name) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(v2.BackupItemAction) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(name) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetBackupItemActions provides a mock function with given fields: func (_m *Manager) GetBackupItemActions() ([]v1.BackupItemAction, error) { ret := _m.Called() @@ -71,6 +96,29 @@ func (_m *Manager) GetBackupItemActions() ([]v1.BackupItemAction, error) { return r0, r1 } +// GetBackupItemActionsV2 provides a mock function with given fields: +func (_m *Manager) GetBackupItemActionsV2() ([]v2.BackupItemAction, error) { + ret := _m.Called() + + var r0 []v2.BackupItemAction + if rf, ok := ret.Get(0).(func() []v2.BackupItemAction); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]v2.BackupItemAction) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetDeleteItemAction provides a mock function with given fields: name func (_m *Manager) GetDeleteItemAction(name string) (velero.DeleteItemAction, error) { ret := _m.Called(name) diff --git a/pkg/plugin/proto/Shared.proto b/pkg/plugin/proto/Shared.proto index c1c298e3a41..5bb73ecb86b 100644 --- a/pkg/plugin/proto/Shared.proto +++ b/pkg/plugin/proto/Shared.proto @@ -2,6 +2,8 @@ syntax = "proto3"; package generated; option go_package = "github.com/vmware-tanzu/velero/pkg/plugin/generated"; +import "google/protobuf/timestamp.proto"; + message Empty {} message Stack { @@ -27,4 +29,13 @@ message ResourceSelector { repeated string includedResources = 3; repeated string excludedResources = 4; string selector = 5; -} \ No newline at end of file +} + +message OperationProgress { + bool completed = 1; + string err = 2; + int64 itemsCompleted = 3; + int64 itemsToComplete = 4; + google.protobuf.Timestamp started = 5; + google.protobuf.Timestamp updated = 6; +} diff --git a/pkg/plugin/proto/backupitemaction/v2/BackupItemAction.proto b/pkg/plugin/proto/backupitemaction/v2/BackupItemAction.proto new file mode 100644 index 00000000000..5f7bfe7d33a --- /dev/null +++ b/pkg/plugin/proto/backupitemaction/v2/BackupItemAction.proto @@ -0,0 +1,50 @@ +syntax = "proto3"; +package v2; +option go_package = "github.com/vmware-tanzu/velero/pkg/plugin/generated/backupitemaction/v2"; + +import "Shared.proto"; +import "google/protobuf/empty.proto"; + + +message ExecuteRequest { + string plugin = 1; + bytes item = 2; + bytes backup = 3; +} + +message ExecuteResponse { + bytes item = 1; + repeated generated.ResourceIdentifier additionalItems = 2; + string operationID = 3; +} + +service BackupItemAction { + rpc AppliesTo(BackupItemActionAppliesToRequest) returns (BackupItemActionAppliesToResponse); + rpc Execute(ExecuteRequest) returns (ExecuteResponse); + rpc Progress(BackupItemActionProgressRequest) returns (BackupItemActionProgressResponse); + rpc Cancel(BackupItemActionCancelRequest) returns (google.protobuf.Empty); +} + +message BackupItemActionAppliesToRequest { + string plugin = 1; +} + +message BackupItemActionAppliesToResponse { + generated.ResourceSelector ResourceSelector = 1; +} + +message BackupItemActionProgressRequest { + string plugin = 1; + string operationID = 2; + bytes backup = 3; +} + +message BackupItemActionProgressResponse { + generated.OperationProgress progress = 1; +} + +message BackupItemActionCancelRequest { + string plugin = 1; + string operationID = 2; + bytes backup = 3; +} diff --git a/pkg/plugin/velero/backupitemaction/v2/backup_item_action.go b/pkg/plugin/velero/backupitemaction/v2/backup_item_action.go new file mode 100644 index 00000000000..e3d45b52b6d --- /dev/null +++ b/pkg/plugin/velero/backupitemaction/v2/backup_item_action.go @@ -0,0 +1,63 @@ +/* +Copyright 2017 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" + + "k8s.io/apimachinery/pkg/runtime" + + "github.com/pkg/errors" + + api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + "github.com/vmware-tanzu/velero/pkg/plugin/velero" +) + +// BackupItemAction is an actor that performs an operation on an individual item being backed up. +type BackupItemAction interface { + // AppliesTo returns information about which resources this action should be invoked for. + // A BackupItemAction'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 BackupItemAction to perform arbitrary logic with the item being backed up, + // including mutating the item itself prior to backup. The item (unmodified or modified) + // should be returned, along with an optional slice of ResourceIdentifiers specifying + // additional related items that should be backed up. + Execute(item runtime.Unstructured, backup *api.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) + + // Progress allows the BackupItemAction 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, backup *api.Backup) (velero.OperationProgress, error) + + // Cancel allows the BackupItemAction 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, backup *api.Backup) error +} + +func AsyncOperationsNotSupportedError() error { + return errors.New("Plugin does not support asynchronous operations") +} + +func InvalidOperationIDError(operationID string) error { + return errors.New(fmt.Sprintf("Operaton ID %v is invalid.", operationID)) +} diff --git a/pkg/plugin/velero/mocks/backupitemaction/v2/BackupItemAction.go b/pkg/plugin/velero/mocks/backupitemaction/v2/BackupItemAction.go new file mode 100644 index 00000000000..63be81d4d66 --- /dev/null +++ b/pkg/plugin/velero/mocks/backupitemaction/v2/BackupItemAction.go @@ -0,0 +1,127 @@ +/* +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" + runtime "k8s.io/apimachinery/pkg/runtime" + + v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + + velero "github.com/vmware-tanzu/velero/pkg/plugin/velero" +) + +// BackupItemAction is an autogenerated mock type for the BackupItemAction type +type BackupItemAction struct { + mock.Mock +} + +// AppliesTo provides a mock function with given fields: +func (_m *BackupItemAction) 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 +} + +// Cancel provides a mock function with given fields: operationID, backup +func (_m *BackupItemAction) Cancel(operationID string, backup *v1.Backup) error { + ret := _m.Called(operationID, backup) + + var r0 error + if rf, ok := ret.Get(0).(func(string, *v1.Backup) error); ok { + r0 = rf(operationID, backup) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Execute provides a mock function with given fields: item, backup +func (_m *BackupItemAction) Execute(item runtime.Unstructured, backup *v1.Backup) (runtime.Unstructured, []velero.ResourceIdentifier, string, error) { + ret := _m.Called(item, backup) + + var r0 runtime.Unstructured + if rf, ok := ret.Get(0).(func(runtime.Unstructured, *v1.Backup) runtime.Unstructured); ok { + r0 = rf(item, backup) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(runtime.Unstructured) + } + } + + var r1 []velero.ResourceIdentifier + if rf, ok := ret.Get(1).(func(runtime.Unstructured, *v1.Backup) []velero.ResourceIdentifier); ok { + r1 = rf(item, backup) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).([]velero.ResourceIdentifier) + } + } + + var r2 string + if rf, ok := ret.Get(2).(func(runtime.Unstructured, *v1.Backup) string); ok { + r2 = rf(item, backup) + } else { + r2 = ret.Get(2).(string) + } + + var r3 error + if rf, ok := ret.Get(3).(func(runtime.Unstructured, *v1.Backup) error); ok { + r3 = rf(item, backup) + } else { + r3 = ret.Error(3) + } + + return r0, r1, r2, r3 +} + +// Progress provides a mock function with given fields: operationID, backup +func (_m *BackupItemAction) Progress(operationID string, backup *v1.Backup) (velero.OperationProgress, error) { + ret := _m.Called(operationID, backup) + + var r0 velero.OperationProgress + if rf, ok := ret.Get(0).(func(string, *v1.Backup) velero.OperationProgress); ok { + r0 = rf(operationID, backup) + } else { + r0 = ret.Get(0).(velero.OperationProgress) + } + + var r1 error + if rf, ok := ret.Get(1).(func(string, *v1.Backup) error); ok { + r1 = rf(operationID, backup) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/pkg/plugin/velero/shared.go b/pkg/plugin/velero/shared.go index a0d3d884c2b..b1ca7084eca 100644 --- a/pkg/plugin/velero/shared.go +++ b/pkg/plugin/velero/shared.go @@ -20,7 +20,11 @@ limitations under the License. // plugins of any type can be implemented. package velero -import "k8s.io/apimachinery/pkg/runtime/schema" +import ( + "time" + + "k8s.io/apimachinery/pkg/runtime/schema" +) // ResourceSelector is a collection of included/excluded namespaces, // included/excluded resources, and a label-selector that can be used @@ -63,3 +67,22 @@ type ResourceIdentifier struct { Namespace string Name string } + +// OperationProgress describes progress of an asynchronous plugin operation. +type OperationProgress struct { + // True when the operation has completed, either successfully or with a failure + Completed bool + // Set when the operation has failed + Err string + // The number of items that have been completed and the items to complete + // For a disk, an item would be a byte and itemsToComplete would be the + // total size to transfer (may be less than the size of a volume if + // performing an incremental) and itemsCompleted is the number of bytes + // transferred. On successful completion, itemsCompleted and itemsToComplete + // should be the same + ItemsCompleted, ItemsToComplete int64 + // When the operation was started and when the last update was seen. Not all + // systems retain when the upload was begun, return Time 0 (time.Unix(0, 0)) + // if unknown. + Started, Updated time.Time +}