diff --git a/cli/cmd/install.go b/cli/cmd/install.go index 2238952a1..0fe9006b5 100644 --- a/cli/cmd/install.go +++ b/cli/cmd/install.go @@ -35,20 +35,20 @@ const ( PreferredNamespace = tridentconfig.OrchestratorName // CRD names - - ActionMirrorUpdateCRDName = "tridentactionmirrorupdates.trident.netapp.io" - BackendConfigCRDName = "tridentbackendconfigs.trident.netapp.io" - BackendCRDName = "tridentbackends.trident.netapp.io" - MirrorRelationshipCRDName = "tridentmirrorrelationships.trident.netapp.io" - NodeCRDName = "tridentnodes.trident.netapp.io" - SnapshotCRDName = "tridentsnapshots.trident.netapp.io" - SnapshotInfoCRDName = "tridentsnapshotinfos.trident.netapp.io" - StorageClassCRDName = "tridentstorageclasses.trident.netapp.io" - TransactionCRDName = "tridenttransactions.trident.netapp.io" - VersionCRDName = "tridentversions.trident.netapp.io" - VolumeCRDName = "tridentvolumes.trident.netapp.io" - VolumePublicationCRDName = "tridentvolumepublications.trident.netapp.io" - VolumeReferenceCRDName = "tridentvolumereferences.trident.netapp.io" + ActionMirrorUpdateCRDName = "tridentactionmirrorupdates.trident.netapp.io" + ActionSnapshotRestoreCRDName = "tridentactionsnapshotrestores.trident.netapp.io" + BackendConfigCRDName = "tridentbackendconfigs.trident.netapp.io" + BackendCRDName = "tridentbackends.trident.netapp.io" + MirrorRelationshipCRDName = "tridentmirrorrelationships.trident.netapp.io" + NodeCRDName = "tridentnodes.trident.netapp.io" + SnapshotCRDName = "tridentsnapshots.trident.netapp.io" + SnapshotInfoCRDName = "tridentsnapshotinfos.trident.netapp.io" + StorageClassCRDName = "tridentstorageclasses.trident.netapp.io" + TransactionCRDName = "tridenttransactions.trident.netapp.io" + VersionCRDName = "tridentversions.trident.netapp.io" + VolumeCRDName = "tridentvolumes.trident.netapp.io" + VolumePublicationCRDName = "tridentvolumepublications.trident.netapp.io" + VolumeReferenceCRDName = "tridentvolumereferences.trident.netapp.io" ControllerRoleFilename = "trident-controller-role.yaml" ControllerClusterRoleFilename = "trident-controller-clusterrole.yaml" @@ -168,6 +168,7 @@ var ( VersionCRDName, VolumeCRDName, VolumePublicationCRDName, + ActionSnapshotRestoreCRDName, } ) diff --git a/cli/cmd/obliviate_crd.go b/cli/cmd/obliviate_crd.go index 3f9c5bee8..2a5f0ef8e 100644 --- a/cli/cmd/obliviate_crd.go +++ b/cli/cmd/obliviate_crd.go @@ -150,6 +150,10 @@ func deleteCRs() error { return err } + if err := deleteActionSnapshotRestores(); err != nil { + return err + } + return nil } @@ -892,6 +896,64 @@ func deleteVolumeReferences() error { return nil } +func deleteActionSnapshotRestores() error { + crd := "tridentactionsnapshotrestores.trident.netapp.io" + logFields := LogFields{"CRD": crd} + + // See if CRD exists + exists, err := k8sClient.CheckCRDExists(crd) + if err != nil { + return err + } else if !exists { + Log().WithField("CRD", crd).Debug("CRD not present.") + return nil + } + + vrefs, err := crdClientset.TridentV1().TridentActionSnapshotRestores(allNamespaces).List(ctx(), listOpts) + if err != nil { + return err + } else if len(vrefs.Items) == 0 { + Log().WithFields(logFields).Info("Resources not present.") + return nil + } + + for _, vref := range vrefs.Items { + if vref.DeletionTimestamp.IsZero() { + _ = crdClientset.TridentV1().TridentActionSnapshotRestores(vref.Namespace).Delete(ctx(), + vref.Name, deleteOpts) + } + } + + vrefs, err = crdClientset.TridentV1().TridentActionSnapshotRestores(allNamespaces).List(ctx(), listOpts) + if err != nil { + return err + } + + for _, vref := range vrefs.Items { + if vref.HasTridentFinalizers() { + crCopy := vref.DeepCopy() + crCopy.RemoveTridentFinalizers() + _, err := crdClientset.TridentV1().TridentActionSnapshotRestores(vref.Namespace).Update(ctx(), + crCopy, updateOpts) + if isNotFoundError(err) { + continue + } else if err != nil { + Log().Errorf("Problem removing finalizers: %v", err) + return err + } + } + + deleteFunc := crdClientset.TridentV1().TridentActionSnapshotRestores(vref.Namespace).Delete + if err = deleteWithRetry(deleteFunc, ctx(), vref.Name, nil); err != nil { + Log().Errorf("Problem deleting resource: %v", err) + return err + } + } + + Log().WithFields(logFields).Info("Resources deleted.") + return nil +} + func deleteCRDs() error { crdNames := []string{ "tridentversions.trident.netapp.io", @@ -907,6 +969,7 @@ func deleteCRDs() error { "tridentsnapshots.trident.netapp.io", "tridentvolumepublications.trident.netapp.io", "tridentvolumereferences.trident.netapp.io", + "tridentactionsnapshotrestores.trident.netapp.io", } for _, crdName := range crdNames { diff --git a/cli/k8s_client/yaml_factory.go b/cli/k8s_client/yaml_factory.go index b1f80c0cd..220282750 100644 --- a/cli/k8s_client/yaml_factory.go +++ b/cli/k8s_client/yaml_factory.go @@ -158,7 +158,8 @@ rules: "tridenttransactions", "tridentsnapshots", "tridentbackendconfigs", "tridentbackendconfigs/status", "tridentmirrorrelationships", "tridentmirrorrelationships/status", "tridentsnapshotinfos", "tridentsnapshotinfos/status", "tridentvolumepublications", "tridentvolumereferences", -"tridentactionmirrorupdates", "tridentactionmirrorupdates/status"] +"tridentactionmirrorupdates", "tridentactionmirrorupdates/status", +"tridentactionsnapshotrestores", "tridentactionsnapshotrestores/status"] verbs: ["get", "list", "watch", "create", "delete", "update", "patch"] - apiGroups: ["policy"] resources: ["podsecuritypolicies"] @@ -1530,6 +1531,12 @@ func GetVolumeReferenceCRDYAML() string { return tridentVolumeReferenceCRDYAMLv1 } +func GetActionSnapshotRestoreCRDYAML() string { + Log().Trace(">>>> GetActionSnapshotRestoreCRDYAML") + defer func() { Log().Trace("<<<< GetActionSnapshotRestoreCRDYAML") }() + return tridentActionSnapshotRestoreCRDYAMLv1 +} + func GetOrchestratorCRDYAML() string { Log().Trace(">>>> GetOrchestratorCRDYAML") defer func() { Log().Trace("<<<< GetOrchestratorCRDYAML") }() @@ -1549,6 +1556,7 @@ kubectl delete crd tridentnodes.trident.netapp.io --wait=false kubectl delete crd tridenttransactions.trident.netapp.io --wait=false kubectl delete crd tridentsnapshots.trident.netapp.io --wait=false kubectl delete crd tridentvolumereferences.trident.netapp.io --wait=false +kubectl delete crd tridentactionsnapshotrestores.trident.netapp.io --wait=false kubectl patch crd tridentversions.trident.netapp.io -p '{"metadata":{"finalizers": []}}' --type=merge kubectl patch crd tridentbackends.trident.netapp.io -p '{"metadata":{"finalizers": []}}' --type=merge @@ -1562,6 +1570,7 @@ kubectl patch crd tridentnodes.trident.netapp.io -p '{"metadata":{"finalizers": kubectl patch crd tridenttransactions.trident.netapp.io -p '{"metadata":{"finalizers": []}}' --type=merge kubectl patch crd tridentsnapshots.trident.netapp.io -p '{"metadata":{"finalizers": []}}' --type=merge kubectl patch crd tridentvolumereferences.trident.netapp.io -p '{"metadata":{"finalizers": []}}' --type=merge +kubectl patch crd tridentactionsnapshotrestores.trident.netapp.io -p '{"metadata":{"finalizers": []}}' --type=merge kubectl delete crd tridentversions.trident.netapp.io kubectl delete crd tridentbackends.trident.netapp.io @@ -1575,6 +1584,7 @@ kubectl delete crd tridentnodes.trident.netapp.io kubectl delete crd tridenttransactions.trident.netapp.io kubectl delete crd tridentsnapshots.trident.netapp.io kubectl delete crd tridentvolumereferences.trident.netapp.io +kubectl delete crd tridentactionsnapshotrestores.trident.netapp.io */ const tridentVersionCRDYAMLv1 = ` @@ -2227,6 +2237,63 @@ spec: - trident-external - trident-internal` +const tridentActionSnapshotRestoreCRDYAMLv1 = ` +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: tridentactionsnapshotrestores.trident.netapp.io +spec: + group: trident.netapp.io + versions: + - name: v1 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + x-kubernetes-preserve-unknown-fields: true + additionalPrinterColumns: + - description: Namespace + jsonPath: .metadata.namespace + name: Namespace + type: string + priority: 0 + - description: PVC + jsonPath: .spec.pvcName + name: PVC + type: string + priority: 0 + - description: Snapshot + jsonPath: .spec.volumeSnapshotName + name: Snapshot + type: string + priority: 0 + - description: State + jsonPath: .status.state + name: State + type: string + priority: 0 + - description: CompletionTime + jsonPath: .status.completionTime + name: CompletionTime + type: date + priority: 0 + - description: Message + jsonPath: .status.message + name: Message + type: string + priority: 1 + scope: Namespaced + names: + plural: tridentactionsnapshotrestores + singular: tridentactionsnapshotrestore + kind: TridentActionSnapshotRestore + shortNames: + - tasr + categories: + - trident + - trident-external` + const customResourceDefinitionYAMLv1 = tridentVersionCRDYAMLv1 + "\n---" + tridentBackendCRDYAMLv1 + "\n---" + tridentBackendConfigCRDYAMLv1 + @@ -2239,7 +2306,8 @@ const customResourceDefinitionYAMLv1 = tridentVersionCRDYAMLv1 + "\n---" + tridentNodeCRDYAMLv1 + "\n---" + tridentTransactionCRDYAMLv1 + "\n---" + tridentSnapshotCRDYAMLv1 + - "\n---" + tridentVolumeReferenceCRDYAMLv1 + "\n" + "\n---" + tridentVolumeReferenceCRDYAMLv1 + + "\n---" + tridentActionSnapshotRestoreCRDYAMLv1 + "\n" func GetCSIDriverYAML(name string, labels, controllingCRDetails map[string]string) string { Log().WithFields(LogFields{ diff --git a/cli/k8s_client/yaml_factory_test.go b/cli/k8s_client/yaml_factory_test.go index 1e688ef8b..a1fb3692c 100644 --- a/cli/k8s_client/yaml_factory_test.go +++ b/cli/k8s_client/yaml_factory_test.go @@ -1906,6 +1906,78 @@ func TestGetCRDsYAML(t *testing.T) { }, }, } + expected14 := apiextensionsv1.CustomResourceDefinition{ + TypeMeta: metav1.TypeMeta{ + Kind: "CustomResourceDefinition", + APIVersion: "apiextensions.k8s.io/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "tridentactionsnapshotrestores.trident.netapp.io", + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: "trident.netapp.io", + Names: apiextensionsv1.CustomResourceDefinitionNames{ + Plural: "tridentactionsnapshotrestores", + Singular: "tridentactionsnapshotrestore", + Kind: "TridentActionSnapshotRestore", + ShortNames: []string{"tasr"}, + Categories: []string{"trident", "trident-external"}, + }, + Scope: "Namespaced", + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ + { + Name: "v1", + Served: true, + Storage: true, + Schema: &schema1, + AdditionalPrinterColumns: []apiextensionsv1.CustomResourceColumnDefinition{ + { + Name: "Namespace", + Type: "string", + Description: "Namespace", + JSONPath: ".metadata.namespace", + Priority: 0, + }, + { + Name: "PVC", + Type: "string", + Description: "PVC", + JSONPath: ".spec.pvcName", + Priority: 0, + }, + { + Name: "Snapshot", + Type: "string", + Description: "Snapshot", + JSONPath: ".spec.volumeSnapshotName", + Priority: 0, + }, + { + Name: "State", + Type: "string", + Description: "State", + JSONPath: ".status.state", + Priority: 0, + }, + { + Name: "CompletionTime", + Type: "date", + Description: "CompletionTime", + JSONPath: ".status.completionTime", + Priority: 0, + }, + { + Name: "Message", + Type: "string", + Description: "Message", + JSONPath: ".status.message", + Priority: 1, + }, + }, + }, + }, + }, + } // trident version var actual1 apiextensionsv1.CustomResourceDefinition @@ -1997,6 +2069,13 @@ func TestGetCRDsYAML(t *testing.T) { assert.True(t, reflect.DeepEqual(expected13.TypeMeta, actual13.TypeMeta)) assert.True(t, reflect.DeepEqual(expected13.ObjectMeta, actual13.ObjectMeta)) assert.True(t, reflect.DeepEqual(expected13.Spec, actual13.Spec)) + + // trident action snapshot restores + var actual14 apiextensionsv1.CustomResourceDefinition + assert.Nil(t, yaml.Unmarshal([]byte(result[13]), &actual14), "invalid YAML") + assert.True(t, reflect.DeepEqual(expected14.TypeMeta, actual14.TypeMeta)) + assert.True(t, reflect.DeepEqual(expected14.ObjectMeta, actual14.ObjectMeta)) + assert.True(t, reflect.DeepEqual(expected14.Spec, actual14.Spec)) } func TestGetVersionCRDYAML(t *testing.T) { @@ -3034,6 +3113,96 @@ func TestGetActionMirrorUpdateCRDYAML(t *testing.T) { assert.True(t, reflect.DeepEqual(expected.Spec, actual.Spec)) } +func TestGetActionSnapshotRestoreCRDYAML(t *testing.T) { + preserveValue := true + schema := apiextensionsv1.CustomResourceValidation{ + OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{ + Type: "object", + XPreserveUnknownFields: &preserveValue, + }, + } + expected := apiextensionsv1.CustomResourceDefinition{ + TypeMeta: metav1.TypeMeta{ + Kind: "CustomResourceDefinition", + APIVersion: "apiextensions.k8s.io/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "tridentactionsnapshotrestores.trident.netapp.io", + }, + Spec: apiextensionsv1.CustomResourceDefinitionSpec{ + Group: "trident.netapp.io", + Names: apiextensionsv1.CustomResourceDefinitionNames{ + Plural: "tridentactionsnapshotrestores", + Singular: "tridentactionsnapshotrestore", + Kind: "TridentActionSnapshotRestore", + ShortNames: []string{"tasr"}, + Categories: []string{"trident", "trident-external"}, + }, + Scope: "Namespaced", + Versions: []apiextensionsv1.CustomResourceDefinitionVersion{ + { + Name: "v1", + Served: true, + Storage: true, + Schema: &schema, + AdditionalPrinterColumns: []apiextensionsv1.CustomResourceColumnDefinition{ + { + Name: "Namespace", + Type: "string", + Description: "Namespace", + Priority: int32(0), + JSONPath: ".metadata.namespace", + }, + { + Name: "PVC", + Type: "string", + Description: "PVC", + Priority: int32(0), + JSONPath: ".spec.pvcName", + }, + { + Name: "Snapshot", + Type: "string", + Description: "Snapshot", + Priority: int32(0), + JSONPath: ".spec.volumeSnapshotName", + }, + { + Name: "State", + Type: "string", + Description: "State", + Priority: int32(0), + JSONPath: ".status.state", + }, + { + Name: "CompletionTime", + Type: "date", + Description: "CompletionTime", + Priority: int32(0), + JSONPath: ".status.completionTime", + }, + { + Name: "Message", + Type: "string", + Description: "Message", + Priority: int32(1), + JSONPath: ".status.message", + }, + }, + }, + }, + }, + } + + actualYAML := GetActionSnapshotRestoreCRDYAML() + + var actual apiextensionsv1.CustomResourceDefinition + assert.Nil(t, yaml.Unmarshal([]byte(actualYAML), &actual), "invalid YAML") + assert.True(t, reflect.DeepEqual(expected.TypeMeta, actual.TypeMeta)) + assert.True(t, reflect.DeepEqual(expected.ObjectMeta, actual.ObjectMeta)) + assert.True(t, reflect.DeepEqual(expected.Spec, actual.Spec)) +} + func TestGetCSIDriverYAML(t *testing.T) { name := "csi.trident.netapp.io" required := true diff --git a/core/orchestrator_core.go b/core/orchestrator_core.go index 3f23bd25d..1b9438f04 100644 --- a/core/orchestrator_core.go +++ b/core/orchestrator_core.go @@ -3989,6 +3989,61 @@ func (o *TridentOrchestrator) updateSnapshot( return updatedSnapshot, nil } +// RestoreSnapshot restores a volume to the specified snapshot. The caller is responsible for ensuring this is +// the newest snapshot known to the container orchestrator. Any other snapshots that are newer than the specified +// one that are not known to the container orchestrator may be lost. +func (o *TridentOrchestrator) RestoreSnapshot(ctx context.Context, volumeName, snapshotName string) (err error) { + ctx = GenerateRequestContextForLayer(ctx, LogLayerCore) + + if o.bootstrapError != nil { + return o.bootstrapError + } + + defer recordTiming("snapshot_restore", &err)() + + o.mutex.Lock() + defer o.mutex.Unlock() + defer o.updateMetrics() + + volume, ok := o.volumes[volumeName] + if !ok { + return errors.NotFoundError(fmt.Sprintf("volume %s not found", volumeName)) + } + + snapshotID := storage.MakeSnapshotID(volumeName, snapshotName) + snapshot, ok := o.snapshots[snapshotID] + if !ok { + return errors.NotFoundError(fmt.Sprintf("snapshot %s not found on volume %s", snapshotName, volumeName)) + } + + backend, ok := o.backends[volume.BackendUUID] + if !ok { + return errors.NotFoundError(fmt.Sprintf("backend %s not found", volume.BackendUUID)) + } + + logFields := LogFields{ + "volume": snapshot.Config.VolumeName, + "snapshot": snapshot.Config.Name, + "backend": backend.Name(), + } + + // Ensure volume is not attached to any containers + if len(o.volumePublications.ListPublicationsForVolume(volumeName)) > 0 { + err = errors.New("cannot restore attached volume to snapshot") + Logc(ctx).WithFields(logFields).WithError(err).Error("Unable to restore volume to snapshot.") + return err + } + + // Restore the snapshot + if err = backend.RestoreSnapshot(ctx, snapshot.Config, volume.Config); err != nil { + Logc(ctx).WithFields(logFields).WithError(err).Error("Unable to restore volume to snapshot.") + return err + } + + Logc(ctx).WithFields(logFields).Info("Restored volume to snapshot.") + return nil +} + // deleteSnapshot does the necessary work to delete a snapshot entirely. It does // not construct a transaction, nor does it take locks; it assumes that the caller will // take care of both of these. diff --git a/core/orchestrator_core_test.go b/core/orchestrator_core_test.go index ed6529ff4..a4668a1d6 100644 --- a/core/orchestrator_core_test.go +++ b/core/orchestrator_core_test.go @@ -6388,6 +6388,162 @@ func TestCreateSnapshotError(t *testing.T) { } } +func TestRestoreSnapshot_BootstrapError(t *testing.T) { + snapName := "snap" + volName := "vol" + + o := getOrchestrator(t, false) + o.bootstrapError = errors.New("failed") + + err := o.RestoreSnapshot(ctx(), volName, snapName) + + assert.Error(t, err, "RestoreSnapshot should have failed") +} + +func TestRestoreSnapshot_VolumeNotFound(t *testing.T) { + snapName := "snap" + volName := "vol" + + o := getOrchestrator(t, false) + + err := o.RestoreSnapshot(ctx(), volName, snapName) + + assert.Error(t, err, "RestoreSnapshot should have failed") +} + +func TestRestoreSnapshot_SnapshotNotFound(t *testing.T) { + o := getOrchestrator(t, false) + + backendUUID := "abcd" + snapName := "snap" + volName := "vol" + volConfig := &storage.VolumeConfig{Name: volName} + + o.volumes[volName] = &storage.Volume{Config: volConfig, BackendUUID: backendUUID} + + err := o.RestoreSnapshot(ctx(), volName, snapName) + + assert.Error(t, err, "RestoreSnapshot should have failed") +} + +func TestRestoreSnapshot_BackendNotFound(t *testing.T) { + o := getOrchestrator(t, false) + + backendUUID := "abcd" + snapName := "snap" + volName := "vol" + snapID := storage.MakeSnapshotID(volName, snapName) + snapConfig := &storage.SnapshotConfig{Name: snapName, VolumeName: volName} + volConfig := &storage.VolumeConfig{Name: volName} + + o.snapshots[snapID] = &storage.Snapshot{Config: snapConfig} + o.volumes[volName] = &storage.Volume{Config: volConfig, BackendUUID: backendUUID} + + err := o.RestoreSnapshot(ctx(), volName, snapName) + + assert.Error(t, err, "RestoreSnapshot should have failed") +} + +func TestRestoreSnapshot_VolumeHasPublications(t *testing.T) { + o := getOrchestrator(t, false) + + backendUUID := "abcd" + backendName := "xyz" + snapName := "snap" + volName := "vol" + nodeName := "node" + snapID := storage.MakeSnapshotID(volName, snapName) + snapConfig := &storage.SnapshotConfig{Name: snapName, VolumeName: volName} + volConfig := &storage.VolumeConfig{Name: volName} + node := &utils.Node{Name: nodeName} + publication := &utils.VolumePublication{ + Name: volName + "/" + nodeName, + NodeName: nodeName, + VolumeName: volName, + ReadOnly: false, + AccessMode: 1, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + mockBackend := mockstorage.NewMockBackend(mockCtrl) + + mockBackend.EXPECT().Name().Return(backendName).AnyTimes() + mockBackend.EXPECT().GetDriverName().Return("driverName").AnyTimes() + mockBackend.EXPECT().State().Return(storage.Online).AnyTimes() + mockBackend.EXPECT().BackendUUID().Return(backendUUID).AnyTimes() + + o.snapshots[snapID] = &storage.Snapshot{Config: snapConfig} + o.volumes[volName] = &storage.Volume{Config: volConfig, BackendUUID: backendUUID} + o.backends[backendUUID] = mockBackend + o.nodes.Set(nodeName, node) + _ = o.volumePublications.Set(volName, nodeName, publication) + + err := o.RestoreSnapshot(ctx(), volName, snapName) + + assert.Error(t, err, "RestoreSnapshot should have failed") +} + +func TestRestoreSnapshot_RestoreFailed(t *testing.T) { + o := getOrchestrator(t, false) + + backendUUID := "abcd" + backendName := "xyz" + snapName := "snap" + volName := "vol" + snapID := storage.MakeSnapshotID(volName, snapName) + snapConfig := &storage.SnapshotConfig{Name: snapName, VolumeName: volName} + volConfig := &storage.VolumeConfig{Name: volName} + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + mockBackend := mockstorage.NewMockBackend(mockCtrl) + + mockBackend.EXPECT().Name().Return(backendName).AnyTimes() + mockBackend.EXPECT().GetDriverName().Return("driverName").AnyTimes() + mockBackend.EXPECT().State().Return(storage.Online).AnyTimes() + mockBackend.EXPECT().BackendUUID().Return(backendUUID).AnyTimes() + mockBackend.EXPECT().RestoreSnapshot(coreCtx, snapConfig, volConfig).Return(errors.New("failed")) + + o.snapshots[snapID] = &storage.Snapshot{Config: snapConfig} + o.volumes[volName] = &storage.Volume{Config: volConfig, BackendUUID: backendUUID} + o.backends[backendUUID] = mockBackend + + err := o.RestoreSnapshot(ctx(), volName, snapName) + + assert.Error(t, err, "RestoreSnapshot should have failed") +} + +func TestRestoreSnapshot(t *testing.T) { + o := getOrchestrator(t, false) + + backendUUID := "abcd" + backendName := "xyz" + snapName := "snap" + volName := "vol" + snapID := storage.MakeSnapshotID(volName, snapName) + snapConfig := &storage.SnapshotConfig{Name: snapName, VolumeName: volName} + volConfig := &storage.VolumeConfig{Name: volName} + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + mockBackend := mockstorage.NewMockBackend(mockCtrl) + + mockBackend.EXPECT().Name().Return(backendName).AnyTimes() + mockBackend.EXPECT().GetDriverName().Return("driverName").AnyTimes() + mockBackend.EXPECT().State().Return(storage.Online).AnyTimes() + mockBackend.EXPECT().BackendUUID().Return(backendUUID).AnyTimes() + mockBackend.EXPECT().RestoreSnapshot(coreCtx, snapConfig, volConfig).Return(nil) + + o.snapshots[snapID] = &storage.Snapshot{Config: snapConfig} + o.volumes[volName] = &storage.Volume{Config: volConfig, BackendUUID: backendUUID} + o.backends[backendUUID] = mockBackend + + err := o.RestoreSnapshot(ctx(), volName, snapName) + + assert.NoError(t, err, "RestoreSnapshot should not have failed") +} + func TestDeleteSnapshotError(t *testing.T) { backendUUID := "abcd" volName := "vol" diff --git a/core/types.go b/core/types.go index 1bf4c7e8b..adb7950f0 100644 --- a/core/types.go +++ b/core/types.go @@ -63,6 +63,7 @@ type Orchestrator interface { ListSnapshotsByName(ctx context.Context, snapshotName string) ([]*storage.SnapshotExternal, error) ListSnapshotsForVolume(ctx context.Context, volumeName string) ([]*storage.SnapshotExternal, error) ReadSnapshotsForVolume(ctx context.Context, volumeName string) ([]*storage.SnapshotExternal, error) + RestoreSnapshot(ctx context.Context, volumeName, snapshotName string) error DeleteSnapshot(ctx context.Context, volumeName, snapshotName string) error AddStorageClass(ctx context.Context, scConfig *storageclass.Config) (*storageclass.External, error) diff --git a/deploy/bundle_post_1_25.yaml b/deploy/bundle_post_1_25.yaml index 248d36e94..13a790dd8 100644 --- a/deploy/bundle_post_1_25.yaml +++ b/deploy/bundle_post_1_25.yaml @@ -224,6 +224,8 @@ rules: - tridentactionmirrorupdates/status - tridentsnapshotinfos - tridentsnapshotinfos/status + - tridentactionsnapshotrestores + - tridentactionsnapshotrestores/status - tridentprovisioners # Required for Tprov - tridentprovisioners/status # Required to update Tprov's status section - tridentorchestrators # Required for Torc diff --git a/deploy/bundle_pre_1_25.yaml b/deploy/bundle_pre_1_25.yaml index 1c701e975..6f92c9e2f 100644 --- a/deploy/bundle_pre_1_25.yaml +++ b/deploy/bundle_pre_1_25.yaml @@ -224,6 +224,8 @@ rules: - tridentactionmirrorupdates/status - tridentsnapshotinfos - tridentsnapshotinfos/status + - tridentactionsnapshotrestores + - tridentactionsnapshotrestores/status - tridentprovisioners # Required for Tprov - tridentprovisioners/status # Required to update Tprov's status section - tridentorchestrators # Required for Torc diff --git a/deploy/clusterrole_post_1_25.yaml b/deploy/clusterrole_post_1_25.yaml index 8c7a98f80..bb50ba652 100644 --- a/deploy/clusterrole_post_1_25.yaml +++ b/deploy/clusterrole_post_1_25.yaml @@ -216,6 +216,8 @@ rules: - tridentactionmirrorupdates/status - tridentsnapshotinfos - tridentsnapshotinfos/status + - tridentactionsnapshotrestores + - tridentactionsnapshotrestores/status - tridentprovisioners # Required for Tprov - tridentprovisioners/status # Required to update Tprov's status section - tridentorchestrators # Required for Torc diff --git a/deploy/clusterrole_pre_1_25.yaml b/deploy/clusterrole_pre_1_25.yaml index 57e72cae2..dea95508c 100644 --- a/deploy/clusterrole_pre_1_25.yaml +++ b/deploy/clusterrole_pre_1_25.yaml @@ -216,6 +216,8 @@ rules: - tridentactionmirrorupdates/status - tridentsnapshotinfos - tridentsnapshotinfos/status + - tridentactionsnapshotrestores + - tridentactionsnapshotrestores/status - tridentprovisioners # Required for Tprov - tridentprovisioners/status # Required to update Tprov's status section - tridentorchestrators # Required for Torc diff --git a/frontend/crd/crd_controller.go b/frontend/crd/crd_controller.go index a3fb1afa6..bbfa27f7b 100644 --- a/frontend/crd/crd_controller.go +++ b/frontend/crd/crd_controller.go @@ -46,12 +46,13 @@ const ( EventForceUpdate EventType = "forceupdate" EventDelete EventType = "delete" - ObjectTypeTridentBackendConfig string = "TridentBackendConfig" - ObjectTypeTridentBackend string = "TridentBackend" - ObjectTypeSecret string = "secret" - ObjectTypeTridentMirrorRelationship string = "TridentMirrorRelationship" - ObjectTypeTridentActionMirrorUpdate string = "TridentActionMirrorUpdate" - ObjectTypeTridentSnapshotInfo string = "TridentSnapshotInfo" + ObjectTypeTridentBackendConfig string = "TridentBackendConfig" + ObjectTypeTridentBackend string = "TridentBackend" + ObjectTypeSecret string = "secret" + ObjectTypeTridentMirrorRelationship string = "TridentMirrorRelationship" + ObjectTypeTridentActionMirrorUpdate string = "TridentActionMirrorUpdate" + ObjectTypeTridentSnapshotInfo string = "TridentSnapshotInfo" + ObjectTypeTridentActionSnapshotRestore string = "TridentActionSnapshotRestore" OperationStatusSuccess string = "Success" OperationStatusFailed string = "Failed" @@ -156,6 +157,10 @@ type TridentCrdController struct { secretsLister v1.SecretLister secretsSynced cache.InformerSynced + // TridentSnapshot CRD handling + actionSnapshotRestoreLister listers.TridentActionSnapshotRestoreLister + actionSnapshotRestoreSynced cache.InformerSynced + // workqueue is a rate limited work queue. This is used to queue work to be // processed instead of performing it as soon as a change happens. This // means we can ensure we only process a fixed amount of resources at a @@ -222,6 +227,7 @@ func newTridentCrdControllerImpl( volumePublicationInformer := crdInformer.TridentVolumePublications() snapshotInformer := crdInformer.TridentSnapshots() secretInformer := kubeInformer.Secrets() + actionSnapshotRestoreInformer := allNSCrdInformer.TridentActionSnapshotRestores() // Create event broadcaster // Add our types to the default Kubernetes Scheme so Events can be logged. @@ -232,43 +238,45 @@ func newTridentCrdControllerImpl( recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: controllerAgentName}) controller := &TridentCrdController{ - orchestrator: orchestrator, - kubeClientset: kubeClientset, - snapshotClientSet: snapshotClientset, - crdClientset: crdClientset, - crdControllerStopChan: make(chan struct{}), - crdInformerFactory: crdInformerFactory, - crdInformer: crdInformer, - txnInformerFactory: txnInformerFactory, - txnInformer: txnInformer, - kubeInformerFactory: kubeInformerFactory, - kubeInformer: kubeInformer, - backendsLister: backendInformer.Lister(), - backendsSynced: backendInformer.Informer().HasSynced, - backendConfigsLister: backendConfigInformer.Lister(), - backendConfigsSynced: backendConfigInformer.Informer().HasSynced, - mirrorLister: mirrorInformer.Lister(), - mirrorSynced: mirrorInformer.Informer().HasSynced, - actionMirrorUpdateLister: actionMirrorUpdateInformer.Lister(), - actionMirrorUpdatesSynced: actionMirrorUpdateInformer.Informer().HasSynced, - snapshotInfoLister: snapshotInfoInformer.Lister(), - snapshotInfoSynced: snapshotInfoInformer.Informer().HasSynced, - nodesLister: nodeInformer.Lister(), - nodesSynced: nodeInformer.Informer().HasSynced, - storageClassesLister: storageClassInformer.Lister(), - storageClassesSynced: storageClassInformer.Informer().HasSynced, - transactionsLister: transactionInformer.Lister(), - transactionsSynced: transactionInformer.Informer().HasSynced, - versionsLister: versionInformer.Lister(), - versionsSynced: versionInformer.Informer().HasSynced, - volumesLister: volumeInformer.Lister(), - volumesSynced: volumeInformer.Informer().HasSynced, - volumePublicationsLister: volumePublicationInformer.Lister(), - volumePublicationsSynced: volumePublicationInformer.Informer().HasSynced, - snapshotsLister: snapshotInformer.Lister(), - snapshotsSynced: snapshotInformer.Informer().HasSynced, - secretsLister: secretInformer.Lister(), - secretsSynced: secretInformer.Informer().HasSynced, + orchestrator: orchestrator, + kubeClientset: kubeClientset, + snapshotClientSet: snapshotClientset, + crdClientset: crdClientset, + crdControllerStopChan: make(chan struct{}), + crdInformerFactory: crdInformerFactory, + crdInformer: crdInformer, + txnInformerFactory: txnInformerFactory, + txnInformer: txnInformer, + kubeInformerFactory: kubeInformerFactory, + kubeInformer: kubeInformer, + backendsLister: backendInformer.Lister(), + backendsSynced: backendInformer.Informer().HasSynced, + backendConfigsLister: backendConfigInformer.Lister(), + backendConfigsSynced: backendConfigInformer.Informer().HasSynced, + mirrorLister: mirrorInformer.Lister(), + mirrorSynced: mirrorInformer.Informer().HasSynced, + actionMirrorUpdateLister: actionMirrorUpdateInformer.Lister(), + actionMirrorUpdatesSynced: actionMirrorUpdateInformer.Informer().HasSynced, + snapshotInfoLister: snapshotInfoInformer.Lister(), + snapshotInfoSynced: snapshotInfoInformer.Informer().HasSynced, + nodesLister: nodeInformer.Lister(), + nodesSynced: nodeInformer.Informer().HasSynced, + storageClassesLister: storageClassInformer.Lister(), + storageClassesSynced: storageClassInformer.Informer().HasSynced, + transactionsLister: transactionInformer.Lister(), + transactionsSynced: transactionInformer.Informer().HasSynced, + versionsLister: versionInformer.Lister(), + versionsSynced: versionInformer.Informer().HasSynced, + volumesLister: volumeInformer.Lister(), + volumesSynced: volumeInformer.Informer().HasSynced, + volumePublicationsLister: volumePublicationInformer.Lister(), + volumePublicationsSynced: volumePublicationInformer.Informer().HasSynced, + snapshotsLister: snapshotInformer.Lister(), + snapshotsSynced: snapshotInformer.Informer().HasSynced, + secretsLister: secretInformer.Lister(), + secretsSynced: secretInformer.Informer().HasSynced, + actionSnapshotRestoreLister: actionSnapshotRestoreInformer.Lister(), + actionSnapshotRestoreSynced: actionSnapshotRestoreInformer.Informer().HasSynced, workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), crdControllerQueueName), recorder: recorder, @@ -307,6 +315,10 @@ func newTridentCrdControllerImpl( DeleteFunc: controller.deleteCRHandler, }) + _, _ = actionSnapshotRestoreInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: controller.addCRHandler, + }) + _, _ = secretInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ // Do not handle AddFunc here otherwise everytime trident is restarted, // there will be unwarranted reconciles and backend initializations @@ -597,6 +609,8 @@ func (c *TridentCrdController) processNextWorkItem() bool { handleFunction = c.handleActionMirrorUpdate case ObjectTypeTridentSnapshotInfo: handleFunction = c.handleTridentSnapshotInfo + case ObjectTypeTridentActionSnapshotRestore: + handleFunction = c.handleActionSnapshotRestore default: return fmt.Errorf("unknown objectType in the workqueue: %v", keyItem.objectType) } diff --git a/frontend/crd/snapshot_restore.go b/frontend/crd/snapshot_restore.go new file mode 100644 index 000000000..714a73d81 --- /dev/null +++ b/frontend/crd/snapshot_restore.go @@ -0,0 +1,278 @@ +// Copyright 2023 NetApp, Inc. All Rights Reserved. + +package crd + +import ( + "context" + "fmt" + + v1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/cache" + + . "github.com/netapp/trident/logging" + netappv1 "github.com/netapp/trident/persistent_store/crd/apis/netapp/v1" + "github.com/netapp/trident/storage" + "github.com/netapp/trident/utils/errors" +) + +func (c *TridentCrdController) handleActionSnapshotRestore(keyItem *KeyItem) (restoreError error) { + Logc(keyItem.ctx).Debug(">>>> TridentCrdController#runActionSnapshotRestore") + defer Logc(keyItem.ctx).Debug("<<<< TridentCrdController#runActionSnapshotRestore") + + key := keyItem.key + ctx := keyItem.ctx + + // This one-shot action runs on Add and does not need Update or Delete + if keyItem.event != EventAdd { + return nil + } + + // Convert the namespace/name string into a distinct namespace and name. If this fails, no + // retry is likely to succeed, so return the error to forget this action. We can't determine + // the CR, so no CR update is possible. + namespace, name, err := cache.SplitMetaNamespaceKey(key) + if err != nil { + Logc(ctx).WithField("key", key).Error("Invalid key.") + return err + } + + // Ensure the CR is new and valid. This may return ReconcileDeferred if it should be retried. + // Any other error returned here indicates a problem with the CR (i.e. it's not new or has been + // deleted), so no CR update is needed. + actionCR, err := c.validateActionSnapshotRestoreCR(ctx, namespace, name) + if err != nil { + return err + } + + // Now that all prechecks that don't require a CR update are done, we can add a deferred + // function that updates the CR based on any additional errors encountered. + defer func() { + // If we want to be retried, we won't update the CR. + if errors.IsReconcileDeferredError(restoreError) { + return + } + + // Update CR with finalizer removal and success/failed status + if err = c.updateActionSnapshotRestoreCRComplete(ctx, namespace, name, restoreError); err != nil { + Logc(ctx).WithField("key", key).WithError(err).Error( + "Could not update snapshot restore action CR with final result.") + } + }() + + // Detect a CR that is in progress but is not a retry from the workqueue. This can only happen + // if Trident restarted while processing a CR, in which case we move the CR directly to Failed. + if actionCR.InProgress() && !keyItem.isRetry { + return fmt.Errorf("in-progress TridentActionSnapshotRestore %s detected at startup, marking as Failed", + keyItem.key) + } + + // Get PV and VSC corresponding to PVC and VS. All of the work of this method constitutes pre-checks + // and should be quick, so we do this before setting the CR to in-progress. Any error other than + // ReconcileDeferred (not applicable here) will update the CR with a failed status and the operation + // will not be retried. + tridentVolume, tridentSnapshot, restoreError := c.getKubernetesObjectsForActionSnapshotRestore(ctx, actionCR) + if restoreError != nil { + return + } + + // Update CR with finalizers and in-progress status + restoreError = c.updateActionSnapshotRestoreCRInProgress(ctx, namespace, name) + if restoreError != nil { + return + } + + // Invoke snapshot restore + restoreError = c.orchestrator.RestoreSnapshot(ctx, tridentVolume, tridentSnapshot) + if errors.IsInProgressError(restoreError) { + restoreError = errors.WrapWithReconcileDeferredError(restoreError, "reconcile deferred") + } + + return +} + +func (c *TridentCrdController) validateActionSnapshotRestoreCR( + ctx context.Context, namespace, name string, +) (*netappv1.TridentActionSnapshotRestore, error) { + // Get the resource with this namespace/name + actionCR, err := c.crdClientset.TridentV1().TridentActionSnapshotRestores(namespace).Get(ctx, name, getOpts) + if apierrors.IsNotFound(err) { + Logc(ctx).Debug("Snapshot restore action in work queue no longer exists.") + return nil, err + } + if err != nil { + return nil, errors.WrapWithReconcileDeferredError(err, "reconcile deferred") + } + + if !actionCR.IsNew() && !actionCR.InProgress() { + return nil, fmt.Errorf("snapshot restore action %s/%s is not new or in progress", namespace, name) + } + + return actionCR, nil +} + +func (c *TridentCrdController) updateActionSnapshotRestoreCRInProgress( + ctx context.Context, namespace, name string, +) error { + // Get the resource with this namespace/name + actionCR, err := c.crdClientset.TridentV1().TridentActionSnapshotRestores(namespace).Get(ctx, name, getOpts) + if apierrors.IsNotFound(err) { + Logc(ctx).Debug("Snapshot restore action in work queue no longer exists.") + return err + } + if err != nil { + return errors.WrapWithReconcileDeferredError(err, "reconcile deferred") + } + + if !actionCR.HasTridentFinalizers() { + actionCR.AddTridentFinalizers() + } + actionCR.Status.State = netappv1.TridentActionStateInProgress + actionCR.Status.Message = "" + startTime := metav1.Now() + actionCR.Status.StartTime = &startTime + + _, err = c.crdClientset.TridentV1().TridentActionSnapshotRestores(namespace).Update(ctx, actionCR, updateOpts) + if apierrors.IsNotFound(err) { + Logc(ctx).Debug("Snapshot restore action in work queue no longer exists.") + return err + } + if err != nil { + return errors.WrapWithReconcileDeferredError(err, "reconcile deferred") + } + + return nil +} + +func (c *TridentCrdController) updateActionSnapshotRestoreCRComplete( + ctx context.Context, namespace, name string, restoreError error, +) error { + // Get the resource with this namespace/name + actionCR, err := c.crdClientset.TridentV1().TridentActionSnapshotRestores(namespace).Get(ctx, name, getOpts) + if apierrors.IsNotFound(err) { + Logc(ctx).Debug("Snapshot restore action in work queue no longer exists.") + return nil + } + if err != nil { + return err + } + + if actionCR.HasTridentFinalizers() { + actionCR.RemoveTridentFinalizers() + } + + if restoreError == nil { + actionCR.Status.State = netappv1.TridentActionStateSucceeded + actionCR.Status.Message = "" + } else { + actionCR.Status.State = netappv1.TridentActionStateFailed + actionCR.Status.Message = restoreError.Error() + } + + completionTime := metav1.Now() + actionCR.Status.CompletionTime = &completionTime + + _, err = c.crdClientset.TridentV1().TridentActionSnapshotRestores(namespace).Update(ctx, actionCR, updateOpts) + if apierrors.IsNotFound(err) { + Logc(ctx).Debug("Snapshot restore action in work queue no longer exists.") + return nil + } + return err +} + +func (c *TridentCrdController) getKubernetesObjectsForActionSnapshotRestore( + ctx context.Context, actionCR *netappv1.TridentActionSnapshotRestore, +) (tridentVolume, tridentSnapshot string, err error) { + // Get PVC + pvc, err := c.kubeClientset.CoreV1().PersistentVolumeClaims(actionCR.Namespace).Get( + ctx, actionCR.Spec.PVCName, getOpts) + if err != nil { + return + } + + // Ensure PVC is bound + if pvc.Status.Phase != v1.ClaimBound { + err = fmt.Errorf("PVC %s/%s is not bound to a PV", pvc.Namespace, pvc.Name) + return + } + + // Get the PV to which the PVC is bound and validate its status + pv, err := c.kubeClientset.CoreV1().PersistentVolumes().Get(ctx, pvc.Spec.VolumeName, getOpts) + if err != nil { + return + } + + // Ensure PV is bound to the PVC + if pv.Status.Phase != v1.VolumeBound || pv.Spec.ClaimRef == nil || + pv.Spec.ClaimRef.Namespace != pvc.Namespace || pv.Spec.ClaimRef.Name != pvc.Name { + err = fmt.Errorf("PV %s is not bound to PVC %s/%s", pv.Name, pvc.Namespace, pvc.Name) + return + } + + // Get VolumeSnapshot + vs, err := c.snapshotClientSet.SnapshotV1().VolumeSnapshots(actionCR.Namespace).Get( + ctx, actionCR.Spec.VolumeSnapshotName, getOpts) + if err != nil { + return + } + + // Ensure VS is bound and validate its status + if vs.Status.BoundVolumeSnapshotContentName == nil || vs.Status.CreationTime == nil || + vs.Status.CreationTime.IsZero() || vs.Status.ReadyToUse == nil || !*vs.Status.ReadyToUse { + err = fmt.Errorf("volume snapshot %s/%s is not bound and ready to use", vs.Namespace, vs.Name) + return + } + + // Get VolumeSnapshotContent + vsc, err := c.snapshotClientSet.SnapshotV1().VolumeSnapshotContents().Get( + ctx, *vs.Status.BoundVolumeSnapshotContentName, getOpts) + if err != nil { + return + } + + // Ensure VSC is bound to the VS and is ready to use and has a valid handle + if vsc.Spec.VolumeSnapshotRef.Name != vs.Name || vsc.Spec.VolumeSnapshotRef.Namespace != vs.Namespace { + err = fmt.Errorf("volume snapshot content %s is not bound to snapshot %s/%s", vsc.Name, vs.Namespace, vs.Name) + return + } + if vsc.Status.ReadyToUse == nil || !*vsc.Status.ReadyToUse { + err = fmt.Errorf("volume snapshot content %s is not ready to use", vsc.Name) + return + } + if vsc.Status.SnapshotHandle == nil { + err = fmt.Errorf("volume snapshot content %s does not have a snapshot handle", vsc.Name) + return + } + tridentVolume, tridentSnapshot, err = storage.ParseSnapshotID(*vsc.Status.SnapshotHandle) + if err != nil { + err = fmt.Errorf("volume snapshot content %s does not have a valid snapshot handle", vsc.Name) + return + } + + // Ensure the VS is the most recent one on the PVC + snapshotList, err := c.snapshotClientSet.SnapshotV1().VolumeSnapshots(actionCR.Namespace).List(ctx, listOpts) + if err != nil { + return + } + + for _, snapshot := range snapshotList.Items { + + // Skip the one we're restoring + if snapshot.Namespace == vs.Namespace && snapshot.Name == vs.Name { + continue + } + + if snapshot.Spec.Source.PersistentVolumeClaimName != nil && + *snapshot.Spec.Source.PersistentVolumeClaimName == pvc.Name && + snapshot.Status.CreationTime != nil && + !snapshot.Status.CreationTime.IsZero() && + snapshot.Status.CreationTime.After(vs.Status.CreationTime.Time) { + err = fmt.Errorf("volume snapshot %s is not the newest snapshot of PVC %s/%s", + vs.Name, pvc.Namespace, pvc.Name) + return + } + } + + return +} diff --git a/frontend/crd/snapshot_restore_test.go b/frontend/crd/snapshot_restore_test.go new file mode 100644 index 000000000..4b8c0f580 --- /dev/null +++ b/frontend/crd/snapshot_restore_test.go @@ -0,0 +1,785 @@ +// Copyright 2023 NetApp, Inc. All Rights Reserved. + +package crd + +import ( + "fmt" + "testing" + "time" + + "github.com/golang/mock/gomock" + snapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1" + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + mockcore "github.com/netapp/trident/mocks/mock_core" + netappv1 "github.com/netapp/trident/persistent_store/crd/apis/netapp/v1" + "github.com/netapp/trident/utils" + "github.com/netapp/trident/utils/errors" +) + +const ( + snapRestorePVC1 = "pvc1" + snapRestorePV1 = "pv1" + snapRestoreSnap1 = "snap1" + snapRestoreSnap2 = "snap2" + snapRestoreSnap3 = "snap3" + snapRestoreTsnap1 = "snapshot1" + snapRestoreTsnap3 = "snapshot3" + snapRestoreVSC1 = "snapcontent1" + snapRestoreVSC2 = "snapcontent2" + snapRestoreVSC3 = "snapcontent3" + snapRestoreSnapHandle1 = "pv1/snapshot1" + snapRestoreSnapHandle2 = "pv1/snapshot2" + snapRestoreSnapHandle3 = "pv1/snapshot3" + tasr1 = "tasr1" +) + +func fakeSnapRestorePVC(pvcName, pvcNamespace, pvName string) *v1.PersistentVolumeClaim { + return &v1.PersistentVolumeClaim{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "PersistentVolumeClaim", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: pvcNamespace, + Name: pvcName, + }, + Spec: v1.PersistentVolumeClaimSpec{ + VolumeName: pvName, + }, + Status: v1.PersistentVolumeClaimStatus{ + Phase: v1.ClaimBound, + }, + } +} + +func fakePV(pvcName, pvcNamespace, pvName string) *v1.PersistentVolume { + return &v1.PersistentVolume{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "PersistentVolume", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: pvName, + }, + Spec: v1.PersistentVolumeSpec{ + ClaimRef: &v1.ObjectReference{ + Namespace: pvcNamespace, + Name: pvcName, + }, + }, + Status: v1.PersistentVolumeStatus{ + Phase: v1.VolumeBound, + }, + } +} + +func fakeVS(vsName, vsNamespace, vscName, pvcName string, creationTime time.Time) *snapshotv1.VolumeSnapshot { + return &snapshotv1.VolumeSnapshot{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "snapshot.storage.k8s.io/v1", + Kind: "VolumeSnapshot", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: vsName, + Namespace: vsNamespace, + }, + Spec: snapshotv1.VolumeSnapshotSpec{ + Source: snapshotv1.VolumeSnapshotSource{ + PersistentVolumeClaimName: utils.Ptr(pvcName), + }, + }, + Status: &snapshotv1.VolumeSnapshotStatus{ + BoundVolumeSnapshotContentName: &vscName, + CreationTime: utils.Ptr(metav1.NewTime(creationTime)), + ReadyToUse: utils.Ptr(true), + }, + } +} + +func fakeVSC(vsName, vsNamespace, vscName, vsHandle string, creationTime time.Time) *snapshotv1.VolumeSnapshotContent { + return &snapshotv1.VolumeSnapshotContent{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "snapshot.storage.k8s.io/v1", + Kind: "VolumeSnapshotContent", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: vscName, + }, + Spec: snapshotv1.VolumeSnapshotContentSpec{ + VolumeSnapshotRef: v1.ObjectReference{ + Namespace: vsNamespace, + Name: vsName, + }, + }, + Status: &snapshotv1.VolumeSnapshotContentStatus{ + SnapshotHandle: utils.Ptr(vsHandle), + CreationTime: utils.Ptr(creationTime.UnixNano()), + ReadyToUse: utils.Ptr(true), + }, + } +} + +func fakeTASR(name, namespace, pvcName, vsName string) *netappv1.TridentActionSnapshotRestore { + return &netappv1.TridentActionSnapshotRestore{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "snapshot.storage.k8s.io/v1", + Kind: "VolumeSnapshotContent", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: netappv1.TridentActionSnapshotRestoreSpec{ + PVCName: pvcName, + VolumeSnapshotName: vsName, + }, + } +} + +func TestHandleActionSnapshotRestore(t *testing.T) { + mockCtrl := gomock.NewController(t) + orchestrator := mockcore.NewMockOrchestrator(mockCtrl) + + tridentNamespace := "trident" + kubeClient := GetTestKubernetesClientset() + snapClient := GetTestSnapshotClientset() + crdClient := GetTestCrdClientset() + crdController, err := newTridentCrdControllerImpl(orchestrator, tridentNamespace, kubeClient, snapClient, crdClient) + if err != nil { + t.Fatalf("cannot create Trident CRD controller frontend; %v", err) + } + + orchestrator.EXPECT().RestoreSnapshot(gomock.Any(), snapRestorePV1, snapRestoreTsnap3).Return(nil).Times(1) + + // Activate the CRD controller and start monitoring + if err = crdController.Activate(); err != nil { + t.Fatalf("error while activating; %v", err) + } + time.Sleep(250 * time.Millisecond) + + pvc := fakeSnapRestorePVC(snapRestorePVC1, namespace1, snapRestorePV1) + _, _ = kubeClient.CoreV1().PersistentVolumeClaims(namespace1).Create(ctx(), pvc, createOpts) + + pv := fakePV(snapRestorePVC1, namespace1, snapRestorePV1) + _, _ = kubeClient.CoreV1().PersistentVolumes().Create(ctx(), pv, createOpts) + + vs1Time := time.Now() + vs2Time := vs1Time.Add(1 * time.Second) + vs3Time := vs2Time.Add(1 * time.Second) + + vs1 := fakeVS(snapRestoreSnap1, namespace1, snapRestoreVSC1, snapRestorePVC1, vs1Time) + _, _ = snapClient.SnapshotV1().VolumeSnapshots(namespace1).Create(ctx(), vs1, createOpts) + + vsc1 := fakeVSC(snapRestoreSnap1, namespace1, snapRestoreVSC1, snapRestoreSnapHandle1, vs1Time) + _, _ = snapClient.SnapshotV1().VolumeSnapshotContents().Create(ctx(), vsc1, createOpts) + + vs2 := fakeVS(snapRestoreSnap2, namespace1, snapRestoreVSC2, snapRestorePVC1, vs2Time) + _, _ = snapClient.SnapshotV1().VolumeSnapshots(namespace1).Create(ctx(), vs2, createOpts) + + vsc2 := fakeVSC(snapRestoreSnap2, namespace1, snapRestoreVSC2, snapRestoreSnapHandle2, vs2Time) + _, _ = snapClient.SnapshotV1().VolumeSnapshotContents().Create(ctx(), vsc2, createOpts) + + vs3 := fakeVS(snapRestoreSnap3, namespace1, snapRestoreVSC3, snapRestorePVC1, vs3Time) + _, _ = snapClient.SnapshotV1().VolumeSnapshots(namespace1).Create(ctx(), vs3, createOpts) + + vsc3 := fakeVSC(snapRestoreSnap3, namespace1, snapRestoreVSC3, snapRestoreSnapHandle3, vs3Time) + _, _ = snapClient.SnapshotV1().VolumeSnapshotContents().Create(ctx(), vsc3, createOpts) + + tasr := fakeTASR(tasr1, namespace1, snapRestorePVC1, snapRestoreSnap3) + _, _ = crdClient.TridentV1().TridentActionSnapshotRestores(namespace1).Create(ctx(), tasr, createOpts) + + // Wait until the operation completes + for i := 0; i < 20; i++ { + time.Sleep(250 * time.Millisecond) + + tasr, err = crdClient.TridentV1().TridentActionSnapshotRestores(namespace1).Get(ctx(), tasr1, getOpts) + if err != nil { + if apierrors.IsNotFound(err) { + continue + } + break + } else if tasr.IsComplete() { + break + } + } + + assert.Nil(t, err, "err should be nil") + assert.True(t, tasr.Succeeded(), "TASR operation failed") + assert.NotZero(t, tasr.Status.StartTime.Time, "Start time should not be zero") + assert.NotZero(t, tasr.Status.CompletionTime.Time, "Completion time should not be zero") + assert.False(t, tasr.Status.CompletionTime.Time.Before(tasr.Status.StartTime.Time), + "Completion time is before start time") + + // Now that the main path has been validated, use the existing state to test some error paths + + _, err = crdController.validateActionSnapshotRestoreCR(ctx(), namespace1, tasr1) + + assert.Error(t, err, "Completed TASR should not have been accepted") + + _ = crdClient.TridentV1().TridentActionSnapshotRestores(namespace1).Delete(ctx(), tasr1, deleteOptions) + + // Wait until the TASR disappears + for i := 0; i < 20; i++ { + tasr, err = crdClient.TridentV1().TridentActionSnapshotRestores(namespace1).Get(ctx(), tasr1, getOpts) + if apierrors.IsNotFound(err) { + break + } + + time.Sleep(250 * time.Millisecond) + } + + tasr, err = crdController.validateActionSnapshotRestoreCR(ctx(), namespace1, tasr1) + + assert.True(t, apierrors.IsNotFound(err), "TASR should not have been found") +} + +func TestHandleActionSnapshotRestore_InProgressError(t *testing.T) { + mockCtrl := gomock.NewController(t) + orchestrator := mockcore.NewMockOrchestrator(mockCtrl) + + tridentNamespace := "trident" + kubeClient := GetTestKubernetesClientset() + snapClient := GetTestSnapshotClientset() + crdClient := GetTestCrdClientset() + crdController, err := newTridentCrdControllerImpl(orchestrator, tridentNamespace, kubeClient, snapClient, crdClient) + if err != nil { + t.Fatalf("cannot create Trident CRD controller frontend; %v", err) + } + + // Fail in the driver a couple times with InProgressError, expecting a retry + orchestrator.EXPECT().RestoreSnapshot(gomock.Any(), snapRestorePV1, snapRestoreTsnap3). + Return(errors.InProgressError("failed")).Times(2) + + // Succeed on the third call to the driver + orchestrator.EXPECT().RestoreSnapshot(gomock.Any(), snapRestorePV1, snapRestoreTsnap3).Return(nil).Times(1) + + // Activate the CRD controller and start monitoring + if err = crdController.Activate(); err != nil { + t.Fatalf("error while activating; %v", err) + } + time.Sleep(250 * time.Millisecond) + + pvc := fakeSnapRestorePVC(snapRestorePVC1, namespace1, snapRestorePV1) + _, _ = kubeClient.CoreV1().PersistentVolumeClaims(namespace1).Create(ctx(), pvc, createOpts) + + pv := fakePV(snapRestorePVC1, namespace1, snapRestorePV1) + _, _ = kubeClient.CoreV1().PersistentVolumes().Create(ctx(), pv, createOpts) + + vs1Time := time.Now() + vs2Time := vs1Time.Add(1 * time.Second) + vs3Time := vs2Time.Add(1 * time.Second) + + vs1 := fakeVS(snapRestoreSnap1, namespace1, snapRestoreVSC1, snapRestorePVC1, vs1Time) + _, _ = snapClient.SnapshotV1().VolumeSnapshots(namespace1).Create(ctx(), vs1, createOpts) + + vsc1 := fakeVSC(snapRestoreSnap1, namespace1, snapRestoreVSC1, snapRestoreSnapHandle1, vs1Time) + _, _ = snapClient.SnapshotV1().VolumeSnapshotContents().Create(ctx(), vsc1, createOpts) + + vs2 := fakeVS(snapRestoreSnap2, namespace1, snapRestoreVSC2, snapRestorePVC1, vs2Time) + _, _ = snapClient.SnapshotV1().VolumeSnapshots(namespace1).Create(ctx(), vs2, createOpts) + + vsc2 := fakeVSC(snapRestoreSnap2, namespace1, snapRestoreVSC2, snapRestoreSnapHandle2, vs2Time) + _, _ = snapClient.SnapshotV1().VolumeSnapshotContents().Create(ctx(), vsc2, createOpts) + + vs3 := fakeVS(snapRestoreSnap3, namespace1, snapRestoreVSC3, snapRestorePVC1, vs3Time) + _, _ = snapClient.SnapshotV1().VolumeSnapshots(namespace1).Create(ctx(), vs3, createOpts) + + vsc3 := fakeVSC(snapRestoreSnap3, namespace1, snapRestoreVSC3, snapRestoreSnapHandle3, vs3Time) + _, _ = snapClient.SnapshotV1().VolumeSnapshotContents().Create(ctx(), vsc3, createOpts) + + tasr := fakeTASR(tasr1, namespace1, snapRestorePVC1, snapRestoreSnap3) + _, _ = crdClient.TridentV1().TridentActionSnapshotRestores(namespace1).Create(ctx(), tasr, createOpts) + + // Wait until the operation completes + for i := 0; i < 20; i++ { + time.Sleep(250 * time.Millisecond) + + tasr, err = crdClient.TridentV1().TridentActionSnapshotRestores(namespace1).Get(ctx(), tasr1, getOpts) + if err != nil { + if apierrors.IsNotFound(err) { + continue + } + break + } else if tasr.IsComplete() { + break + } + } + + assert.True(t, tasr.Succeeded(), "TASR operation failed") +} + +func TestHandleActionSnapshotRestore_InProgressAtStartup(t *testing.T) { + mockCtrl := gomock.NewController(t) + orchestrator := mockcore.NewMockOrchestrator(mockCtrl) + + tridentNamespace := "trident" + kubeClient := GetTestKubernetesClientset() + snapClient := GetTestSnapshotClientset() + crdClient := GetTestCrdClientset() + crdController, err := newTridentCrdControllerImpl(orchestrator, tridentNamespace, kubeClient, snapClient, crdClient) + if err != nil { + t.Fatalf("cannot create Trident CRD controller frontend; %v", err) + } + + tasr := fakeTASR(tasr1, namespace1, snapRestorePVC1, snapRestoreSnap3) + tasr.Status.State = netappv1.TridentActionStateInProgress + _, _ = crdClient.TridentV1().TridentActionSnapshotRestores(namespace1).Create(ctx(), tasr, createOpts) + + // Activate the CRD controller and start monitoring + if err = crdController.Activate(); err != nil { + t.Fatalf("error while activating; %v", err) + } + + // Wait until the operation completes + for i := 0; i < 20; i++ { + time.Sleep(250 * time.Millisecond) + + tasr, err = crdClient.TridentV1().TridentActionSnapshotRestores(namespace1).Get(ctx(), tasr1, getOpts) + if err != nil { + if apierrors.IsNotFound(err) { + continue + } + break + } else if tasr.IsComplete() { + break + } + } + + assert.True(t, tasr.Failed(), "TASR operation did not fail") +} + +func TestHandleActionSnapshotRestore_WrongAction(t *testing.T) { + mockCtrl := gomock.NewController(t) + orchestrator := mockcore.NewMockOrchestrator(mockCtrl) + + tridentNamespace := "trident" + kubeClient := GetTestKubernetesClientset() + snapClient := GetTestSnapshotClientset() + crdClient := GetTestCrdClientset() + crdController, err := newTridentCrdControllerImpl(orchestrator, tridentNamespace, kubeClient, snapClient, crdClient) + if err != nil { + t.Fatalf("cannot create Trident CRD controller frontend; %v", err) + } + + keyItem := &KeyItem{ + key: "key", + event: EventUpdate, + ctx: ctx(), + objectType: ObjectTypeTridentActionSnapshotRestore, + } + + err = crdController.handleActionSnapshotRestore(keyItem) + + assert.NoError(t, err, "update KeyItem event should be ignored") + + keyItem.event = EventDelete + + err = crdController.handleActionSnapshotRestore(keyItem) + + assert.NoError(t, err, "delete KeyItem event should be ignored") +} + +func TestHandleActionSnapshotRestore_InvalidKey(t *testing.T) { + mockCtrl := gomock.NewController(t) + orchestrator := mockcore.NewMockOrchestrator(mockCtrl) + + tridentNamespace := "trident" + kubeClient := GetTestKubernetesClientset() + snapClient := GetTestSnapshotClientset() + crdClient := GetTestCrdClientset() + crdController, err := newTridentCrdControllerImpl(orchestrator, tridentNamespace, kubeClient, snapClient, crdClient) + if err != nil { + t.Fatalf("cannot create Trident CRD controller frontend; %v", err) + } + + keyItem := &KeyItem{ + key: "cluster/namespace/name", + event: EventAdd, + ctx: ctx(), + objectType: ObjectTypeTridentActionSnapshotRestore, + } + + err = crdController.handleActionSnapshotRestore(keyItem) + + assert.Error(t, err, "invalid KeyItem key did not fail") +} + +func TestHandleActionSnapshotRestore_ValidateFailure(t *testing.T) { + mockCtrl := gomock.NewController(t) + orchestrator := mockcore.NewMockOrchestrator(mockCtrl) + + tridentNamespace := "trident" + kubeClient := GetTestKubernetesClientset() + snapClient := GetTestSnapshotClientset() + crdClient := GetTestCrdClientset() + crdController, err := newTridentCrdControllerImpl(orchestrator, tridentNamespace, kubeClient, snapClient, crdClient) + if err != nil { + t.Fatalf("cannot create Trident CRD controller frontend; %v", err) + } + + keyItem := &KeyItem{ + key: "trident/tasr1", + event: EventAdd, + ctx: ctx(), + objectType: ObjectTypeTridentActionSnapshotRestore, + } + + err = crdController.handleActionSnapshotRestore(keyItem) + + assert.Error(t, err, "validation did not fail") +} + +func TestHandleActionSnapshotRestore_NotNewest(t *testing.T) { + mockCtrl := gomock.NewController(t) + orchestrator := mockcore.NewMockOrchestrator(mockCtrl) + + tridentNamespace := "trident" + kubeClient := GetTestKubernetesClientset() + snapClient := GetTestSnapshotClientset() + crdClient := GetTestCrdClientset() + crdController, err := newTridentCrdControllerImpl(orchestrator, tridentNamespace, kubeClient, snapClient, crdClient) + if err != nil { + t.Fatalf("cannot create Trident CRD controller frontend; %v", err) + } + + // Activate the CRD controller and start monitoring + if err = crdController.Activate(); err != nil { + t.Fatalf("error while activating; %v", err) + } + time.Sleep(250 * time.Millisecond) + + pvc := fakeSnapRestorePVC(snapRestorePVC1, namespace1, snapRestorePV1) + _, _ = kubeClient.CoreV1().PersistentVolumeClaims(namespace1).Create(ctx(), pvc, createOpts) + + pv := fakePV(snapRestorePVC1, namespace1, snapRestorePV1) + _, _ = kubeClient.CoreV1().PersistentVolumes().Create(ctx(), pv, createOpts) + + vs1Time := time.Now() + vs2Time := vs1Time.Add(1 * time.Second) + vs3Time := vs2Time.Add(1 * time.Second) + + vs1 := fakeVS(snapRestoreSnap1, namespace1, snapRestoreVSC1, snapRestorePVC1, vs1Time) + _, _ = snapClient.SnapshotV1().VolumeSnapshots(namespace1).Create(ctx(), vs1, createOpts) + + vsc1 := fakeVSC(snapRestoreSnap1, namespace1, snapRestoreVSC1, snapRestoreSnapHandle1, vs1Time) + _, _ = snapClient.SnapshotV1().VolumeSnapshotContents().Create(ctx(), vsc1, createOpts) + + vs2 := fakeVS(snapRestoreSnap2, namespace1, snapRestoreVSC2, snapRestorePVC1, vs2Time) + _, _ = snapClient.SnapshotV1().VolumeSnapshots(namespace1).Create(ctx(), vs2, createOpts) + + vsc2 := fakeVSC(snapRestoreSnap2, namespace1, snapRestoreVSC2, snapRestoreSnapHandle2, vs2Time) + _, _ = snapClient.SnapshotV1().VolumeSnapshotContents().Create(ctx(), vsc2, createOpts) + + vs3 := fakeVS(snapRestoreSnap3, namespace1, snapRestoreVSC3, snapRestorePVC1, vs3Time) + _, _ = snapClient.SnapshotV1().VolumeSnapshots(namespace1).Create(ctx(), vs3, createOpts) + + vsc3 := fakeVSC(snapRestoreSnap3, namespace1, snapRestoreVSC3, snapRestoreSnapHandle3, vs3Time) + _, _ = snapClient.SnapshotV1().VolumeSnapshotContents().Create(ctx(), vsc3, createOpts) + + tasr := fakeTASR(tasr1, namespace1, snapRestorePVC1, snapRestoreSnap2) + _, _ = crdClient.TridentV1().TridentActionSnapshotRestores(namespace1).Create(ctx(), tasr, createOpts) + + // Wait until the operation completes + for i := 0; i < 20; i++ { + time.Sleep(250 * time.Millisecond) + + tasr, err = crdClient.TridentV1().TridentActionSnapshotRestores(namespace1).Get(ctx(), tasr1, getOpts) + if err != nil { + if apierrors.IsNotFound(err) { + continue + } + break + } else if tasr.IsComplete() { + break + } + } + + assert.True(t, tasr.Failed(), "TASR operation failed") +} + +func TestUpdateActionSnapshotRestoreCRInProgress(t *testing.T) { + mockCtrl := gomock.NewController(t) + orchestrator := mockcore.NewMockOrchestrator(mockCtrl) + + tridentNamespace := "trident" + kubeClient := GetTestKubernetesClientset() + snapClient := GetTestSnapshotClientset() + crdClient := GetTestCrdClientset() + crdController, err := newTridentCrdControllerImpl(orchestrator, tridentNamespace, kubeClient, snapClient, crdClient) + if err != nil { + t.Fatalf("cannot create Trident CRD controller frontend; %v", err) + } + + err = crdController.updateActionSnapshotRestoreCRInProgress(ctx(), namespace1, tasr1) + + assert.True(t, apierrors.IsNotFound(err), "TASR should not have been found") + + tasr := fakeTASR(tasr1, namespace1, snapRestorePVC1, snapRestoreSnap1) + _, _ = crdClient.TridentV1().TridentActionSnapshotRestores(namespace1).Create(ctx(), tasr, createOpts) + + err = crdController.updateActionSnapshotRestoreCRInProgress(ctx(), namespace1, tasr1) + + assert.NoError(t, err, "TASR update should have succeeded") + + inProgressTASR, err := crdClient.TridentV1().TridentActionSnapshotRestores(namespace1).Get(ctx(), tasr1, getOpts) + + assert.NoError(t, err, "TASR get should have succeeded") + assert.True(t, inProgressTASR.HasTridentFinalizers()) + assert.Equal(t, netappv1.TridentActionStateInProgress, inProgressTASR.Status.State, "TASR not in progress") + assert.Zero(t, inProgressTASR.Status.Message, "TASR should not have error message") + assert.NotZero(t, inProgressTASR.Status.StartTime, "Start time should not be zero") +} + +func TestUpdateActionSnapshotRestoreCRComplete_Succeeded(t *testing.T) { + mockCtrl := gomock.NewController(t) + orchestrator := mockcore.NewMockOrchestrator(mockCtrl) + + tridentNamespace := "trident" + kubeClient := GetTestKubernetesClientset() + snapClient := GetTestSnapshotClientset() + crdClient := GetTestCrdClientset() + crdController, err := newTridentCrdControllerImpl(orchestrator, tridentNamespace, kubeClient, snapClient, crdClient) + if err != nil { + t.Fatalf("cannot create Trident CRD controller frontend; %v", err) + } + + err = crdController.updateActionSnapshotRestoreCRComplete(ctx(), namespace1, tasr1, nil) + + assert.NoError(t, err, "TASR should not have been found") + + tasr := fakeTASR(tasr1, namespace1, snapRestorePVC1, snapRestoreSnap1) + tasr.Status.State = netappv1.TridentActionStateInProgress + _, _ = crdClient.TridentV1().TridentActionSnapshotRestores(namespace1).Create(ctx(), tasr, createOpts) + + err = crdController.updateActionSnapshotRestoreCRComplete(ctx(), namespace1, tasr1, nil) + + assert.NoError(t, err, "TASR update should have succeeded") + + completeTASR, err := crdClient.TridentV1().TridentActionSnapshotRestores(namespace1).Get(ctx(), tasr1, getOpts) + + assert.NoError(t, err, "TASR get should have succeeded") + assert.False(t, completeTASR.HasTridentFinalizers()) + assert.Equal(t, netappv1.TridentActionStateSucceeded, completeTASR.Status.State, "TASR not in succeeded state") + assert.Zero(t, completeTASR.Status.Message, "TASR should not have error message") +} + +func TestUpdateActionSnapshotRestoreCRComplete_Failed(t *testing.T) { + mockCtrl := gomock.NewController(t) + orchestrator := mockcore.NewMockOrchestrator(mockCtrl) + + tridentNamespace := "trident" + kubeClient := GetTestKubernetesClientset() + snapClient := GetTestSnapshotClientset() + crdClient := GetTestCrdClientset() + crdController, err := newTridentCrdControllerImpl(orchestrator, tridentNamespace, kubeClient, snapClient, crdClient) + if err != nil { + t.Fatalf("cannot create Trident CRD controller frontend; %v", err) + } + + err = crdController.updateActionSnapshotRestoreCRComplete(ctx(), namespace1, tasr1, nil) + + assert.NoError(t, err, "TASR should not have been found") + + tasr := fakeTASR(tasr1, namespace1, snapRestorePVC1, snapRestoreSnap1) + tasr.Status.State = netappv1.TridentActionStateInProgress + _, _ = crdClient.TridentV1().TridentActionSnapshotRestores(namespace1).Create(ctx(), tasr, createOpts) + + err = crdController.updateActionSnapshotRestoreCRComplete(ctx(), namespace1, tasr1, fmt.Errorf("failed")) + + assert.NoError(t, err, "TASR update should have succeeded") + + completeTASR, err := crdClient.TridentV1().TridentActionSnapshotRestores(namespace1).Get(ctx(), tasr1, getOpts) + + assert.NoError(t, err, "TASR get should have succeeded") + assert.False(t, completeTASR.HasTridentFinalizers()) + assert.Equal(t, netappv1.TridentActionStateFailed, completeTASR.Status.State, "TASR not in failed state") + assert.Equal(t, "failed", completeTASR.Status.Message, "TASR should have error message") +} + +func TestGetKubernetesObjectsForActionSnapshotRestore(t *testing.T) { + mockCtrl := gomock.NewController(t) + orchestrator := mockcore.NewMockOrchestrator(mockCtrl) + + tridentNamespace := "trident" + kubeClient := GetTestKubernetesClientset() + snapClient := GetTestSnapshotClientset() + crdClient := GetTestCrdClientset() + crdController, err := newTridentCrdControllerImpl(orchestrator, tridentNamespace, kubeClient, snapClient, crdClient) + if err != nil { + t.Fatalf("cannot create Trident CRD controller frontend; %v", err) + } + + tasr := fakeTASR(tasr1, namespace1, snapRestorePVC1, snapRestoreSnap1) + + // Missing PVC + _, _, err = crdController.getKubernetesObjectsForActionSnapshotRestore(ctx(), tasr) + + assert.True(t, apierrors.IsNotFound(err), "PVC was found") + + // Non-bound PVC + pvc := fakeSnapRestorePVC(snapRestorePVC1, namespace1, snapRestorePV1) + pvc.Status.Phase = v1.ClaimPending + _, _ = kubeClient.CoreV1().PersistentVolumeClaims(namespace1).Create(ctx(), pvc, createOpts) + + _, _, err = crdController.getKubernetesObjectsForActionSnapshotRestore(ctx(), tasr) + + assert.Error(t, err) + + // Missing PV + pvc.Status.Phase = v1.ClaimBound + _, _ = kubeClient.CoreV1().PersistentVolumeClaims(namespace1).Update(ctx(), pvc, updateOpts) + + _, _, err = crdController.getKubernetesObjectsForActionSnapshotRestore(ctx(), tasr) + + assert.Error(t, err) + + // Non-bound PV + pv := fakePV(snapRestorePVC1, namespace1, snapRestorePV1) + pv.Status.Phase = v1.VolumePending + pv.Spec.ClaimRef = nil + _, _ = kubeClient.CoreV1().PersistentVolumes().Create(ctx(), pv, createOpts) + + _, _, err = crdController.getKubernetesObjectsForActionSnapshotRestore(ctx(), tasr) + + assert.Error(t, err) + + // Missing PV ClaimRef + pv.Status.Phase = v1.VolumeBound + _, _ = kubeClient.CoreV1().PersistentVolumes().Update(ctx(), pv, updateOpts) + + _, _, err = crdController.getKubernetesObjectsForActionSnapshotRestore(ctx(), tasr) + + assert.Error(t, err) + + // Wrong ClaimRef + pv.Spec.ClaimRef = &v1.ObjectReference{ + Namespace: namespace1, + Name: "otherPVC", + } + _, _ = kubeClient.CoreV1().PersistentVolumes().Update(ctx(), pv, updateOpts) + + _, _, err = crdController.getKubernetesObjectsForActionSnapshotRestore(ctx(), tasr) + + assert.Error(t, err) + + // Missing VS + pv.Spec.ClaimRef = &v1.ObjectReference{ + Namespace: namespace1, + Name: snapRestorePVC1, + } + _, _ = kubeClient.CoreV1().PersistentVolumes().Update(ctx(), pv, updateOpts) + + _, _, err = crdController.getKubernetesObjectsForActionSnapshotRestore(ctx(), tasr) + + assert.Error(t, err) + + // Unbound VS + vsTime := time.Now() + + vs := fakeVS(snapRestoreSnap1, namespace1, snapRestoreVSC1, snapRestorePVC1, vsTime) + vs.Status.BoundVolumeSnapshotContentName = nil + _, _ = snapClient.SnapshotV1().VolumeSnapshots(namespace1).Create(ctx(), vs, createOpts) + + _, _, err = crdController.getKubernetesObjectsForActionSnapshotRestore(ctx(), tasr) + + assert.Error(t, err) + + // No VS snapshot creation time + vs.Status.BoundVolumeSnapshotContentName = utils.Ptr(snapRestoreVSC1) + vs.Status.CreationTime = nil + _, _ = snapClient.SnapshotV1().VolumeSnapshots(namespace1).Update(ctx(), vs, updateOpts) + + _, _, err = crdController.getKubernetesObjectsForActionSnapshotRestore(ctx(), tasr) + + assert.Error(t, err) + + // Zero VS snapshot creation time + vs.Status.CreationTime = utils.Ptr(metav1.NewTime(time.Time{})) + _, _ = snapClient.SnapshotV1().VolumeSnapshots(namespace1).Update(ctx(), vs, updateOpts) + + _, _, err = crdController.getKubernetesObjectsForActionSnapshotRestore(ctx(), tasr) + + assert.Error(t, err) + + // VS nil ready to use + vs.Status.CreationTime = utils.Ptr(metav1.NewTime(time.Now())) + vs.Status.ReadyToUse = nil + _, _ = snapClient.SnapshotV1().VolumeSnapshots(namespace1).Update(ctx(), vs, updateOpts) + + _, _, err = crdController.getKubernetesObjectsForActionSnapshotRestore(ctx(), tasr) + + assert.Error(t, err) + + // VS not ready to use + vs.Status.ReadyToUse = utils.Ptr(false) + _, _ = snapClient.SnapshotV1().VolumeSnapshots(namespace1).Update(ctx(), vs, updateOpts) + + _, _, err = crdController.getKubernetesObjectsForActionSnapshotRestore(ctx(), tasr) + + assert.Error(t, err) + + // Missing VSC + vs.Status.ReadyToUse = utils.Ptr(true) + _, _ = snapClient.SnapshotV1().VolumeSnapshots(namespace1).Update(ctx(), vs, updateOpts) + + _, _, err = crdController.getKubernetesObjectsForActionSnapshotRestore(ctx(), tasr) + + assert.Error(t, err) + + // Wrong VSC VolumeSnapshotRef name + vsc := fakeVSC(snapRestoreSnap1, namespace1, snapRestoreVSC1, snapRestoreSnapHandle1, vsTime) + vsc.Spec.VolumeSnapshotRef.Name = snapRestoreSnap2 + _, _ = snapClient.SnapshotV1().VolumeSnapshotContents().Create(ctx(), vsc, createOpts) + + _, _, err = crdController.getKubernetesObjectsForActionSnapshotRestore(ctx(), tasr) + + assert.Error(t, err) + + // VSC nil ready to use + vsc.Spec.VolumeSnapshotRef.Name = snapRestoreSnap1 + vsc.Status.ReadyToUse = nil + _, _ = snapClient.SnapshotV1().VolumeSnapshotContents().Update(ctx(), vsc, updateOpts) + + _, _, err = crdController.getKubernetesObjectsForActionSnapshotRestore(ctx(), tasr) + + assert.Error(t, err) + + // VSC not ready to use + vsc.Status.ReadyToUse = utils.Ptr(false) + _, _ = snapClient.SnapshotV1().VolumeSnapshotContents().Update(ctx(), vsc, updateOpts) + + _, _, err = crdController.getKubernetesObjectsForActionSnapshotRestore(ctx(), tasr) + + assert.Error(t, err) + + // VSC nil snapshot handle + vsc.Status.ReadyToUse = utils.Ptr(true) + vsc.Status.SnapshotHandle = nil + _, _ = snapClient.SnapshotV1().VolumeSnapshotContents().Update(ctx(), vsc, updateOpts) + + _, _, err = crdController.getKubernetesObjectsForActionSnapshotRestore(ctx(), tasr) + + assert.Error(t, err) + + // VSC invalid snapshot handle + vsc.Status.SnapshotHandle = utils.Ptr(snapRestoreSnap1) + _, _ = snapClient.SnapshotV1().VolumeSnapshotContents().Update(ctx(), vsc, updateOpts) + + _, _, err = crdController.getKubernetesObjectsForActionSnapshotRestore(ctx(), tasr) + + assert.Error(t, err) + + // All OK + vsc.Status.SnapshotHandle = utils.Ptr(snapRestoreSnapHandle1) + _, _ = snapClient.SnapshotV1().VolumeSnapshotContents().Update(ctx(), vsc, updateOpts) + + tridentVolume, tridentSnapshot, err := crdController.getKubernetesObjectsForActionSnapshotRestore(ctx(), tasr) + + assert.NoError(t, err, "call should not have failed") + assert.Equal(t, snapRestorePV1, tridentVolume) + assert.Equal(t, snapRestoreTsnap1, tridentSnapshot) +} diff --git a/helm/trident-operator/templates/clusterrole.yaml b/helm/trident-operator/templates/clusterrole.yaml index dc280750a..e6c8a91aa 100644 --- a/helm/trident-operator/templates/clusterrole.yaml +++ b/helm/trident-operator/templates/clusterrole.yaml @@ -216,6 +216,8 @@ rules: - tridentactionmirrorupdates/status - tridentsnapshotinfos - tridentsnapshotinfos/status + - tridentactionsnapshotrestores + - tridentactionsnapshotrestores/status - tridentprovisioners # Required for Tprov - tridentprovisioners/status # Required to update Tprov's status section - tridentorchestrators # Required for torc diff --git a/mocks/mock_core/mock_core.go b/mocks/mock_core/mock_core.go index c25f5ca20..be5c58cfe 100644 --- a/mocks/mock_core/mock_core.go +++ b/mocks/mock_core/mock_core.go @@ -1002,6 +1002,20 @@ func (mr *MockOrchestratorMockRecorder) ResizeVolume(arg0, arg1, arg2 interface{ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResizeVolume", reflect.TypeOf((*MockOrchestrator)(nil).ResizeVolume), arg0, arg1, arg2) } +// RestoreSnapshot mocks base method. +func (m *MockOrchestrator) RestoreSnapshot(arg0 context.Context, arg1, arg2 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RestoreSnapshot", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// RestoreSnapshot indicates an expected call of RestoreSnapshot. +func (mr *MockOrchestratorMockRecorder) RestoreSnapshot(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RestoreSnapshot", reflect.TypeOf((*MockOrchestrator)(nil).RestoreSnapshot), arg0, arg1, arg2) +} + // SetLogLayers mocks base method. func (m *MockOrchestrator) SetLogLayers(arg0 context.Context, arg1 string) error { m.ctrl.T.Helper() diff --git a/mocks/mock_storage_drivers/mock_azure/mock_api.go b/mocks/mock_storage_drivers/mock_azure/mock_api.go index c0e1a7856..feb29f3b0 100644 --- a/mocks/mock_storage_drivers/mock_azure/mock_api.go +++ b/mocks/mock_storage_drivers/mock_azure/mock_api.go @@ -341,6 +341,20 @@ func (mr *MockAzureMockRecorder) ResizeVolume(arg0, arg1, arg2 interface{}) *gom return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResizeVolume", reflect.TypeOf((*MockAzure)(nil).ResizeVolume), arg0, arg1, arg2) } +// RestoreSnapshot mocks base method. +func (m *MockAzure) RestoreSnapshot(arg0 context.Context, arg1 *api.FileSystem, arg2 *api.Snapshot) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RestoreSnapshot", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// RestoreSnapshot indicates an expected call of RestoreSnapshot. +func (mr *MockAzureMockRecorder) RestoreSnapshot(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RestoreSnapshot", reflect.TypeOf((*MockAzure)(nil).RestoreSnapshot), arg0, arg1, arg2) +} + // SnapshotForVolume mocks base method. func (m *MockAzure) SnapshotForVolume(arg0 context.Context, arg1 *api.FileSystem, arg2 string) (*api.Snapshot, error) { m.ctrl.T.Helper() diff --git a/operator/controllers/orchestrator/installer/installer.go b/operator/controllers/orchestrator/installer/installer.go index da9335743..b1816350e 100644 --- a/operator/controllers/orchestrator/installer/installer.go +++ b/operator/controllers/orchestrator/installer/installer.go @@ -30,20 +30,20 @@ import ( const ( // CRD names - - ActionMirrorUpdateCRDName = "tridentactionmirrorupdates.trident.netapp.io" - BackendCRDName = "tridentbackends.trident.netapp.io" - BackendConfigCRDName = "tridentbackendconfigs.trident.netapp.io" - MirrorRelationshipCRDName = "tridentmirrorrelationships.trident.netapp.io" - SnapshotInfoCRDName = "tridentsnapshotinfos.trident.netapp.io" - NodeCRDName = "tridentnodes.trident.netapp.io" - StorageClassCRDName = "tridentstorageclasses.trident.netapp.io" - TransactionCRDName = "tridenttransactions.trident.netapp.io" - VersionCRDName = "tridentversions.trident.netapp.io" - VolumeCRDName = "tridentvolumes.trident.netapp.io" - VolumePublicationCRDName = "tridentvolumepublications.trident.netapp.io" - SnapshotCRDName = "tridentsnapshots.trident.netapp.io" - VolumeReferenceCRDName = "tridentvolumereferences.trident.netapp.io" + ActionMirrorUpdateCRDName = "tridentactionmirrorupdates.trident.netapp.io" + ActionSnapshotRestoreCRDName = "tridentactionsnapshotrestores.trident.netapp.io" + BackendCRDName = "tridentbackends.trident.netapp.io" + BackendConfigCRDName = "tridentbackendconfigs.trident.netapp.io" + MirrorRelationshipCRDName = "tridentmirrorrelationships.trident.netapp.io" + SnapshotInfoCRDName = "tridentsnapshotinfos.trident.netapp.io" + NodeCRDName = "tridentnodes.trident.netapp.io" + StorageClassCRDName = "tridentstorageclasses.trident.netapp.io" + TransactionCRDName = "tridenttransactions.trident.netapp.io" + VersionCRDName = "tridentversions.trident.netapp.io" + VolumeCRDName = "tridentvolumes.trident.netapp.io" + VolumePublicationCRDName = "tridentvolumepublications.trident.netapp.io" + SnapshotCRDName = "tridentsnapshots.trident.netapp.io" + VolumeReferenceCRDName = "tridentvolumereferences.trident.netapp.io" DefaultTimeout = 30 ) @@ -89,16 +89,17 @@ var ( CRDnames = []string{ ActionMirrorUpdateCRDName, + ActionSnapshotRestoreCRDName, BackendCRDName, BackendConfigCRDName, MirrorRelationshipCRDName, - SnapshotInfoCRDName, NodeCRDName, + SnapshotCRDName, + SnapshotInfoCRDName, StorageClassCRDName, TransactionCRDName, VersionCRDName, VolumeCRDName, - SnapshotCRDName, VolumeReferenceCRDName, VolumePublicationCRDName, } @@ -664,6 +665,10 @@ func (i *Installer) createCRDs(performOperationOnce bool) error { performOperationOnce); err != nil { return err } + if err = i.CreateOrPatchCRD(ActionSnapshotRestoreCRDName, k8sclient.GetActionSnapshotRestoreCRDYAML(), + false); err != nil { + return err + } return err } diff --git a/persistent_store/crd/apis/netapp/v1/actionsnapshotrestore.go b/persistent_store/crd/apis/netapp/v1/actionsnapshotrestore.go new file mode 100644 index 000000000..084e483c9 --- /dev/null +++ b/persistent_store/crd/apis/netapp/v1/actionsnapshotrestore.go @@ -0,0 +1,71 @@ +// Copyright 2023 NetApp, Inc. All Rights Reserved. + +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/netapp/trident/utils" +) + +func (in *TridentActionSnapshotRestore) GetObjectMeta() metav1.ObjectMeta { + return in.ObjectMeta +} + +func (in *TridentActionSnapshotRestore) GetKind() string { + return "TridentActionSnapshotRestore" +} + +func (in *TridentActionSnapshotRestore) GetFinalizers() []string { + if in.ObjectMeta.Finalizers != nil { + return in.ObjectMeta.Finalizers + } + return []string{} +} + +func (in *TridentActionSnapshotRestore) HasTridentFinalizers() bool { + for _, finalizerName := range GetTridentFinalizers() { + if utils.SliceContainsString(in.ObjectMeta.Finalizers, finalizerName) { + return true + } + } + return false +} + +func (in *TridentActionSnapshotRestore) AddTridentFinalizers() { + for _, finalizerName := range GetTridentFinalizers() { + if !utils.SliceContainsString(in.ObjectMeta.Finalizers, finalizerName) { + in.ObjectMeta.Finalizers = append(in.ObjectMeta.Finalizers, finalizerName) + } + } +} + +func (in *TridentActionSnapshotRestore) RemoveTridentFinalizers() { + for _, finalizerName := range GetTridentFinalizers() { + in.ObjectMeta.Finalizers = utils.RemoveStringFromSlice(in.ObjectMeta.Finalizers, finalizerName) + } +} + +// IsNew indicates whether the snapshot restore action has not been started. +func (in *TridentActionSnapshotRestore) IsNew() bool { + return in.Status.State == "" && in.Status.CompletionTime == nil && in.DeletionTimestamp == nil +} + +// IsComplete indicates whether the snapshot restore action has been completed. +func (in *TridentActionSnapshotRestore) IsComplete() bool { + return in.Status.CompletionTime != nil && !in.Status.CompletionTime.IsZero() +} + +// Succeeded indicates whether the snapshot restore action succeeded. +func (in *TridentActionSnapshotRestore) Succeeded() bool { + return in.Status.State == TridentActionStateSucceeded +} + +func (in *TridentActionSnapshotRestore) InProgress() bool { + return in.Status.State == TridentActionStateInProgress +} + +// Failed indicates whether the snapshot restore action failed. +func (in *TridentActionSnapshotRestore) Failed() bool { + return in.Status.State == TridentActionStateFailed +} diff --git a/persistent_store/crd/apis/netapp/v1/register.go b/persistent_store/crd/apis/netapp/v1/register.go index 7da686c21..9689ef56a 100644 --- a/persistent_store/crd/apis/netapp/v1/register.go +++ b/persistent_store/crd/apis/netapp/v1/register.go @@ -60,6 +60,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { &TridentSnapshotList{}, &TridentVolumeReference{}, &TridentVolumeReferenceList{}, + &TridentActionSnapshotRestore{}, + &TridentActionSnapshotRestoreList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil diff --git a/persistent_store/crd/apis/netapp/v1/types.go b/persistent_store/crd/apis/netapp/v1/types.go index ed70c2f7b..4c975feed 100644 --- a/persistent_store/crd/apis/netapp/v1/types.go +++ b/persistent_store/crd/apis/netapp/v1/types.go @@ -472,3 +472,46 @@ type TridentVolumeReferenceList struct { // List of TridentVolumeReference objects Items []*TridentVolumeReference `json:"items"` } + +// TridentActionSnapshotRestore defines an imperative action to restore a volume to a snapshot. +// +genclient +// +k8s:openapi-gen=true +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type TridentActionSnapshotRestore struct { + metav1.TypeMeta `json:",inline"` + // +k8s:openapi-gen=false + metav1.ObjectMeta `json:"metadata,omitempty"` + + // Input spec for TridentActionSnapshotRestore + Spec TridentActionSnapshotRestoreSpec `json:"spec"` + + // Completion status for TridentActionSnapshotRestore + Status TridentActionSnapshotRestoreStatus `json:"status"` +} + +// TridentActionSnapshotRestoreSpec defines the arguments of TridentActionSnapshotRestore +type TridentActionSnapshotRestoreSpec struct { + // PVCName is the name of the PVC (not the PV) whose bound volume is to be restored + PVCName string `json:"pvcName"` + // VolumeSnapshotName is the name of the volume snapshot (not the VSC) to restore + VolumeSnapshotName string `json:"volumeSnapshotName"` +} + +// TridentActionSnapshotRestoreStatus defines the result of TridentActionSnapshotRestore +type TridentActionSnapshotRestoreStatus struct { + State string `json:"state,omitempty"` + Message string `json:"message,omitempty"` + StartTime *metav1.Time `json:"startTime,omitempty"` + CompletionTime *metav1.Time `json:"completionTime,omitempty"` +} + +// TridentActionSnapshotRestoreList is a list of TridentActionSnapshotRestore objects. +// +k8s:openapi-gen=true +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type TridentActionSnapshotRestoreList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + + // List of TridentActionSnapshotRestore objects + Items []*TridentActionSnapshotRestore `json:"items"` +} diff --git a/persistent_store/crd/apis/netapp/v1/zz_generated.deepcopy.go b/persistent_store/crd/apis/netapp/v1/zz_generated.deepcopy.go index c6725f27d..68af2c83f 100644 --- a/persistent_store/crd/apis/netapp/v1/zz_generated.deepcopy.go +++ b/persistent_store/crd/apis/netapp/v1/zz_generated.deepcopy.go @@ -99,6 +99,10 @@ func (in *TridentActionMirrorUpdateStatus) DeepCopyInto(out *TridentActionMirror in, out := &in.CompletionTime, &out.CompletionTime *out = (*in).DeepCopy() } + if in.StartTime != nil { + in, out := &in.StartTime, &out.StartTime + *out = (*in).DeepCopy() + } return } @@ -112,6 +116,111 @@ func (in *TridentActionMirrorUpdateStatus) DeepCopy() *TridentActionMirrorUpdate return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TridentActionSnapshotRestore) DeepCopyInto(out *TridentActionSnapshotRestore) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TridentActionSnapshotRestore. +func (in *TridentActionSnapshotRestore) DeepCopy() *TridentActionSnapshotRestore { + if in == nil { + return nil + } + out := new(TridentActionSnapshotRestore) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TridentActionSnapshotRestore) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TridentActionSnapshotRestoreList) DeepCopyInto(out *TridentActionSnapshotRestoreList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]*TridentActionSnapshotRestore, len(*in)) + for i := range *in { + if (*in)[i] != nil { + in, out := &(*in)[i], &(*out)[i] + *out = new(TridentActionSnapshotRestore) + (*in).DeepCopyInto(*out) + } + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TridentActionSnapshotRestoreList. +func (in *TridentActionSnapshotRestoreList) DeepCopy() *TridentActionSnapshotRestoreList { + if in == nil { + return nil + } + out := new(TridentActionSnapshotRestoreList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TridentActionSnapshotRestoreList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TridentActionSnapshotRestoreSpec) DeepCopyInto(out *TridentActionSnapshotRestoreSpec) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TridentActionSnapshotRestoreSpec. +func (in *TridentActionSnapshotRestoreSpec) DeepCopy() *TridentActionSnapshotRestoreSpec { + if in == nil { + return nil + } + out := new(TridentActionSnapshotRestoreSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TridentActionSnapshotRestoreStatus) DeepCopyInto(out *TridentActionSnapshotRestoreStatus) { + *out = *in + if in.StartTime != nil { + in, out := &in.StartTime, &out.StartTime + *out = (*in).DeepCopy() + } + if in.CompletionTime != nil { + in, out := &in.CompletionTime, &out.CompletionTime + *out = (*in).DeepCopy() + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TridentActionSnapshotRestoreStatus. +func (in *TridentActionSnapshotRestoreStatus) DeepCopy() *TridentActionSnapshotRestoreStatus { + if in == nil { + return nil + } + out := new(TridentActionSnapshotRestoreStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TridentBackend) DeepCopyInto(out *TridentBackend) { *out = *in diff --git a/persistent_store/crd/client/clientset/versioned/clientset.go b/persistent_store/crd/client/clientset/versioned/clientset.go index d77fef8f3..2d348bb7a 100644 --- a/persistent_store/crd/client/clientset/versioned/clientset.go +++ b/persistent_store/crd/client/clientset/versioned/clientset.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/clientset/versioned/doc.go b/persistent_store/crd/client/clientset/versioned/doc.go index c726f22ef..5f58c9011 100644 --- a/persistent_store/crd/client/clientset/versioned/doc.go +++ b/persistent_store/crd/client/clientset/versioned/doc.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/clientset/versioned/fake/clientset_generated.go b/persistent_store/crd/client/clientset/versioned/fake/clientset_generated.go index 71f893ad0..daf095148 100644 --- a/persistent_store/crd/client/clientset/versioned/fake/clientset_generated.go +++ b/persistent_store/crd/client/clientset/versioned/fake/clientset_generated.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/clientset/versioned/fake/doc.go b/persistent_store/crd/client/clientset/versioned/fake/doc.go index 7eae831bc..e1f96a121 100644 --- a/persistent_store/crd/client/clientset/versioned/fake/doc.go +++ b/persistent_store/crd/client/clientset/versioned/fake/doc.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/clientset/versioned/scheme/doc.go b/persistent_store/crd/client/clientset/versioned/scheme/doc.go index d16c8a976..02121db3d 100644 --- a/persistent_store/crd/client/clientset/versioned/scheme/doc.go +++ b/persistent_store/crd/client/clientset/versioned/scheme/doc.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/doc.go b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/doc.go index a856cf979..2c4fa60ac 100644 --- a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/doc.go +++ b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/doc.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/doc.go b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/doc.go index 396c6e2b0..0ed352683 100644 --- a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/doc.go +++ b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/doc.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_netapp_client.go b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_netapp_client.go index eb9dd92eb..90e85c47c 100644 --- a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_netapp_client.go +++ b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_netapp_client.go @@ -18,6 +18,10 @@ func (c *FakeTridentV1) TridentActionMirrorUpdates(namespace string) v1.TridentA return &FakeTridentActionMirrorUpdates{c, namespace} } +func (c *FakeTridentV1) TridentActionSnapshotRestores(namespace string) v1.TridentActionSnapshotRestoreInterface { + return &FakeTridentActionSnapshotRestores{c, namespace} +} + func (c *FakeTridentV1) TridentBackends(namespace string) v1.TridentBackendInterface { return &FakeTridentBackends{c, namespace} } diff --git a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentactionsnapshotrestore.go b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentactionsnapshotrestore.go new file mode 100644 index 000000000..610dd503b --- /dev/null +++ b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentactionsnapshotrestore.go @@ -0,0 +1,128 @@ +// Copyright 2023 NetApp, Inc. All Rights Reserved. + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + + netappv1 "github.com/netapp/trident/persistent_store/crd/apis/netapp/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + schema "k8s.io/apimachinery/pkg/runtime/schema" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// FakeTridentActionSnapshotRestores implements TridentActionSnapshotRestoreInterface +type FakeTridentActionSnapshotRestores struct { + Fake *FakeTridentV1 + ns string +} + +var tridentactionsnapshotrestoresResource = schema.GroupVersionResource{Group: "trident.netapp.io", Version: "v1", Resource: "tridentactionsnapshotrestores"} + +var tridentactionsnapshotrestoresKind = schema.GroupVersionKind{Group: "trident.netapp.io", Version: "v1", Kind: "TridentActionSnapshotRestore"} + +// Get takes name of the tridentActionSnapshotRestore, and returns the corresponding tridentActionSnapshotRestore object, and an error if there is any. +func (c *FakeTridentActionSnapshotRestores) Get(ctx context.Context, name string, options v1.GetOptions) (result *netappv1.TridentActionSnapshotRestore, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(tridentactionsnapshotrestoresResource, c.ns, name), &netappv1.TridentActionSnapshotRestore{}) + + if obj == nil { + return nil, err + } + return obj.(*netappv1.TridentActionSnapshotRestore), err +} + +// List takes label and field selectors, and returns the list of TridentActionSnapshotRestores that match those selectors. +func (c *FakeTridentActionSnapshotRestores) List(ctx context.Context, opts v1.ListOptions) (result *netappv1.TridentActionSnapshotRestoreList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(tridentactionsnapshotrestoresResource, tridentactionsnapshotrestoresKind, c.ns, opts), &netappv1.TridentActionSnapshotRestoreList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &netappv1.TridentActionSnapshotRestoreList{ListMeta: obj.(*netappv1.TridentActionSnapshotRestoreList).ListMeta} + for _, item := range obj.(*netappv1.TridentActionSnapshotRestoreList).Items { + if label.Matches(labels.Set(item.Labels)) { + list.Items = append(list.Items, item) + } + } + return list, err +} + +// Watch returns a watch.Interface that watches the requested tridentActionSnapshotRestores. +func (c *FakeTridentActionSnapshotRestores) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(tridentactionsnapshotrestoresResource, c.ns, opts)) + +} + +// Create takes the representation of a tridentActionSnapshotRestore and creates it. Returns the server's representation of the tridentActionSnapshotRestore, and an error, if there is any. +func (c *FakeTridentActionSnapshotRestores) Create(ctx context.Context, tridentActionSnapshotRestore *netappv1.TridentActionSnapshotRestore, opts v1.CreateOptions) (result *netappv1.TridentActionSnapshotRestore, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(tridentactionsnapshotrestoresResource, c.ns, tridentActionSnapshotRestore), &netappv1.TridentActionSnapshotRestore{}) + + if obj == nil { + return nil, err + } + return obj.(*netappv1.TridentActionSnapshotRestore), err +} + +// Update takes the representation of a tridentActionSnapshotRestore and updates it. Returns the server's representation of the tridentActionSnapshotRestore, and an error, if there is any. +func (c *FakeTridentActionSnapshotRestores) Update(ctx context.Context, tridentActionSnapshotRestore *netappv1.TridentActionSnapshotRestore, opts v1.UpdateOptions) (result *netappv1.TridentActionSnapshotRestore, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(tridentactionsnapshotrestoresResource, c.ns, tridentActionSnapshotRestore), &netappv1.TridentActionSnapshotRestore{}) + + if obj == nil { + return nil, err + } + return obj.(*netappv1.TridentActionSnapshotRestore), err +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeTridentActionSnapshotRestores) UpdateStatus(ctx context.Context, tridentActionSnapshotRestore *netappv1.TridentActionSnapshotRestore, opts v1.UpdateOptions) (*netappv1.TridentActionSnapshotRestore, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(tridentactionsnapshotrestoresResource, "status", c.ns, tridentActionSnapshotRestore), &netappv1.TridentActionSnapshotRestore{}) + + if obj == nil { + return nil, err + } + return obj.(*netappv1.TridentActionSnapshotRestore), err +} + +// Delete takes name of the tridentActionSnapshotRestore and deletes it. Returns an error if one occurs. +func (c *FakeTridentActionSnapshotRestores) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteAction(tridentactionsnapshotrestoresResource, c.ns, name), &netappv1.TridentActionSnapshotRestore{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeTridentActionSnapshotRestores) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(tridentactionsnapshotrestoresResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &netappv1.TridentActionSnapshotRestoreList{}) + return err +} + +// Patch applies the patch and returns the patched tridentActionSnapshotRestore. +func (c *FakeTridentActionSnapshotRestores) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *netappv1.TridentActionSnapshotRestore, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(tridentactionsnapshotrestoresResource, c.ns, name, pt, data, subresources...), &netappv1.TridentActionSnapshotRestore{}) + + if obj == nil { + return nil, err + } + return obj.(*netappv1.TridentActionSnapshotRestore), err +} diff --git a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentbackend.go b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentbackend.go index 90a6c261d..2ffa606ec 100644 --- a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentbackend.go +++ b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentbackend.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentbackendconfig.go b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentbackendconfig.go index 2cc98b79c..c9f667424 100644 --- a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentbackendconfig.go +++ b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentbackendconfig.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentmirrorrelationship.go b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentmirrorrelationship.go index 4d5199f03..a36c59853 100644 --- a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentmirrorrelationship.go +++ b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentmirrorrelationship.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentnode.go b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentnode.go index 7db0d0cf3..79caf4ceb 100644 --- a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentnode.go +++ b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentnode.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentsnapshot.go b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentsnapshot.go index 334c5bf50..cba102e93 100644 --- a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentsnapshot.go +++ b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentsnapshot.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentsnapshotinfo.go b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentsnapshotinfo.go index eb60c5b2b..4d19ddb97 100644 --- a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentsnapshotinfo.go +++ b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentsnapshotinfo.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentstorageclass.go b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentstorageclass.go index 1d0bba0dd..d5ab83e7a 100644 --- a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentstorageclass.go +++ b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentstorageclass.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridenttransaction.go b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridenttransaction.go index d59f69796..49753f5e5 100644 --- a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridenttransaction.go +++ b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridenttransaction.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentversion.go b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentversion.go index d1f7e7db4..1571c3d53 100644 --- a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentversion.go +++ b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentversion.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentvolume.go b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentvolume.go index 10b87eea5..84a114641 100644 --- a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentvolume.go +++ b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentvolume.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentvolumepublication.go b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentvolumepublication.go index 86c120770..28f20299a 100644 --- a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentvolumepublication.go +++ b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentvolumepublication.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentvolumereference.go b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentvolumereference.go index b96683d88..e8e041962 100644 --- a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentvolumereference.go +++ b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/fake/fake_tridentvolumereference.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/generated_expansion.go b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/generated_expansion.go index 5e85dad20..d60ba33ac 100644 --- a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/generated_expansion.go +++ b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/generated_expansion.go @@ -6,6 +6,8 @@ package v1 type TridentActionMirrorUpdateExpansion interface{} +type TridentActionSnapshotRestoreExpansion interface{} + type TridentBackendExpansion interface{} type TridentBackendConfigExpansion interface{} diff --git a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/netapp_client.go b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/netapp_client.go index 7f00c4569..7a9935c29 100644 --- a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/netapp_client.go +++ b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/netapp_client.go @@ -13,6 +13,7 @@ import ( type TridentV1Interface interface { RESTClient() rest.Interface TridentActionMirrorUpdatesGetter + TridentActionSnapshotRestoresGetter TridentBackendsGetter TridentBackendConfigsGetter TridentMirrorRelationshipsGetter @@ -36,6 +37,10 @@ func (c *TridentV1Client) TridentActionMirrorUpdates(namespace string) TridentAc return newTridentActionMirrorUpdates(c, namespace) } +func (c *TridentV1Client) TridentActionSnapshotRestores(namespace string) TridentActionSnapshotRestoreInterface { + return newTridentActionSnapshotRestores(c, namespace) +} + func (c *TridentV1Client) TridentBackends(namespace string) TridentBackendInterface { return newTridentBackends(c, namespace) } diff --git a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentactionsnapshotrestore.go b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentactionsnapshotrestore.go new file mode 100644 index 000000000..32183063d --- /dev/null +++ b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentactionsnapshotrestore.go @@ -0,0 +1,181 @@ +// Copyright 2023 NetApp, Inc. All Rights Reserved. + +// Code generated by client-gen. DO NOT EDIT. + +package v1 + +import ( + "context" + "time" + + v1 "github.com/netapp/trident/persistent_store/crd/apis/netapp/v1" + scheme "github.com/netapp/trident/persistent_store/crd/client/clientset/versioned/scheme" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// TridentActionSnapshotRestoresGetter has a method to return a TridentActionSnapshotRestoreInterface. +// A group's client should implement this interface. +type TridentActionSnapshotRestoresGetter interface { + TridentActionSnapshotRestores(namespace string) TridentActionSnapshotRestoreInterface +} + +// TridentActionSnapshotRestoreInterface has methods to work with TridentActionSnapshotRestore resources. +type TridentActionSnapshotRestoreInterface interface { + Create(ctx context.Context, tridentActionSnapshotRestore *v1.TridentActionSnapshotRestore, opts metav1.CreateOptions) (*v1.TridentActionSnapshotRestore, error) + Update(ctx context.Context, tridentActionSnapshotRestore *v1.TridentActionSnapshotRestore, opts metav1.UpdateOptions) (*v1.TridentActionSnapshotRestore, error) + UpdateStatus(ctx context.Context, tridentActionSnapshotRestore *v1.TridentActionSnapshotRestore, opts metav1.UpdateOptions) (*v1.TridentActionSnapshotRestore, error) + Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error + Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.TridentActionSnapshotRestore, error) + List(ctx context.Context, opts metav1.ListOptions) (*v1.TridentActionSnapshotRestoreList, error) + Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.TridentActionSnapshotRestore, err error) + TridentActionSnapshotRestoreExpansion +} + +// tridentActionSnapshotRestores implements TridentActionSnapshotRestoreInterface +type tridentActionSnapshotRestores struct { + client rest.Interface + ns string +} + +// newTridentActionSnapshotRestores returns a TridentActionSnapshotRestores +func newTridentActionSnapshotRestores(c *TridentV1Client, namespace string) *tridentActionSnapshotRestores { + return &tridentActionSnapshotRestores{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the tridentActionSnapshotRestore, and returns the corresponding tridentActionSnapshotRestore object, and an error if there is any. +func (c *tridentActionSnapshotRestores) Get(ctx context.Context, name string, options metav1.GetOptions) (result *v1.TridentActionSnapshotRestore, err error) { + result = &v1.TridentActionSnapshotRestore{} + err = c.client.Get(). + Namespace(c.ns). + Resource("tridentactionsnapshotrestores"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of TridentActionSnapshotRestores that match those selectors. +func (c *tridentActionSnapshotRestores) List(ctx context.Context, opts metav1.ListOptions) (result *v1.TridentActionSnapshotRestoreList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v1.TridentActionSnapshotRestoreList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("tridentactionsnapshotrestores"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested tridentActionSnapshotRestores. +func (c *tridentActionSnapshotRestores) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("tridentactionsnapshotrestores"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a tridentActionSnapshotRestore and creates it. Returns the server's representation of the tridentActionSnapshotRestore, and an error, if there is any. +func (c *tridentActionSnapshotRestores) Create(ctx context.Context, tridentActionSnapshotRestore *v1.TridentActionSnapshotRestore, opts metav1.CreateOptions) (result *v1.TridentActionSnapshotRestore, err error) { + result = &v1.TridentActionSnapshotRestore{} + err = c.client.Post(). + Namespace(c.ns). + Resource("tridentactionsnapshotrestores"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(tridentActionSnapshotRestore). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a tridentActionSnapshotRestore and updates it. Returns the server's representation of the tridentActionSnapshotRestore, and an error, if there is any. +func (c *tridentActionSnapshotRestores) Update(ctx context.Context, tridentActionSnapshotRestore *v1.TridentActionSnapshotRestore, opts metav1.UpdateOptions) (result *v1.TridentActionSnapshotRestore, err error) { + result = &v1.TridentActionSnapshotRestore{} + err = c.client.Put(). + Namespace(c.ns). + Resource("tridentactionsnapshotrestores"). + Name(tridentActionSnapshotRestore.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(tridentActionSnapshotRestore). + Do(ctx). + Into(result) + return +} + +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *tridentActionSnapshotRestores) UpdateStatus(ctx context.Context, tridentActionSnapshotRestore *v1.TridentActionSnapshotRestore, opts metav1.UpdateOptions) (result *v1.TridentActionSnapshotRestore, err error) { + result = &v1.TridentActionSnapshotRestore{} + err = c.client.Put(). + Namespace(c.ns). + Resource("tridentactionsnapshotrestores"). + Name(tridentActionSnapshotRestore.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(tridentActionSnapshotRestore). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the tridentActionSnapshotRestore and deletes it. Returns an error if one occurs. +func (c *tridentActionSnapshotRestores) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("tridentactionsnapshotrestores"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *tridentActionSnapshotRestores) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("tridentactionsnapshotrestores"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched tridentActionSnapshotRestore. +func (c *tridentActionSnapshotRestores) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.TridentActionSnapshotRestore, err error) { + result = &v1.TridentActionSnapshotRestore{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("tridentactionsnapshotrestores"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentbackend.go b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentbackend.go index 0144f4a9d..6bf697494 100644 --- a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentbackend.go +++ b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentbackend.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentbackendconfig.go b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentbackendconfig.go index 11f399df1..dbcfedd40 100644 --- a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentbackendconfig.go +++ b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentbackendconfig.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentmirrorrelationship.go b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentmirrorrelationship.go index 7ecf9db1b..751a67c4a 100644 --- a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentmirrorrelationship.go +++ b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentmirrorrelationship.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentnode.go b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentnode.go index 18c094569..2b764887f 100644 --- a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentnode.go +++ b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentnode.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentsnapshot.go b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentsnapshot.go index 47b93351e..c01d6ef9e 100644 --- a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentsnapshot.go +++ b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentsnapshot.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentsnapshotinfo.go b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentsnapshotinfo.go index 97aac121d..aa1a15a49 100644 --- a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentsnapshotinfo.go +++ b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentsnapshotinfo.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentstorageclass.go b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentstorageclass.go index 7f86bfdc2..37d34adc1 100644 --- a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentstorageclass.go +++ b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentstorageclass.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridenttransaction.go b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridenttransaction.go index f18b722d4..d78fa65f6 100644 --- a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridenttransaction.go +++ b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridenttransaction.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentversion.go b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentversion.go index ae256082b..f6d37fd13 100644 --- a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentversion.go +++ b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentversion.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentvolume.go b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentvolume.go index c10f3fbfb..4c834f15b 100644 --- a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentvolume.go +++ b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentvolume.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentvolumepublication.go b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentvolumepublication.go index 7293163d8..be54445f9 100644 --- a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentvolumepublication.go +++ b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentvolumepublication.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentvolumereference.go b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentvolumereference.go index 176fb83ab..0814ae99f 100644 --- a/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentvolumereference.go +++ b/persistent_store/crd/client/clientset/versioned/typed/netapp/v1/tridentvolumereference.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by client-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/informers/externalversions/factory.go b/persistent_store/crd/client/informers/externalversions/factory.go index 0bf5ef0b7..ea61f378e 100644 --- a/persistent_store/crd/client/informers/externalversions/factory.go +++ b/persistent_store/crd/client/informers/externalversions/factory.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/informers/externalversions/generic.go b/persistent_store/crd/client/informers/externalversions/generic.go index ca1b5e2b1..be1d3df88 100644 --- a/persistent_store/crd/client/informers/externalversions/generic.go +++ b/persistent_store/crd/client/informers/externalversions/generic.go @@ -41,6 +41,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource // Group=trident.netapp.io, Version=v1 case v1.SchemeGroupVersion.WithResource("tridentactionmirrorupdates"): return &genericInformer{resource: resource.GroupResource(), informer: f.Trident().V1().TridentActionMirrorUpdates().Informer()}, nil + case v1.SchemeGroupVersion.WithResource("tridentactionsnapshotrestores"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Trident().V1().TridentActionSnapshotRestores().Informer()}, nil case v1.SchemeGroupVersion.WithResource("tridentbackends"): return &genericInformer{resource: resource.GroupResource(), informer: f.Trident().V1().TridentBackends().Informer()}, nil case v1.SchemeGroupVersion.WithResource("tridentbackendconfigs"): diff --git a/persistent_store/crd/client/informers/externalversions/internalinterfaces/factory_interfaces.go b/persistent_store/crd/client/informers/externalversions/internalinterfaces/factory_interfaces.go index b1fbc5d66..493572cef 100644 --- a/persistent_store/crd/client/informers/externalversions/internalinterfaces/factory_interfaces.go +++ b/persistent_store/crd/client/informers/externalversions/internalinterfaces/factory_interfaces.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/informers/externalversions/netapp/v1/interface.go b/persistent_store/crd/client/informers/externalversions/netapp/v1/interface.go index 18c5620be..57e7654f0 100644 --- a/persistent_store/crd/client/informers/externalversions/netapp/v1/interface.go +++ b/persistent_store/crd/client/informers/externalversions/netapp/v1/interface.go @@ -12,6 +12,8 @@ import ( type Interface interface { // TridentActionMirrorUpdates returns a TridentActionMirrorUpdateInformer. TridentActionMirrorUpdates() TridentActionMirrorUpdateInformer + // TridentActionSnapshotRestores returns a TridentActionSnapshotRestoreInformer. + TridentActionSnapshotRestores() TridentActionSnapshotRestoreInformer // TridentBackends returns a TridentBackendInformer. TridentBackends() TridentBackendInformer // TridentBackendConfigs returns a TridentBackendConfigInformer. @@ -54,6 +56,11 @@ func (v *version) TridentActionMirrorUpdates() TridentActionMirrorUpdateInformer return &tridentActionMirrorUpdateInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} } +// TridentActionSnapshotRestores returns a TridentActionSnapshotRestoreInformer. +func (v *version) TridentActionSnapshotRestores() TridentActionSnapshotRestoreInformer { + return &tridentActionSnapshotRestoreInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // TridentBackends returns a TridentBackendInformer. func (v *version) TridentBackends() TridentBackendInformer { return &tridentBackendInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentactionsnapshotrestore.go b/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentactionsnapshotrestore.go new file mode 100644 index 000000000..319c57a3c --- /dev/null +++ b/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentactionsnapshotrestore.go @@ -0,0 +1,76 @@ +// Copyright 2023 NetApp, Inc. All Rights Reserved. + +// Code generated by informer-gen. DO NOT EDIT. + +package v1 + +import ( + "context" + time "time" + + netappv1 "github.com/netapp/trident/persistent_store/crd/apis/netapp/v1" + versioned "github.com/netapp/trident/persistent_store/crd/client/clientset/versioned" + internalinterfaces "github.com/netapp/trident/persistent_store/crd/client/informers/externalversions/internalinterfaces" + v1 "github.com/netapp/trident/persistent_store/crd/client/listers/netapp/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// TridentActionSnapshotRestoreInformer provides access to a shared informer and lister for +// TridentActionSnapshotRestores. +type TridentActionSnapshotRestoreInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1.TridentActionSnapshotRestoreLister +} + +type tridentActionSnapshotRestoreInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewTridentActionSnapshotRestoreInformer constructs a new informer for TridentActionSnapshotRestore type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewTridentActionSnapshotRestoreInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredTridentActionSnapshotRestoreInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredTridentActionSnapshotRestoreInformer constructs a new informer for TridentActionSnapshotRestore type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredTridentActionSnapshotRestoreInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.TridentV1().TridentActionSnapshotRestores(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.TridentV1().TridentActionSnapshotRestores(namespace).Watch(context.TODO(), options) + }, + }, + &netappv1.TridentActionSnapshotRestore{}, + resyncPeriod, + indexers, + ) +} + +func (f *tridentActionSnapshotRestoreInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredTridentActionSnapshotRestoreInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *tridentActionSnapshotRestoreInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&netappv1.TridentActionSnapshotRestore{}, f.defaultInformer) +} + +func (f *tridentActionSnapshotRestoreInformer) Lister() v1.TridentActionSnapshotRestoreLister { + return v1.NewTridentActionSnapshotRestoreLister(f.Informer().GetIndexer()) +} diff --git a/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentbackend.go b/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentbackend.go index e9b9926d5..cb317cf73 100644 --- a/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentbackend.go +++ b/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentbackend.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentbackendconfig.go b/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentbackendconfig.go index 0ee8b5f2c..fab1a096a 100644 --- a/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentbackendconfig.go +++ b/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentbackendconfig.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentmirrorrelationship.go b/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentmirrorrelationship.go index eaf4e93a2..eed65fe93 100644 --- a/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentmirrorrelationship.go +++ b/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentmirrorrelationship.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentnode.go b/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentnode.go index f9bec5a55..f8c466145 100644 --- a/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentnode.go +++ b/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentnode.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentsnapshot.go b/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentsnapshot.go index 86985a9c9..b2b91c696 100644 --- a/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentsnapshot.go +++ b/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentsnapshot.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentsnapshotinfo.go b/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentsnapshotinfo.go index ad36e3a97..10a0f3730 100644 --- a/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentsnapshotinfo.go +++ b/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentsnapshotinfo.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentstorageclass.go b/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentstorageclass.go index 1e6d43299..0635554aa 100644 --- a/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentstorageclass.go +++ b/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentstorageclass.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/informers/externalversions/netapp/v1/tridenttransaction.go b/persistent_store/crd/client/informers/externalversions/netapp/v1/tridenttransaction.go index 30e74ec2d..6e6f3eb66 100644 --- a/persistent_store/crd/client/informers/externalversions/netapp/v1/tridenttransaction.go +++ b/persistent_store/crd/client/informers/externalversions/netapp/v1/tridenttransaction.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentversion.go b/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentversion.go index 370cc701a..6879d492d 100644 --- a/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentversion.go +++ b/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentversion.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentvolume.go b/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentvolume.go index 3e496ccce..0ab2ed59a 100644 --- a/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentvolume.go +++ b/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentvolume.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentvolumepublication.go b/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentvolumepublication.go index e71419092..f134b4cc1 100644 --- a/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentvolumepublication.go +++ b/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentvolumepublication.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentvolumereference.go b/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentvolumereference.go index fa0c41435..06d162155 100644 --- a/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentvolumereference.go +++ b/persistent_store/crd/client/informers/externalversions/netapp/v1/tridentvolumereference.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by informer-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/listers/netapp/v1/expansion_generated.go b/persistent_store/crd/client/listers/netapp/v1/expansion_generated.go index e8d6cea17..f94615546 100644 --- a/persistent_store/crd/client/listers/netapp/v1/expansion_generated.go +++ b/persistent_store/crd/client/listers/netapp/v1/expansion_generated.go @@ -12,6 +12,14 @@ type TridentActionMirrorUpdateListerExpansion interface{} // TridentActionMirrorUpdateNamespaceLister. type TridentActionMirrorUpdateNamespaceListerExpansion interface{} +// TridentActionSnapshotRestoreListerExpansion allows custom methods to be added to +// TridentActionSnapshotRestoreLister. +type TridentActionSnapshotRestoreListerExpansion interface{} + +// TridentActionSnapshotRestoreNamespaceListerExpansion allows custom methods to be added to +// TridentActionSnapshotRestoreNamespaceLister. +type TridentActionSnapshotRestoreNamespaceListerExpansion interface{} + // TridentBackendListerExpansion allows custom methods to be added to // TridentBackendLister. type TridentBackendListerExpansion interface{} diff --git a/persistent_store/crd/client/listers/netapp/v1/tridentactionsnapshotrestore.go b/persistent_store/crd/client/listers/netapp/v1/tridentactionsnapshotrestore.go new file mode 100644 index 000000000..15951a66a --- /dev/null +++ b/persistent_store/crd/client/listers/netapp/v1/tridentactionsnapshotrestore.go @@ -0,0 +1,80 @@ +// Copyright 2023 NetApp, Inc. All Rights Reserved. + +// Code generated by lister-gen. DO NOT EDIT. + +package v1 + +import ( + v1 "github.com/netapp/trident/persistent_store/crd/apis/netapp/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// TridentActionSnapshotRestoreLister helps list TridentActionSnapshotRestores. +type TridentActionSnapshotRestoreLister interface { + // List lists all TridentActionSnapshotRestores in the indexer. + List(selector labels.Selector) (ret []*v1.TridentActionSnapshotRestore, err error) + // TridentActionSnapshotRestores returns an object that can list and get TridentActionSnapshotRestores. + TridentActionSnapshotRestores(namespace string) TridentActionSnapshotRestoreNamespaceLister + TridentActionSnapshotRestoreListerExpansion +} + +// tridentActionSnapshotRestoreLister implements the TridentActionSnapshotRestoreLister interface. +type tridentActionSnapshotRestoreLister struct { + indexer cache.Indexer +} + +// NewTridentActionSnapshotRestoreLister returns a new TridentActionSnapshotRestoreLister. +func NewTridentActionSnapshotRestoreLister(indexer cache.Indexer) TridentActionSnapshotRestoreLister { + return &tridentActionSnapshotRestoreLister{indexer: indexer} +} + +// List lists all TridentActionSnapshotRestores in the indexer. +func (s *tridentActionSnapshotRestoreLister) List(selector labels.Selector) (ret []*v1.TridentActionSnapshotRestore, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1.TridentActionSnapshotRestore)) + }) + return ret, err +} + +// TridentActionSnapshotRestores returns an object that can list and get TridentActionSnapshotRestores. +func (s *tridentActionSnapshotRestoreLister) TridentActionSnapshotRestores(namespace string) TridentActionSnapshotRestoreNamespaceLister { + return tridentActionSnapshotRestoreNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// TridentActionSnapshotRestoreNamespaceLister helps list and get TridentActionSnapshotRestores. +type TridentActionSnapshotRestoreNamespaceLister interface { + // List lists all TridentActionSnapshotRestores in the indexer for a given namespace. + List(selector labels.Selector) (ret []*v1.TridentActionSnapshotRestore, err error) + // Get retrieves the TridentActionSnapshotRestore from the indexer for a given namespace and name. + Get(name string) (*v1.TridentActionSnapshotRestore, error) + TridentActionSnapshotRestoreNamespaceListerExpansion +} + +// tridentActionSnapshotRestoreNamespaceLister implements the TridentActionSnapshotRestoreNamespaceLister +// interface. +type tridentActionSnapshotRestoreNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all TridentActionSnapshotRestores in the indexer for a given namespace. +func (s tridentActionSnapshotRestoreNamespaceLister) List(selector labels.Selector) (ret []*v1.TridentActionSnapshotRestore, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v1.TridentActionSnapshotRestore)) + }) + return ret, err +} + +// Get retrieves the TridentActionSnapshotRestore from the indexer for a given namespace and name. +func (s tridentActionSnapshotRestoreNamespaceLister) Get(name string) (*v1.TridentActionSnapshotRestore, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1.Resource("tridentactionsnapshotrestore"), name) + } + return obj.(*v1.TridentActionSnapshotRestore), nil +} diff --git a/persistent_store/crd/client/listers/netapp/v1/tridentbackend.go b/persistent_store/crd/client/listers/netapp/v1/tridentbackend.go index 6d2af323e..0259022ac 100644 --- a/persistent_store/crd/client/listers/netapp/v1/tridentbackend.go +++ b/persistent_store/crd/client/listers/netapp/v1/tridentbackend.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by lister-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/listers/netapp/v1/tridentbackendconfig.go b/persistent_store/crd/client/listers/netapp/v1/tridentbackendconfig.go index e0baea896..d40ff580b 100644 --- a/persistent_store/crd/client/listers/netapp/v1/tridentbackendconfig.go +++ b/persistent_store/crd/client/listers/netapp/v1/tridentbackendconfig.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by lister-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/listers/netapp/v1/tridentmirrorrelationship.go b/persistent_store/crd/client/listers/netapp/v1/tridentmirrorrelationship.go index 4dd56f902..3864c4b93 100644 --- a/persistent_store/crd/client/listers/netapp/v1/tridentmirrorrelationship.go +++ b/persistent_store/crd/client/listers/netapp/v1/tridentmirrorrelationship.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by lister-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/listers/netapp/v1/tridentnode.go b/persistent_store/crd/client/listers/netapp/v1/tridentnode.go index d24365d7e..7829d298f 100644 --- a/persistent_store/crd/client/listers/netapp/v1/tridentnode.go +++ b/persistent_store/crd/client/listers/netapp/v1/tridentnode.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by lister-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/listers/netapp/v1/tridentsnapshot.go b/persistent_store/crd/client/listers/netapp/v1/tridentsnapshot.go index 37abefc50..904bdfd61 100644 --- a/persistent_store/crd/client/listers/netapp/v1/tridentsnapshot.go +++ b/persistent_store/crd/client/listers/netapp/v1/tridentsnapshot.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by lister-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/listers/netapp/v1/tridentsnapshotinfo.go b/persistent_store/crd/client/listers/netapp/v1/tridentsnapshotinfo.go index 243804374..c1d989b2f 100644 --- a/persistent_store/crd/client/listers/netapp/v1/tridentsnapshotinfo.go +++ b/persistent_store/crd/client/listers/netapp/v1/tridentsnapshotinfo.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by lister-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/listers/netapp/v1/tridentstorageclass.go b/persistent_store/crd/client/listers/netapp/v1/tridentstorageclass.go index 9eae65f25..3f4a44d66 100644 --- a/persistent_store/crd/client/listers/netapp/v1/tridentstorageclass.go +++ b/persistent_store/crd/client/listers/netapp/v1/tridentstorageclass.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by lister-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/listers/netapp/v1/tridenttransaction.go b/persistent_store/crd/client/listers/netapp/v1/tridenttransaction.go index 4178e5f66..9dcfd119d 100644 --- a/persistent_store/crd/client/listers/netapp/v1/tridenttransaction.go +++ b/persistent_store/crd/client/listers/netapp/v1/tridenttransaction.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by lister-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/listers/netapp/v1/tridentversion.go b/persistent_store/crd/client/listers/netapp/v1/tridentversion.go index dd1303d98..52fc77628 100644 --- a/persistent_store/crd/client/listers/netapp/v1/tridentversion.go +++ b/persistent_store/crd/client/listers/netapp/v1/tridentversion.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by lister-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/listers/netapp/v1/tridentvolume.go b/persistent_store/crd/client/listers/netapp/v1/tridentvolume.go index bb847399d..75b13b454 100644 --- a/persistent_store/crd/client/listers/netapp/v1/tridentvolume.go +++ b/persistent_store/crd/client/listers/netapp/v1/tridentvolume.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by lister-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/listers/netapp/v1/tridentvolumepublication.go b/persistent_store/crd/client/listers/netapp/v1/tridentvolumepublication.go index 551e13811..9fd7ca41c 100644 --- a/persistent_store/crd/client/listers/netapp/v1/tridentvolumepublication.go +++ b/persistent_store/crd/client/listers/netapp/v1/tridentvolumepublication.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by lister-gen. DO NOT EDIT. diff --git a/persistent_store/crd/client/listers/netapp/v1/tridentvolumereference.go b/persistent_store/crd/client/listers/netapp/v1/tridentvolumereference.go index 42ef36724..f1d77edaf 100644 --- a/persistent_store/crd/client/listers/netapp/v1/tridentvolumereference.go +++ b/persistent_store/crd/client/listers/netapp/v1/tridentvolumereference.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. // Code generated by lister-gen. DO NOT EDIT. diff --git a/storage_drivers/azure/api/azure.go b/storage_drivers/azure/api/azure.go index 8af935fde..bd273a5c9 100644 --- a/storage_drivers/azure/api/azure.go +++ b/storage_drivers/azure/api/azure.go @@ -1370,6 +1370,37 @@ func (c Client) CreateSnapshot(ctx context.Context, filesystem *FileSystem, name return c.newSnapshotFromANFSnapshot(ctx, &anfSnapshot) } +// RestoreSnapshot restores a volume to a snapshot. +func (c Client) RestoreSnapshot(ctx context.Context, filesystem *FileSystem, snapshot *Snapshot) error { + logFields := LogFields{ + "API": "SnapshotsClient.BeginRevert", + "volume": filesystem.FullName, + "snapshot": snapshot.Name, + } + + var rawResponse *http.Response + responseCtx := runtime.WithCaptureResponse(ctx, &rawResponse) + + revertBody := netapp.VolumeRevert{ + SnapshotID: utils.Ptr(snapshot.SnapshotID), + } + + _, err := c.sdkClient.VolumesClient.BeginRevert(responseCtx, + filesystem.ResourceGroup, filesystem.NetAppAccount, filesystem.CapacityPool, + filesystem.Name, revertBody, nil) + + logFields["correlationID"] = GetCorrelationID(rawResponse) + + if err != nil { + Logc(ctx).WithFields(logFields).WithError(err).Error("Error reverting snapshot.") + return err + } + + Logc(ctx).WithFields(logFields).Debug("Volume reverted to snapshot.") + + return nil +} + // DeleteSnapshot deletes a snapshot. func (c Client) DeleteSnapshot(ctx context.Context, filesystem *FileSystem, snapshot *Snapshot) error { logFields := LogFields{ diff --git a/storage_drivers/azure/api/azure_structs.go b/storage_drivers/azure/api/azure_structs.go index 108a1306f..5c986671c 100644 --- a/storage_drivers/azure/api/azure_structs.go +++ b/storage_drivers/azure/api/azure_structs.go @@ -16,6 +16,7 @@ const ( StateDeleted = "NoSuchState" StateMoving = "Moving" // Currently unused by ANF StateError = "Failed" + StateReverting = "Reverting" ProtocolTypeNFSPrefix = "NFSv" ProtocolTypeNFSv3 = ProtocolTypeNFSPrefix + "3" diff --git a/storage_drivers/azure/api/types.go b/storage_drivers/azure/api/types.go index 029a6cf2b..fc132012d 100644 --- a/storage_drivers/azure/api/types.go +++ b/storage_drivers/azure/api/types.go @@ -58,5 +58,6 @@ type Azure interface { SnapshotForVolume(context.Context, *FileSystem, string) (*Snapshot, error) WaitForSnapshotState(context.Context, *Snapshot, *FileSystem, string, []string, time.Duration) error CreateSnapshot(context.Context, *FileSystem, string) (*Snapshot, error) + RestoreSnapshot(context.Context, *FileSystem, *Snapshot) error DeleteSnapshot(context.Context, *FileSystem, *Snapshot) error } diff --git a/storage_drivers/azure/azure_anf.go b/storage_drivers/azure/azure_anf.go index b7d48ae89..686ead054 100644 --- a/storage_drivers/azure/azure_anf.go +++ b/storage_drivers/azure/azure_anf.go @@ -1223,7 +1223,7 @@ func (d *NASStorageDriver) waitForVolumeCreate(ctx context.Context, volume *api. Logc(ctx).WithField("volume", volume.Name).Info("Volume deleted.") } - case api.StateMoving: + case api.StateMoving, api.StateReverting: fallthrough default: @@ -1509,9 +1509,9 @@ func (d *NASStorageDriver) CreateSnapshot( }, nil } -// RestoreSnapshot restores a volume (in place) from a snapshot. Not supported by this driver. +// RestoreSnapshot restores a volume (in place) from a snapshot. func (d *NASStorageDriver) RestoreSnapshot( - ctx context.Context, snapConfig *storage.SnapshotConfig, _ *storage.VolumeConfig, + ctx context.Context, snapConfig *storage.SnapshotConfig, volConfig *storage.VolumeConfig, ) error { internalSnapName := snapConfig.InternalName internalVolName := snapConfig.VolumeInternalName @@ -1524,7 +1524,33 @@ func (d *NASStorageDriver) RestoreSnapshot( Logd(ctx, d.Name(), d.Config.DebugTraceFlags["method"]).WithFields(fields).Trace(">>>> RestoreSnapshot") defer Logd(ctx, d.Name(), d.Config.DebugTraceFlags["method"]).WithFields(fields).Trace("<<<< RestoreSnapshot") - return errors.UnsupportedError(fmt.Sprintf("restoring snapshots is not supported by backend type %s", d.Name())) + // Update resource cache as needed + if err := d.SDK.RefreshAzureResources(ctx); err != nil { + return fmt.Errorf("could not update ANF resource cache; %v", err) + } + + // Get the volume + volume, err := d.SDK.Volume(ctx, volConfig) + if err != nil { + return fmt.Errorf("could not find volume %s; %v", internalVolName, err) + } + + // Get the snapshot + snapshot, err := d.SDK.SnapshotForVolume(ctx, volume, internalSnapName) + if err != nil { + return fmt.Errorf("unable to find snapshot %s: %v", internalSnapName, err) + } + + // Do the restore + if err = d.SDK.RestoreSnapshot(ctx, volume, snapshot); err != nil { + return err + } + + // Wait for snapshot deletion to complete + _, err = d.SDK.WaitForVolumeState(ctx, volume, api.StateAvailable, + []string{api.StateError, api.StateDeleting, api.StateDeleted}, api.DefaultSDKTimeout, + ) + return err } // DeleteSnapshot deletes a snapshot of a volume. diff --git a/storage_drivers/azure/azure_anf_subvolume.go b/storage_drivers/azure/azure_anf_subvolume.go index ab63d5298..6e2eda38a 100644 --- a/storage_drivers/azure/azure_anf_subvolume.go +++ b/storage_drivers/azure/azure_anf_subvolume.go @@ -1,4 +1,4 @@ -// Copyright 2021 NetApp, Inc. All Rights Reserved. +// Copyright 2023 NetApp, Inc. All Rights Reserved. package azure @@ -33,6 +33,7 @@ const ( snapshotNameSeparator = "--" pvcPrefix = "pvc-" + tempCopySuffix = "-og" ) var ( @@ -43,11 +44,23 @@ var ( pollerResponseCache = make(map[PollerKey]api.PollerResponse) ) +type Operation int64 + +const ( + Create Operation = iota + Delete + Update + Restore +) + type PollerKey struct { ID string - Operation string + Operation Operation } +// key is subvolume ID and value can be snapshot ID or empty +var subvolumesToDelete map[string]string + type SubvolumeHelper struct { Config drivers.AzureNASStorageDriverConfig Context tridentconfig.DriverContext @@ -231,8 +244,10 @@ func (d *NASBlockStorageDriver) Initialize( commonConfig *drivers.CommonStorageDriverConfig, backendSecret map[string]string, backendUUID string, ) error { fields := LogFields{"Method": "Initialize", "Type": "NASBlockStorageDriver"} - Logd(ctx, commonConfig.StorageDriverName, commonConfig.DebugTraceFlags["method"]).WithFields(fields).Trace(">>>> Initialize") - defer Logd(ctx, commonConfig.StorageDriverName, commonConfig.DebugTraceFlags["method"]).WithFields(fields).Trace("<<<< Initialize") + Logd(ctx, commonConfig.StorageDriverName, + commonConfig.DebugTraceFlags["method"]).WithFields(fields).Trace(">>>> Initialize") + defer Logd(ctx, commonConfig.StorageDriverName, + commonConfig.DebugTraceFlags["method"]).WithFields(fields).Trace("<<<< Initialize") commonConfig.DriverContext = context @@ -313,8 +328,10 @@ func (d *NASBlockStorageDriver) populateConfigurationDefaults( ctx context.Context, config *drivers.AzureNASStorageDriverConfig, ) { fields := LogFields{"Method": "populateConfigurationDefaults", "Type": "NASBlockStorageDriver"} - Logd(ctx, config.StorageDriverName, config.DebugTraceFlags["method"]).WithFields(fields).Trace(">>>> populateConfigurationDefaults") - defer Logd(ctx, config.StorageDriverName, config.DebugTraceFlags["method"]).WithFields(fields).Trace("<<<< populateConfigurationDefaults") + Logd(ctx, config.StorageDriverName, + config.DebugTraceFlags["method"]).WithFields(fields).Trace(">>>> populateConfigurationDefaults") + defer Logd(ctx, config.StorageDriverName, + config.DebugTraceFlags["method"]).WithFields(fields).Trace("<<<< populateConfigurationDefaults") if config.StoragePrefix == nil { defaultPrefix := drivers.GetDefaultStoragePrefix(config.DriverContext) @@ -487,8 +504,10 @@ func (d *NASBlockStorageDriver) initializeAzureConfig( backendSecret map[string]string, ) (*drivers.AzureNASStorageDriverConfig, error) { fields := LogFields{"Method": "initializeAzureConfig", "Type": "NASBlockStorageDriver"} - Logd(ctx, commonConfig.StorageDriverName, commonConfig.DebugTraceFlags["method"]).WithFields(fields).Trace(">>>> initializeAzureConfig") - defer Logd(ctx, commonConfig.StorageDriverName, commonConfig.DebugTraceFlags["method"]).WithFields(fields).Trace("<<<< initializeAzureConfig") + Logd(ctx, commonConfig.StorageDriverName, + commonConfig.DebugTraceFlags["method"]).WithFields(fields).Trace(">>>> initializeAzureConfig") + defer Logd(ctx, commonConfig.StorageDriverName, + commonConfig.DebugTraceFlags["method"]).WithFields(fields).Trace("<<<< initializeAzureConfig") config := &drivers.AzureNASStorageDriverConfig{} config.CommonStorageDriverConfig = commonConfig @@ -513,8 +532,10 @@ func (d *NASBlockStorageDriver) initializeAzureSDKClient( ctx context.Context, config *drivers.AzureNASStorageDriverConfig, ) error { fields := LogFields{"Method": "initializeAzureSDKClient", "Type": "NASBlockStorageDriver"} - Logd(ctx, config.StorageDriverName, config.DebugTraceFlags["method"]).WithFields(fields).Trace(">>>> initializeAzureSDKClient") - defer Logd(ctx, config.StorageDriverName, config.DebugTraceFlags["method"]).WithFields(fields).Trace("<<<< initializeAzureSDKClient") + Logd(ctx, config.StorageDriverName, + config.DebugTraceFlags["method"]).WithFields(fields).Trace(">>>> initializeAzureSDKClient") + defer Logd(ctx, config.StorageDriverName, + config.DebugTraceFlags["method"]).WithFields(fields).Trace("<<<< initializeAzureSDKClient") sdkTimeout := api.DefaultSDKTimeout if config.SDKTimeout != "" { @@ -657,13 +678,13 @@ func (d *NASBlockStorageDriver) Create( // Get the reference object pollerKey := PollerKey{ ID: extantSubvolume.Name, - Operation: "add", + Operation: Create, } poller := pollerResponseCache[pollerKey] // Wait for creation to complete - if err = d.waitForSubvolumeCreate(ctx, extantSubvolume, poller); err != nil { + if err = d.waitForSubvolumeCreate(ctx, extantSubvolume, poller, pollerKey.Operation, true); err != nil { return err } @@ -727,13 +748,13 @@ func (d *NASBlockStorageDriver) Create( // Save the Poller's reference for later uses (if needed) pollerKey := PollerKey{ ID: subvolume.Name, - Operation: "add", + Operation: Create, } pollerResponseCache[pollerKey] = poller // Wait for creation to complete - return d.waitForSubvolumeCreate(ctx, subvolume, poller) + return d.waitForSubvolumeCreate(ctx, subvolume, poller, pollerKey.Operation, true) } // CreateClone clones an existing volume. If a snapshot is not specified, one is created. @@ -804,13 +825,13 @@ func (d *NASBlockStorageDriver) CreateClone( // Get the reference object pollerKey := PollerKey{ ID: extantSubvolume.Name, - Operation: "add", + Operation: Create, } poller := pollerResponseCache[pollerKey] // Wait for creation to complete - if err = d.waitForSubvolumeCreate(ctx, extantSubvolume, poller); err != nil { + if err = d.waitForSubvolumeCreate(ctx, extantSubvolume, poller, pollerKey.Operation, true); err != nil { return err } @@ -846,13 +867,13 @@ func (d *NASBlockStorageDriver) CreateClone( // Save the Poller's reference for later uses (if needed) pollerKey := PollerKey{ ID: subvolume.Name, - Operation: "add", + Operation: Create, } pollerResponseCache[pollerKey] = poller // Wait for creation to complete - return d.waitForSubvolumeCreate(ctx, subvolume, poller) + return d.waitForSubvolumeCreate(ctx, subvolume, poller, pollerKey.Operation, true) } // Import finds an existing subvolume and makes it available for containers. If ImportNotManaged is false, the @@ -920,7 +941,7 @@ func (d *NASBlockStorageDriver) Rename(ctx context.Context, name, newName string // volume reaches a terminal state (Error), the volume is deleted. If the wait times out and the volume // is still creating, a VolumeCreatingError is returned so the caller may try again. func (d *NASBlockStorageDriver) waitForSubvolumeCreate(ctx context.Context, subvolume *api.Subvolume, - poller api.PollerResponse, + poller api.PollerResponse, operation Operation, handleErrorInFollowup bool, ) error { var pollForError bool @@ -957,7 +978,7 @@ func (d *NASBlockStorageDriver) waitForSubvolumeCreate(ctx context.Context, subv pollForError = true - case api.StateMoving: + case api.StateMoving, api.StateReverting: fallthrough default: @@ -966,11 +987,11 @@ func (d *NASBlockStorageDriver) waitForSubvolumeCreate(ctx context.Context, subv } } - // If here, it mean volume might be successful, or in deleting, error, moving or unexpected state, + // If here, it means volume might be successful, or in deleting, error, moving or unexpected state, // and not in creating state, so it should be safe to remove it from futures cache pollerKey := PollerKey{ ID: subvolume.Name, - Operation: "add", + Operation: operation, } delete(pollerResponseCache, pollerKey) @@ -983,7 +1004,12 @@ func (d *NASBlockStorageDriver) waitForSubvolumeCreate(ctx context.Context, subv } } - return nil + // If followup exists to handler error, then return nil + if handleErrorInFollowup { + return nil + } + + return err } // Destroy deletes a volume. @@ -1034,25 +1060,7 @@ func (d *NASBlockStorageDriver) Destroy(ctx context.Context, volConfig *storage. } } - // Delete the subvolume - poller, err := d.SDK.DeleteSubvolume(ctx, extantSubvolume) - if err != nil { - if !errors.IsNotFoundError(err) { - return fmt.Errorf("error deleting subvolume %s; %v", creationToken, err) - } - } - - Logc(ctx).WithField("subvolume", extantSubvolume.Name).Info("subvolume deleted.") - - // Wait for deletion to complete - state, err := d.SDK.WaitForSubvolumeState(ctx, extantSubvolume, api.StateDeleted, []string{api.StateError}, - d.defaultTimeout()) - - if err != nil && state == api.StateError { - Logc(ctx).WithField("subvolume", extantSubvolume.Name).Errorf("failed to delete volume: %v", poller.Result(ctx)) - } - - return err + return d.deleteSubvolume(extantSubvolume) } // Publish the volume to the host specified in publishInfo. This method may or may not be running on the host @@ -1083,7 +1091,8 @@ func (d *NASBlockStorageDriver) Publish( } // Set the correct NFS mount option based on volume's protocol - NFSMountOption := fmt.Sprintf("vers=%s", strings.TrimPrefix(volume.ProtocolTypes[0], api.ProtocolTypeNFSPrefix)) + NFSMountOption := fmt.Sprintf("vers=%s", strings.TrimPrefix(volume.ProtocolTypes[0], + api.ProtocolTypeNFSPrefix)) mountOptions := utils.SetNFSVersionMountOptions(d.Config.NfsMountOptions, NFSMountOption) // Subvolume mount options can only be specified via tha storage class. @@ -1337,12 +1346,12 @@ func (d *NASBlockStorageDriver) CreateSnapshot( // Save the Poller's reference for later uses (if needed) pollerKey := PollerKey{ ID: subvolume.Name, - Operation: "add", + Operation: Create, } pollerResponseCache[pollerKey] = poller - if err = d.waitForSubvolumeCreate(ctx, subvolume, poller); err != nil { + if err = d.waitForSubvolumeCreate(ctx, subvolume, poller, pollerKey.Operation, false); err != nil { return nil, err } @@ -1363,22 +1372,209 @@ func (d *NASBlockStorageDriver) CreateSnapshot( } // RestoreSnapshot restores a volume (in place) from a snapshot. +// Subvolume driver does not support in-place restore or renaming of subvolumes, so the "snapshot restore" +// operation works by deleting the original subvolume and replacing it with a clone of the snapshot copy. func (d *NASBlockStorageDriver) RestoreSnapshot( - ctx context.Context, snapConfig *storage.SnapshotConfig, _ *storage.VolumeConfig, + ctx context.Context, snapConfig *storage.SnapshotConfig, volConfig *storage.VolumeConfig, ) error { - snapName := snapConfig.Name - internalVolName := snapConfig.VolumeInternalName + internalSnapName := snapConfig.InternalName + internalVolName := volConfig.InternalName + internalVolID := volConfig.InternalID + tempInternalVolName := volConfig.InternalName + tempCopySuffix + tempInternalVolID := volConfig.InternalID + tempCopySuffix + + var subvolume *api.Subvolume fields := LogFields{ - "Method": "RestoreSnapshot", - "Type": "NASBlockStorageDriver", - "snapshotName": snapName, - "volumeName": internalVolName, + "Method": "RestoreSnapshot", + "Type": "NASBlockStorageDriver", + "snapshotName": internalSnapName, + "volumeName": internalVolName, + "temporaryVolumeName": tempInternalVolName, } + Logd(ctx, d.Name(), d.Config.DebugTraceFlags["method"]).WithFields(fields).Trace(">>>> RestoreSnapshot") defer Logd(ctx, d.Name(), d.Config.DebugTraceFlags["method"]).WithFields(fields).Trace("<<<< RestoreSnapshot") - return errors.UnsupportedError(fmt.Sprintf("restoring snapshots is not supported by backend type %s", d.Name())) + if volConfig.InternalName != snapConfig.VolumeInternalName { + return fmt.Errorf("snapshot/volume mismatch") + } + + _, resourceGroup, _, netappAccount, cPoolName, volumeName, _, err := api.ParseSubvolumeID(volConfig.InternalID) + if err != nil { + Logc(ctx).WithError(err).Errorf("error parsing source volume config internal ID '%s'", + volConfig.InternalName) + return err + } + + snapshotInternalID := api.CreateSubvolumeID(d.Config.SubscriptionID, resourceGroup, + netappAccount, cPoolName, volumeName, internalSnapName) + + // Check to see if only remaining step is temporary subvolume deletion in current snapshot context, + // if so return after delete else proceed with the restore operation + deletedInCurrentSnapshotContext, err := d.deleteSubvolumeInSnapshotContext(ctx, tempInternalVolID, + snapshotInternalID) + if err != nil { + return err + } else if deletedInCurrentSnapshotContext { + // Being here signifies deletion of temporary subvolume after + // a successful restore operation; thus it can return nil + return nil + } + + // Check if subvolume restore already in progress + pollerKey := PollerKey{ + ID: internalVolName, + Operation: Restore, + } + + poller, ok := pollerResponseCache[pollerKey] + + if !ok { + // Create name of the volume where this `-og` subvolume will live + filePoolVolume := api.CreateVolumeFullName(resourceGroup, netappAccount, cPoolName, volumeName) + + // Check to see if `-og` subvolume already exists + tempSubvolumeExists, tempSubvolume, err := d.SDK.SubvolumeExistsByID(ctx, tempInternalVolID) + if err != nil { + Logc(ctx).WithError(err).Errorf("Error checking for existing subvolume: %v", err) + return errors.InProgressError(err.Error()) + } + + if !tempSubvolumeExists { + Logc(ctx).WithFields(LogFields{ + "creationToken": tempInternalVolName, + "volume": filePoolVolume, + "parentPath": internalVolName, + }).Debug("Creating temporary subvolume.") + + // Create an `-og` subvolume request + tempSubvolumeCreateRequest := &api.SubvolumeCreateRequest{ + CreationToken: tempInternalVolName, + Volume: filePoolVolume, + Parent: internalVolName, // Needed only when cloning + } + + // Create the subvolume + tempSubvolume, poller, err = d.SDK.CreateSubvolume(ctx, tempSubvolumeCreateRequest) + if err != nil { + Logc(ctx).WithError(err).Errorf("error creating a temporary subvolume '%s'", + tempInternalVolName) + return errors.InProgressError(err.Error()) + } + } + + // Save the Poller's reference for later uses (if needed) + pollerKey = PollerKey{ + ID: tempSubvolume.Name, + Operation: Create, + } + + pollerResponseCache[pollerKey] = poller + + if err = d.waitForSubvolumeCreate(ctx, tempSubvolume, poller, pollerKey.Operation, false); err != nil { + if errors.IsVolumeCreatingError(err) { + return errors.InProgressError(err.Error()) + } + + return err + } + + Logc(ctx).Debugf("Temporary subvolume '%s' created", tempInternalVolName) + + // Delete the actual subvolume + subvolume = &api.Subvolume{ + ID: internalVolID, + ResourceGroup: resourceGroup, + NetAppAccount: netappAccount, + CapacityPool: cPoolName, + Volume: volumeName, + Name: internalVolName, + } + + if err = d.deleteSubvolume(subvolume); err != nil { + Logc(ctx).WithError(err).Errorf("failed to delete the actual subvolume '%s'", internalVolName) + return errors.InProgressError(err.Error()) + } + + // Create the subvolume again using snapshot + Logc(ctx).WithFields(LogFields{ + "creationToken": internalVolName, + "volume": filePoolVolume, + "parentPath": internalSnapName, + }).Debug("Creating subvolume from snapshot.") + + // Create a subvolume request using snapshot + subvolumeCreateRequest := &api.SubvolumeCreateRequest{ + CreationToken: internalVolName, + Volume: filePoolVolume, + Parent: internalSnapName, // Needed only when cloning + } + + // Create the subvolume using snapshot + subvolume, poller, err = d.SDK.CreateSubvolume(ctx, subvolumeCreateRequest) + if err != nil { + Logc(ctx).WithError(err).Errorf("error creating subvolume '%s' from snapshot '%s'", + internalVolName, internalSnapName) + return errors.InProgressError(err.Error()) + } + + // Save the Poller's reference for later uses (if needed) + pollerKey = PollerKey{ + ID: subvolume.Name, + Operation: Restore, + } + + pollerResponseCache[pollerKey] = poller + } + + // Create Subvolume Object + subvolume = &api.Subvolume{ + ID: internalVolID, + ResourceGroup: resourceGroup, + NetAppAccount: netappAccount, + CapacityPool: cPoolName, + Volume: volumeName, + Name: internalVolName, + } + + if err = d.waitForSubvolumeCreate(ctx, subvolume, poller, pollerKey.Operation, false); err != nil { + if errors.IsVolumeCreatingError(err) { + return errors.InProgressError(err.Error()) + } + + return err + } + + Logc(ctx).Debugf("Subvolume '%s' restored using snapshot '%s'.", internalVolName, internalSnapName) + + // Delete temporary `-og` subvolume + subvolume = &api.Subvolume{ + ID: tempInternalVolID, + ResourceGroup: resourceGroup, + NetAppAccount: netappAccount, + CapacityPool: cPoolName, + Volume: volumeName, + Name: tempInternalVolName, + } + + // If temporary subvolume delete fails, then throwing an error would cause the complete + // restore process to repeat; thus adding a retry here to give best shot at deleting + // the temporary subvolume. + if err = d.deleteSubvolume(subvolume); err != nil { + Logc(ctx).WithError(err).Errorf("failed to delete the temporary subvolume '%s'; retrying", tempInternalVolName) + + if err = d.deleteSubvolume(subvolume); err != nil { + Logc(ctx).WithError(err).Errorf("failed to delete the temporary subvolume '%s'", tempInternalVolName) + + // Fail-safe mechanism to ensure temporary subvolume is definitely deleted. + d.ensureSubvolumeDelete(tempInternalVolID, snapshotInternalID) + + return errors.InProgressError(err.Error()) + } + } + + return nil } // DeleteSnapshot creates a snapshot of a volume. @@ -1417,25 +1613,7 @@ func (d *NASBlockStorageDriver) DeleteSnapshot( Name: creationToken, } - // If the specified snapshot subvolume already exists, return an error - poller, err := d.SDK.DeleteSubvolume(ctx, subvolume) - if err != nil { - if !errors.IsNotFoundError(err) { - return fmt.Errorf("error deleting snapshot %s; %v", creationToken, err) - } - } - - Logc(ctx).Debugf("Snapshot %s deleted.", creationToken) - - // Wait for deletion to complete - state, err := d.SDK.WaitForSubvolumeState(ctx, subvolume, api.StateDeleted, []string{api.StateError}, - d.defaultTimeout()) - - if err != nil && state == api.StateError { - Logc(ctx).WithField("subvolume", subvolume.Name).Errorf("failed to delete volume: %v", poller.Result(ctx)) - } - - return err + return d.deleteSubvolume(subvolume) } // Get tests for the existence of a volume @@ -1574,12 +1752,13 @@ func (d *NASBlockStorageDriver) CreateFollowup(ctx context.Context, volConfig *s } // Ensure subvolume is in a good state - if subvolume.ProvisioningState == api.StateError { + if subvolume.ProvisioningState != api.StateAvailable { return fmt.Errorf("subvolume %s is in %s state", creationToken, subvolume.ProvisioningState) } // Set the correct NFS mount option based on volume's protocol - NFSMountOption := fmt.Sprintf("vers=%s", strings.TrimPrefix(volume.ProtocolTypes[0], api.ProtocolTypeNFSPrefix)) + NFSMountOption := fmt.Sprintf("vers=%s", strings.TrimPrefix(volume.ProtocolTypes[0], + api.ProtocolTypeNFSPrefix)) mountOptions := utils.SetNFSVersionMountOptions(d.Config.NfsMountOptions, NFSMountOption) if len(volume.MountTargets) == 0 { @@ -1645,7 +1824,8 @@ func (d *NASBlockStorageDriver) GetVolumeExternalWrappers( ) { fields := LogFields{"Method": "GetVolumeExternalWrappers", "Type": "NASBlockStorageDriver"} Logd(ctx, d.Name(), d.Config.DebugTraceFlags["method"]).WithFields(fields).Trace(">>>> GetVolumeExternalWrappers") - defer Logd(ctx, d.Name(), d.Config.DebugTraceFlags["method"]).WithFields(fields).Trace("<<<< GetVolumeExternalWrappers") + defer Logd(ctx, d.Name(), + d.Config.DebugTraceFlags["method"]).WithFields(fields).Trace("<<<< GetVolumeExternalWrappers") // Let the caller know we're done by closing the channel defer close(channel) @@ -1809,3 +1989,82 @@ func (d *NASBlockStorageDriver) createFilePoolVolumePathHash(filePoolVolume *api return fmt.Sprintf("%032x", sha256Hash[:RequiredHashLength]) } + +func (d *NASBlockStorageDriver) deleteSubvolume(subvolume *api.Subvolume) error { + poller, err := d.SDK.DeleteSubvolume(ctx, subvolume) + if err != nil { + if !errors.IsNotFoundError(err) { + return fmt.Errorf("error deleting snapshot %s; %v", subvolume.Name, err) + } + } + + Logc(ctx).Debugf("Subvolume %s deleted.", subvolume.Name) + + // Wait for deletion to complete + state, err := d.SDK.WaitForSubvolumeState(ctx, subvolume, api.StateDeleted, []string{api.StateError}, + d.defaultTimeout()) + + if err != nil && state == api.StateError { + Logc(ctx).WithField("subvolume", subvolume.Name).Errorf("failed to delete volume: %v", poller.Result(ctx)) + } + + return err +} + +func (d *NASBlockStorageDriver) ensureSubvolumeDelete(subvolumeID, snapshotID string) { + if subvolumesToDelete == nil { + subvolumesToDelete = make(map[string]string) + } + + subvolumesToDelete[subvolumeID] = snapshotID +} + +func (d *NASBlockStorageDriver) deleteSubvolumeInSnapshotContext(ctx context.Context, subvolumeID, + snapshotID string, +) (bool, error) { + var deletedInCurrentSnapshotContext bool + + if subvolumesToDelete == nil { + return deletedInCurrentSnapshotContext, nil + } + + if existingSnapshotID, ok := subvolumesToDelete[subvolumeID]; ok { + // Subvolume deletion is needed + _, resourceGroup, _, netappAccount, cPoolName, volumeName, subvolumeName, + err := api.ParseSubvolumeID(subvolumeID) + if err != nil { + Logc(ctx).WithError(err).Errorf("Failed to parse the subvolume ID '%s'.", subvolumeID) + return deletedInCurrentSnapshotContext, err + } + + subvolume := &api.Subvolume{ + ID: subvolumeID, + ResourceGroup: resourceGroup, + NetAppAccount: netappAccount, + CapacityPool: cPoolName, + Volume: volumeName, + Name: subvolumeName, + } + + if err = d.deleteSubvolume(subvolume); err != nil { + Logc(ctx).WithError(err).Errorf("Failed to delete the subvolume '%s'.", subvolumeName) + return deletedInCurrentSnapshotContext, errors.InProgressError(err.Error()) + } + + // Remove subvolumeID from list of subvolumes required deletion + delete(subvolumesToDelete, subvolumeID) + + Logc(ctx).Debugf("Subvolume '%s' deleted.", subvolumeName) + + if snapshotID != existingSnapshotID { + Logc(ctx).Errorf("Subvolume '%s' deleted not in context of snapshot '%s' instead of snapshot '%s'.", + subvolumeName, existingSnapshotID, snapshotID) + deletedInCurrentSnapshotContext = false + } else { + Logc(ctx).Debugf("Subvolume '%s' deleted in context of snapshot '%s'.", subvolumeName, snapshotID) + deletedInCurrentSnapshotContext = true + } + } + + return deletedInCurrentSnapshotContext, nil +} diff --git a/storage_drivers/azure/azure_anf_subvolume_test.go b/storage_drivers/azure/azure_anf_subvolume_test.go index 709491d48..fa8686b90 100644 --- a/storage_drivers/azure/azure_anf_subvolume_test.go +++ b/storage_drivers/azure/azure_anf_subvolume_test.go @@ -61,6 +61,7 @@ func newMockANFSubvolumeDriver(t *testing.T) (*mockapi.MockAzure, *NASBlockStora mockAPI := mockapi.NewMockAzure(mockCtrl) + subvolumesToDelete = nil return mockAPI, newTestANFSubvolumeDriver(mockAPI) } @@ -1865,7 +1866,7 @@ func TestSubvolumeWaitForSubvolumeCreate_Creating(t *testing.T) { mockAPI.EXPECT().WaitForSubvolumeState(ctx, subVolume, api.StateAvailable, []string{api.StateError}, driver.volumeCreateTimeout).Return(state, errFailed).Times(1) - result := driver.waitForSubvolumeCreate(ctx, subVolume, nil) + result := driver.waitForSubvolumeCreate(ctx, subVolume, nil, Create, true) assert.Error(t, result, "subvolume creation is complete") } } @@ -1885,7 +1886,7 @@ func TestSubvolumeWaitForSubvolumeCreate_DeletingNotCompleted(t *testing.T) { mockAPI.EXPECT().WaitForSubvolumeState(ctx, subVolume, api.StateDeleted, []string{api.StateError}, driver.defaultTimeout()).Return(api.StateDeleted, nil).Times(1) - result := driver.waitForSubvolumeCreate(ctx, subVolume, nil) + result := driver.waitForSubvolumeCreate(ctx, subVolume, nil, Create, true) assert.Nil(t, result, "subvolume creation is complete") } @@ -1904,7 +1905,7 @@ func TestSubvolumeWaitForSubvolumeCreate_DeletingCompleted(t *testing.T) { mockAPI.EXPECT().WaitForSubvolumeState(ctx, subVolume, api.StateDeleted, []string{api.StateError}, driver.defaultTimeout()).Return(api.StateDeleted, errFailed).Times(1) - result := driver.waitForSubvolumeCreate(ctx, subVolume, nil) + result := driver.waitForSubvolumeCreate(ctx, subVolume, nil, Create, true) assert.Nil(t, result, "subvolume creation is complete") } @@ -1924,7 +1925,7 @@ func TestSubvolumeWaitForSubvolumeCreate_ErrorDelete(t *testing.T) { poller := api.PollerSVCreateResponse{} - result := driver.waitForSubvolumeCreate(ctx, subVolume, &poller) + result := driver.waitForSubvolumeCreate(ctx, subVolume, &poller, Create, true) assert.Nil(t, result, "subvolume creation is complete") } @@ -1944,7 +1945,7 @@ func TestSubvolumeWaitForSubvolumeCreate_ErrorDeleteFailed(t *testing.T) { poller := api.PollerSVCreateResponse{} - result := driver.waitForSubvolumeCreate(ctx, subVolume, &poller) + result := driver.waitForSubvolumeCreate(ctx, subVolume, &poller, Create, true) assert.Nil(t, result, "subvolume creation is complete") } @@ -1963,7 +1964,7 @@ func TestSubvolumeWaitForSubvolumeCreate_OtherStates(t *testing.T) { poller := api.PollerSVCreateResponse{} - result := driver.waitForSubvolumeCreate(ctx, subVolume, &poller) + result := driver.waitForSubvolumeCreate(ctx, subVolume, &poller, Create, true) assert.Nil(t, result, "subvolume creation is complete") } } @@ -2913,20 +2914,372 @@ func TestSubvolumeGetSnapshots_ErrorSubvolumesDoNotExist(t *testing.T) { assert.Error(t, resultErr, "no error") } +func TestSubvolumeRestoreSnapshot_InternalNameMismatch(t *testing.T) { + _, volConfig, _, _, snapConfig := getStructsForSubvolumeCreateSnapshot() + volConfig.InternalName = "random" + + _, driver := newMockANFSubvolumeDriver(t) + + result := driver.RestoreSnapshot(ctx, snapConfig, volConfig) + assert.Error(t, result, "snapshot restore should fail") +} + +func TestSubvolumeRestoreSnapshot_ErrorParsingVolConfigID(t *testing.T) { + _, volConfig, _, _, snapConfig := getStructsForSubvolumeCreateSnapshot() + volConfig.InternalID = "" + + _, driver := newMockANFSubvolumeDriver(t) + + result := driver.RestoreSnapshot(ctx, snapConfig, volConfig) + assert.Error(t, result, "snapshot restore should fail") +} + +func TestSubvolumeRestoreSnapshot_ErrorCheckingForSubvolumeIDExists(t *testing.T) { + config, volConfig, _, _, snapConfig := getStructsForSubvolumeCreateSnapshot() + tempInternalID := volConfig.InternalID + tempCopySuffix + + mockAPI, driver := newMockANFSubvolumeDriver(t) + driver.Config = *config + prefix := "trident" + + driver.populateConfigurationDefaults(ctx, &driver.Config) + driver.helper = newMockANFSubvolumeHelper() + driver.helper.Config.StoragePrefix = &prefix + + mockAPI.EXPECT().SubvolumeExistsByID(ctx, tempInternalID).Return(false, nil, fmt.Errorf("some error")).Times(1) + + result := driver.RestoreSnapshot(ctx, snapConfig, volConfig) + assert.Error(t, result, "snapshot restore should fail") +} + +func TestSubvolumeRestoreSnapshot_ErrorWaitingForTempSubvolume(t *testing.T) { + config, volConfig, subVolume, _, snapConfig := getStructsForSubvolumeCreateSnapshot() + tempInternalID := volConfig.InternalID + tempCopySuffix + + mockAPI, driver := newMockANFSubvolumeDriver(t) + driver.Config = *config + prefix := "trident" + + driver.populateConfigurationDefaults(ctx, &driver.Config) + driver.helper = newMockANFSubvolumeHelper() + driver.helper.Config.StoragePrefix = &prefix + + mockAPI.EXPECT().SubvolumeExistsByID(ctx, tempInternalID).Return(false, nil, nil).Times(1) + mockAPI.EXPECT().CreateSubvolume(ctx, gomock.Any()).Return(subVolume, nil, + fmt.Errorf("some error")).Times(1) + + result := driver.RestoreSnapshot(ctx, snapConfig, volConfig) + assert.Error(t, result, "snapshot restore should fail") +} + +func TestSubvolumeRestoreSnapshot_ErrorCreatingTempSubvolume(t *testing.T) { + config, volConfig, _, _, snapConfig := getStructsForSubvolumeCreateSnapshot() + tempInternalID := volConfig.InternalID + tempCopySuffix + + tempSubVolume := &api.Subvolume{ + ID: tempInternalID, + Name: volConfig.Name + tempCopySuffix, + } + + mockAPI, driver := newMockANFSubvolumeDriver(t) + driver.Config = *config + prefix := "trident" + + driver.populateConfigurationDefaults(ctx, &driver.Config) + driver.helper = newMockANFSubvolumeHelper() + driver.helper.Config.StoragePrefix = &prefix + + mockAPI.EXPECT().SubvolumeExistsByID(ctx, tempInternalID).Return(false, nil, nil).Times(1) + mockAPI.EXPECT().CreateSubvolume(ctx, gomock.Any()).Return(tempSubVolume, nil, nil).Times(1) + mockAPI.EXPECT().WaitForSubvolumeState(ctx, tempSubVolume, api.StateAvailable, []string{api.StateError}, + driver.volumeCreateTimeout).Return(api.StateCreating, fmt.Errorf("some error")).Times(1) + + result := driver.RestoreSnapshot(ctx, snapConfig, volConfig) + assert.Error(t, result, "snapshot restore should fail") +} + +func TestSubvolumeRestoreSnapshot_ErrorDeletingOriginalSubvolume(t *testing.T) { + config, volConfig, _, _, snapConfig := getStructsForSubvolumeCreateSnapshot() + tempInternalID := volConfig.InternalID + tempCopySuffix + + tempSubVolume := &api.Subvolume{ + ID: tempInternalID, + Name: volConfig.Name + tempCopySuffix, + } + + mockAPI, driver := newMockANFSubvolumeDriver(t) + driver.Config = *config + prefix := "trident" + + driver.populateConfigurationDefaults(ctx, &driver.Config) + driver.helper = newMockANFSubvolumeHelper() + driver.helper.Config.StoragePrefix = &prefix + + mockAPI.EXPECT().SubvolumeExistsByID(ctx, tempInternalID).Return(false, nil, nil).Times(1) + mockAPI.EXPECT().CreateSubvolume(ctx, gomock.Any()).Return(tempSubVolume, nil, nil).Times(1) + mockAPI.EXPECT().WaitForSubvolumeState(ctx, tempSubVolume, api.StateAvailable, []string{api.StateError}, + driver.volumeCreateTimeout).Return(api.StateAvailable, nil).Times(1) + mockAPI.EXPECT().DeleteSubvolume(ctx, gomock.Any()).Return(nil, fmt.Errorf("some error")).Times(1) + + result := driver.RestoreSnapshot(ctx, snapConfig, volConfig) + assert.Error(t, result, "snapshot restore should fail") +} + +func TestSubvolumeRestoreSnapshot_ErrorCreatingNewSnapshot(t *testing.T) { + config, volConfig, _, _, snapConfig := getStructsForSubvolumeCreateSnapshot() + tempInternalID := volConfig.InternalID + tempCopySuffix + + tempSubVolume := &api.Subvolume{ + ID: tempInternalID, + Name: volConfig.Name + tempCopySuffix, + } + + mockAPI, driver := newMockANFSubvolumeDriver(t) + driver.Config = *config + prefix := "trident" + + driver.populateConfigurationDefaults(ctx, &driver.Config) + driver.helper = newMockANFSubvolumeHelper() + driver.helper.Config.StoragePrefix = &prefix + + mockAPI.EXPECT().SubvolumeExistsByID(ctx, tempInternalID).Return(false, nil, nil).Times(1) + mockAPI.EXPECT().CreateSubvolume(ctx, gomock.Any()).Return(tempSubVolume, nil, nil).Times(1) + mockAPI.EXPECT().WaitForSubvolumeState(ctx, tempSubVolume, api.StateAvailable, []string{api.StateError}, + driver.volumeCreateTimeout).Return(api.StateAvailable, nil).Times(1) + mockAPI.EXPECT().DeleteSubvolume(ctx, gomock.Any()).Return(&api.PollerSVDeleteResponse{}, nil).Times(1) + mockAPI.EXPECT().WaitForSubvolumeState(ctx, gomock.Any(), api.StateDeleted, []string{api.StateError}, + driver.defaultTimeout()).Return(api.StateDeleted, nil).Times(1) + mockAPI.EXPECT().CreateSubvolume(ctx, gomock.Any()).Return(tempSubVolume, nil, fmt.Errorf("some error")).Times(1) + + result := driver.RestoreSnapshot(ctx, snapConfig, volConfig) + assert.Error(t, result, "snapshot restore should fail") +} + +func TestSubvolumeRestoreSnapshot_ErrorWaitingForNewSubvolume(t *testing.T) { + config, volConfig, _, _, snapConfig := getStructsForSubvolumeCreateSnapshot() + tempInternalID := volConfig.InternalID + tempCopySuffix + + tempSubVolume := &api.Subvolume{ + ID: tempInternalID, + Name: volConfig.Name + tempCopySuffix, + } + + mockAPI, driver := newMockANFSubvolumeDriver(t) + driver.Config = *config + prefix := "trident" + + driver.populateConfigurationDefaults(ctx, &driver.Config) + driver.helper = newMockANFSubvolumeHelper() + driver.helper.Config.StoragePrefix = &prefix + + mockAPI.EXPECT().SubvolumeExistsByID(ctx, tempInternalID).Return(false, nil, nil).Times(1) + mockAPI.EXPECT().CreateSubvolume(ctx, gomock.Any()).Return(tempSubVolume, nil, nil).Times(2) + mockAPI.EXPECT().WaitForSubvolumeState(ctx, gomock.Any(), api.StateAvailable, []string{api.StateError}, + driver.volumeCreateTimeout).Return(api.StateAvailable, nil).Times(1) + mockAPI.EXPECT().DeleteSubvolume(ctx, gomock.Any()).Return(&api.PollerSVDeleteResponse{}, nil).Times(1) + mockAPI.EXPECT().WaitForSubvolumeState(ctx, gomock.Any(), api.StateDeleted, []string{api.StateError}, + driver.defaultTimeout()).Return(api.StateDeleted, nil).Times(1) + mockAPI.EXPECT().WaitForSubvolumeState(ctx, gomock.Any(), api.StateAvailable, []string{api.StateError}, + driver.volumeCreateTimeout).Return(api.StateAvailable, fmt.Errorf("some error")).Times(1) + + result := driver.RestoreSnapshot(ctx, snapConfig, volConfig) + assert.Error(t, result, "snapshot restore should fail") +} + +func TestSubvolumeRestoreSnapshot_FailToDeleteTempSubvolume(t *testing.T) { + config, volConfig, _, _, snapConfig := getStructsForSubvolumeCreateSnapshot() + tempInternalID := volConfig.InternalID + tempCopySuffix + + tempSubVolume := &api.Subvolume{ + ID: tempInternalID, + Name: volConfig.Name + tempCopySuffix, + } + + mockAPI, driver := newMockANFSubvolumeDriver(t) + driver.Config = *config + prefix := "trident" + + driver.populateConfigurationDefaults(ctx, &driver.Config) + driver.helper = newMockANFSubvolumeHelper() + driver.helper.Config.StoragePrefix = &prefix + + mockAPI.EXPECT().SubvolumeExistsByID(ctx, tempInternalID).Return(false, nil, nil).Times(1) + mockAPI.EXPECT().CreateSubvolume(ctx, gomock.Any()).Return(tempSubVolume, nil, nil).Times(2) + mockAPI.EXPECT().WaitForSubvolumeState(ctx, gomock.Any(), api.StateAvailable, []string{api.StateError}, + driver.volumeCreateTimeout).Return(api.StateAvailable, nil).Times(2) + mockAPI.EXPECT().DeleteSubvolume(ctx, gomock.Any()).Return(&api.PollerSVDeleteResponse{}, nil).Times(1) + mockAPI.EXPECT().WaitForSubvolumeState(ctx, gomock.Any(), api.StateDeleted, []string{api.StateError}, + driver.defaultTimeout()).Return(api.StateDeleted, nil).Times(1) + mockAPI.EXPECT().DeleteSubvolume(ctx, gomock.Any()).Return(&api.PollerSVDeleteResponse{}, + fmt.Errorf("some error")).Times(2) + + result := driver.RestoreSnapshot(ctx, snapConfig, volConfig) + assert.True(t, len(subvolumesToDelete) > 0, "subvolume should be marked for deletion") + subvolumesToDelete = nil + assert.Error(t, result, "snapshot restore should fail") +} + +func TestSubvolumeRestoreSnapshot_WithTempSubvolumeDeleteRetryFail(t *testing.T) { + config, volConfig, _, _, snapConfig := getStructsForSubvolumeCreateSnapshot() + tempInternalID := volConfig.InternalID + tempCopySuffix + + tempSubVolume := &api.Subvolume{ + ID: tempInternalID, + Name: volConfig.Name + tempCopySuffix, + } + + mockAPI, driver := newMockANFSubvolumeDriver(t) + driver.Config = *config + prefix := "trident" + + driver.populateConfigurationDefaults(ctx, &driver.Config) + driver.helper = newMockANFSubvolumeHelper() + driver.helper.Config.StoragePrefix = &prefix + + mockAPI.EXPECT().SubvolumeExistsByID(ctx, tempInternalID).Return(false, nil, nil).Times(1) + mockAPI.EXPECT().CreateSubvolume(ctx, gomock.Any()).Return(tempSubVolume, nil, nil).Times(2) + mockAPI.EXPECT().WaitForSubvolumeState(ctx, gomock.Any(), api.StateAvailable, []string{api.StateError}, + driver.volumeCreateTimeout).Return(api.StateAvailable, nil).Times(2) + mockAPI.EXPECT().DeleteSubvolume(ctx, gomock.Any()).Return(&api.PollerSVDeleteResponse{}, nil).Times(1) + mockAPI.EXPECT().WaitForSubvolumeState(ctx, gomock.Any(), api.StateDeleted, []string{api.StateError}, + driver.defaultTimeout()).Return(api.StateDeleted, nil).Times(1) + mockAPI.EXPECT().DeleteSubvolume(ctx, gomock.Any()).Return(&api.PollerSVDeleteResponse{}, + fmt.Errorf("some error")).Times(3) + + result := driver.RestoreSnapshot(ctx, snapConfig, volConfig) + assert.True(t, len(subvolumesToDelete) > 0, "subvolume should be marked for deletion") + assert.Error(t, result, "snapshot restore should fail") + + result = driver.RestoreSnapshot(ctx, snapConfig, volConfig) + assert.Error(t, result, "snapshot restore should fail") +} + +func TestSubvolumeRestoreSnapshot_WithTempSubvolumeDeleteRetryDifferentSnapshotContext(t *testing.T) { + config, volConfig, _, _, snapConfig := getStructsForSubvolumeCreateSnapshot() + tempInternalID := volConfig.InternalID + tempCopySuffix + + tempSubVolume := &api.Subvolume{ + ID: tempInternalID, + Name: volConfig.Name + tempCopySuffix, + } + + mockAPI, driver := newMockANFSubvolumeDriver(t) + driver.Config = *config + prefix := "trident" + + driver.populateConfigurationDefaults(ctx, &driver.Config) + driver.helper = newMockANFSubvolumeHelper() + driver.helper.Config.StoragePrefix = &prefix + + mockAPI.EXPECT().SubvolumeExistsByID(ctx, tempInternalID).Return(false, nil, nil).Times(1) + mockAPI.EXPECT().CreateSubvolume(ctx, gomock.Any()).Return(tempSubVolume, nil, nil).Times(2) + mockAPI.EXPECT().WaitForSubvolumeState(ctx, gomock.Any(), api.StateAvailable, []string{api.StateError}, + driver.volumeCreateTimeout).Return(api.StateAvailable, nil).Times(2) + mockAPI.EXPECT().DeleteSubvolume(ctx, gomock.Any()).Return(&api.PollerSVDeleteResponse{}, nil).Times(1) + mockAPI.EXPECT().WaitForSubvolumeState(ctx, gomock.Any(), api.StateDeleted, []string{api.StateError}, + driver.defaultTimeout()).Return(api.StateDeleted, nil).Times(1) + mockAPI.EXPECT().DeleteSubvolume(ctx, gomock.Any()).Return(&api.PollerSVDeleteResponse{}, + fmt.Errorf("some error")).Times(2) + + result := driver.RestoreSnapshot(ctx, snapConfig, volConfig) + assert.True(t, len(subvolumesToDelete) > 0, "subvolume should be marked for deletion") + assert.Error(t, result, "snapshot restore should fail") + + snapConfig.InternalName = "oldsnapshot" + snapConfig.Name = "oldsnapshot" + + // Re-run complete restore with other snapshot + mockAPI.EXPECT().SubvolumeExistsByID(ctx, tempInternalID).Return(true, tempSubVolume, nil).Times(1) + mockAPI.EXPECT().CreateSubvolume(ctx, gomock.Any()).Return(tempSubVolume, nil, nil).Times(1) + mockAPI.EXPECT().WaitForSubvolumeState(ctx, gomock.Any(), api.StateAvailable, []string{api.StateError}, + driver.volumeCreateTimeout).Return(api.StateAvailable, nil).Times(2) + mockAPI.EXPECT().DeleteSubvolume(ctx, gomock.Any()).Return(&api.PollerSVDeleteResponse{}, nil).Times(3) + mockAPI.EXPECT().WaitForSubvolumeState(ctx, gomock.Any(), api.StateDeleted, []string{api.StateError}, + driver.defaultTimeout()).Return(api.StateDeleted, nil).Times(3) + + result = driver.RestoreSnapshot(ctx, snapConfig, volConfig) + assert.Nil(t, result, "snapshot restore should pass") +} + +func TestSubvolumeRestoreSnapshot_WithTempSubvolumeDeleteRetry(t *testing.T) { + config, volConfig, _, _, snapConfig := getStructsForSubvolumeCreateSnapshot() + tempInternalID := volConfig.InternalID + tempCopySuffix + + tempSubVolume := &api.Subvolume{ + ID: tempInternalID, + Name: volConfig.Name + tempCopySuffix, + } + + mockAPI, driver := newMockANFSubvolumeDriver(t) + driver.Config = *config + prefix := "trident" + + driver.populateConfigurationDefaults(ctx, &driver.Config) + driver.helper = newMockANFSubvolumeHelper() + driver.helper.Config.StoragePrefix = &prefix + + mockAPI.EXPECT().SubvolumeExistsByID(ctx, tempInternalID).Return(false, nil, nil).Times(1) + mockAPI.EXPECT().CreateSubvolume(ctx, gomock.Any()).Return(tempSubVolume, nil, nil).Times(2) + mockAPI.EXPECT().WaitForSubvolumeState(ctx, gomock.Any(), api.StateAvailable, []string{api.StateError}, + driver.volumeCreateTimeout).Return(api.StateAvailable, nil).Times(2) + mockAPI.EXPECT().DeleteSubvolume(ctx, gomock.Any()).Return(&api.PollerSVDeleteResponse{}, nil).Times(1) + mockAPI.EXPECT().WaitForSubvolumeState(ctx, gomock.Any(), api.StateDeleted, []string{api.StateError}, + driver.defaultTimeout()).Return(api.StateDeleted, nil).Times(1) + mockAPI.EXPECT().DeleteSubvolume(ctx, gomock.Any()).Return(&api.PollerSVDeleteResponse{}, + fmt.Errorf("some error")).Times(2) + + result := driver.RestoreSnapshot(ctx, snapConfig, volConfig) + assert.True(t, len(subvolumesToDelete) > 0, "subvolume should be marked for deletion") + assert.Error(t, result, "snapshot restore should fail") + + mockAPI.EXPECT().DeleteSubvolume(ctx, gomock.Any()).Return(&api.PollerSVDeleteResponse{}, nil).Times(1) + mockAPI.EXPECT().WaitForSubvolumeState(ctx, gomock.Any(), api.StateDeleted, []string{api.StateError}, + driver.defaultTimeout()).Return(api.StateDeleted, nil).Times(1) + + result = driver.RestoreSnapshot(ctx, snapConfig, volConfig) + assert.Nil(t, result, "snapshot restore should pass") +} + func TestSubvolumeRestoreSnapshot(t *testing.T) { - snapConfig := &storage.SnapshotConfig{ - Version: "1", - Name: "snap1", - InternalName: "snap1", - VolumeName: "testvol1", - VolumeInternalName: "trident-testvol1", + config, volConfig, _, _, snapConfig := getStructsForSubvolumeCreateSnapshot() + tempInternalID := volConfig.InternalID + tempCopySuffix + + tempSubVolume := &api.Subvolume{ + ID: tempInternalID, + Name: volConfig.Name + tempCopySuffix, } - _, driver := newMockANFSubvolumeDriver(t) + mockAPI, driver := newMockANFSubvolumeDriver(t) + driver.Config = *config + prefix := "trident" - result := driver.RestoreSnapshot(ctx, snapConfig, nil) + driver.populateConfigurationDefaults(ctx, &driver.Config) + driver.helper = newMockANFSubvolumeHelper() + driver.helper.Config.StoragePrefix = &prefix + + mockAPI.EXPECT().SubvolumeExistsByID(ctx, tempInternalID).Return(false, nil, nil).Times(1) + mockAPI.EXPECT().CreateSubvolume(ctx, gomock.Any()).Return(tempSubVolume, nil, nil).Times(2) + mockAPI.EXPECT().WaitForSubvolumeState(ctx, gomock.Any(), api.StateAvailable, []string{api.StateError}, + driver.volumeCreateTimeout).Return(api.StateAvailable, nil).Times(2) + mockAPI.EXPECT().DeleteSubvolume(ctx, gomock.Any()).Return(&api.PollerSVDeleteResponse{}, nil).Times(2) + mockAPI.EXPECT().WaitForSubvolumeState(ctx, gomock.Any(), api.StateDeleted, []string{api.StateError}, + driver.defaultTimeout()).Return(api.StateDeleted, nil).Times(2) - assert.Error(t, result, "restored snapshot") + result := driver.RestoreSnapshot(ctx, snapConfig, volConfig) + assert.Nil(t, result, "snapshot restore should pass") +} + +func TestDeleteSubvolumeInSnapshotContext_ParseError(t *testing.T) { + config, _, _, _, _ := getStructsForSubvolumeCreateSnapshot() + subvolumeID := "somesubvolume" + snapshotID := "somesnapshot" + + _, driver := newMockANFSubvolumeDriver(t) + driver.Config = *config + + driver.ensureSubvolumeDelete(subvolumeID, snapshotID) + _, result := driver.deleteSubvolumeInSnapshotContext(ctx, subvolumeID, snapshotID) + assert.Error(t, result, "should result in parse error") } func TestSubvolumeDeleteSnapshot(t *testing.T) { @@ -3294,8 +3647,24 @@ func TestSubvolumeCreateFollowUp_StateError(t *testing.T) { assert.Error(t, result, "parent volume found") } +func TestSubvolumeCreateFollowUp_NoMountTarget(t *testing.T) { + config, filesystems, volConfig, subVolume, _ := getStructsForSubvolumeCreate() + subVolume.ProvisioningState = api.StateAvailable + + mockAPI, driver := newMockANFSubvolumeDriver(t) + driver.Config = *config + filesystems[0].MountTargets = []api.MountTarget{} + + mockAPI.EXPECT().Subvolume(ctx, volConfig, false).Return(subVolume, nil).Times(1) + mockAPI.EXPECT().SubvolumeParentVolume(ctx, volConfig).Return(filesystems[0], nil).Times(1) + + result := driver.CreateFollowup(ctx, volConfig) + assert.Error(t, result, "has no mount targets") +} + func TestSubvolumeCreateFollowUp_MountTarget(t *testing.T) { config, filesystems, volConfig, subVolume, _ := getStructsForSubvolumeCreate() + subVolume.ProvisioningState = api.StateAvailable mockAPI, driver := newMockANFSubvolumeDriver(t) driver.Config = *config diff --git a/storage_drivers/azure/azure_anf_test.go b/storage_drivers/azure/azure_anf_test.go index 9c846eef5..7a7db03fb 100644 --- a/storage_drivers/azure/azure_anf_test.go +++ b/storage_drivers/azure/azure_anf_test.go @@ -3922,16 +3922,136 @@ func TestCreateSnapshot_SnapshotWaitFailed(t *testing.T) { } func TestRestoreSnapshot(t *testing.T) { - _, driver := newMockANFDriver(t) + mockAPI, driver := newMockANFDriver(t) + driver.initializeTelemetry(ctx, BackendUUID) + + snapTime := time.Now() + volConfig, filesystem, snapConfig, snapshot := getStructsForCreateSnapshot(ctx, driver, snapTime) + + mockAPI.EXPECT().RefreshAzureResources(ctx).Return(nil).Times(1) + mockAPI.EXPECT().Volume(ctx, volConfig).Return(filesystem, nil).Times(1) + mockAPI.EXPECT().SnapshotForVolume(ctx, filesystem, snapConfig.InternalName).Return(snapshot, nil).Times(1) + mockAPI.EXPECT().RestoreSnapshot(ctx, filesystem, snapshot).Return(nil).Times(1) + mockAPI.EXPECT().WaitForVolumeState(ctx, filesystem, api.StateAvailable, + []string{api.StateError, api.StateDeleting, api.StateDeleted}, api.DefaultSDKTimeout). + Return(api.StateAvailable, nil).Times(1) + + result := driver.RestoreSnapshot(ctx, snapConfig, volConfig) + + assert.Nil(t, result, "not nil") +} + +func TestRestoreSnapshot_DiscoveryFailed(t *testing.T) { + mockAPI, driver := newMockANFDriver(t) driver.initializeTelemetry(ctx, BackendUUID) snapTime := time.Now() volConfig, _, snapConfig, _ := getStructsForCreateSnapshot(ctx, driver, snapTime) + mockAPI.EXPECT().RefreshAzureResources(ctx).Return(errFailed).Times(1) + result := driver.RestoreSnapshot(ctx, snapConfig, volConfig) - assert.Error(t, result, "expected error") - assert.IsType(t, errors.UnsupportedError(""), result, "not UnsupportedError") + assert.NotNil(t, result, "expected error") +} + +func TestRestoreSnapshot_VolumeExistsCheckFailed(t *testing.T) { + mockAPI, driver := newMockANFDriver(t) + driver.initializeTelemetry(ctx, BackendUUID) + + snapTime := time.Now() + volConfig, _, snapConfig, _ := getStructsForCreateSnapshot(ctx, driver, snapTime) + + mockAPI.EXPECT().RefreshAzureResources(ctx).Return(nil).Times(1) + mockAPI.EXPECT().Volume(ctx, volConfig).Return(nil, errFailed).Times(1) + + result := driver.RestoreSnapshot(ctx, snapConfig, volConfig) + + assert.NotNil(t, result, "expected error") +} + +func TestRestoreSnapshot_NonexistentVolume(t *testing.T) { + mockAPI, driver := newMockANFDriver(t) + driver.initializeTelemetry(ctx, BackendUUID) + + snapTime := time.Now() + volConfig, _, snapConfig, _ := getStructsForCreateSnapshot(ctx, driver, snapTime) + + mockAPI.EXPECT().RefreshAzureResources(ctx).Return(nil).Times(1) + mockAPI.EXPECT().Volume(ctx, volConfig).Return(nil, errors.NotFoundError("not found")).Times(1) + + result := driver.RestoreSnapshot(ctx, snapConfig, volConfig) + + assert.NotNil(t, result, "expected error") +} + +func TestRestoreSnapshot_NonexistentSnapshot(t *testing.T) { + mockAPI, driver := newMockANFDriver(t) + driver.initializeTelemetry(ctx, BackendUUID) + + snapTime := time.Now() + volConfig, filesystem, snapConfig, _ := getStructsForCreateSnapshot(ctx, driver, snapTime) + + mockAPI.EXPECT().RefreshAzureResources(ctx).Return(nil).Times(1) + mockAPI.EXPECT().Volume(ctx, volConfig).Return(filesystem, nil).Times(1) + mockAPI.EXPECT().SnapshotForVolume(ctx, filesystem, "snap1").Return(nil, errors.NotFoundError("not found")).Times(1) + + result := driver.RestoreSnapshot(ctx, snapConfig, volConfig) + + assert.NotNil(t, result, "expected error") +} + +func TestRestoreSnapshot_GetSnapshotFailed(t *testing.T) { + mockAPI, driver := newMockANFDriver(t) + driver.initializeTelemetry(ctx, BackendUUID) + + snapTime := time.Now() + volConfig, filesystem, snapConfig, _ := getStructsForCreateSnapshot(ctx, driver, snapTime) + + mockAPI.EXPECT().RefreshAzureResources(ctx).Return(nil).Times(1) + mockAPI.EXPECT().Volume(ctx, volConfig).Return(filesystem, nil).Times(1) + mockAPI.EXPECT().SnapshotForVolume(ctx, filesystem, "snap1").Return(nil, errFailed).Times(1) + + result := driver.RestoreSnapshot(ctx, snapConfig, volConfig) + + assert.NotNil(t, result, "expected error") +} + +func TestRestoreSnapshot_SnapshotRestoreFailed(t *testing.T) { + mockAPI, driver := newMockANFDriver(t) + driver.initializeTelemetry(ctx, BackendUUID) + + snapTime := time.Now() + volConfig, filesystem, snapConfig, snapshot := getStructsForCreateSnapshot(ctx, driver, snapTime) + + mockAPI.EXPECT().RefreshAzureResources(ctx).Return(nil).Times(1) + mockAPI.EXPECT().Volume(ctx, volConfig).Return(filesystem, nil).Times(1) + mockAPI.EXPECT().SnapshotForVolume(ctx, filesystem, "snap1").Return(snapshot, nil).Times(1) + mockAPI.EXPECT().RestoreSnapshot(ctx, filesystem, snapshot).Return(errFailed).Times(1) + + result := driver.RestoreSnapshot(ctx, snapConfig, volConfig) + + assert.NotNil(t, result, "expected error") +} + +func TestRestoreSnapshot_VolumeWaitFailed(t *testing.T) { + mockAPI, driver := newMockANFDriver(t) + driver.initializeTelemetry(ctx, BackendUUID) + + snapTime := time.Now() + volConfig, filesystem, snapConfig, snapshot := getStructsForCreateSnapshot(ctx, driver, snapTime) + + mockAPI.EXPECT().RefreshAzureResources(ctx).Return(nil).Times(1) + mockAPI.EXPECT().Volume(ctx, volConfig).Return(filesystem, nil).Times(1) + mockAPI.EXPECT().SnapshotForVolume(ctx, filesystem, "snap1").Return(snapshot, nil).Times(1) + mockAPI.EXPECT().RestoreSnapshot(ctx, filesystem, snapshot).Return(nil).Times(1) + mockAPI.EXPECT().WaitForVolumeState(ctx, filesystem, api.StateAvailable, + []string{api.StateError, api.StateDeleting, api.StateDeleted}, + api.DefaultSDKTimeout).Return("", errFailed).Times(1) + + result := driver.RestoreSnapshot(ctx, snapConfig, volConfig) + + assert.NotNil(t, result, "expected error") } func TestDeleteSnapshot(t *testing.T) { @@ -4472,7 +4592,8 @@ func TestCreateFollowup_NonexistentVolume(t *testing.T) { func TestCreateFollowup_VolumeNotAvailable(t *testing.T) { nonAvailableStates := []string{ - api.StateAccepted, api.StateCreating, api.StateDeleting, api.StateDeleted, api.StateMoving, api.StateError, + api.StateAccepted, api.StateCreating, api.StateDeleting, api.StateDeleted, + api.StateMoving, api.StateError, api.StateReverting, } for _, state := range nonAvailableStates { diff --git a/storage_drivers/gcp/api/gcp.go b/storage_drivers/gcp/api/gcp.go index 8f0468f0c..17d92d9f4 100644 --- a/storage_drivers/gcp/api/gcp.go +++ b/storage_drivers/gcp/api/gcp.go @@ -951,8 +951,7 @@ func (d *Client) RestoreSnapshot(ctx context.Context, volume *Volume, snapshot * resourcePath := fmt.Sprintf("/Volumes/%s/Revert", volume.VolumeID) snapshotRevertRequest := &SnapshotRevertRequest{ - Name: snapshot.Name, - Region: volume.Region, + SnapshotID: snapshot.SnapshotID, } jsonRequest, err := json.Marshal(snapshotRevertRequest) diff --git a/storage_drivers/gcp/api/gcp_structs.go b/storage_drivers/gcp/api/gcp_structs.go index 94570627f..2c1c999b5 100644 --- a/storage_drivers/gcp/api/gcp_structs.go +++ b/storage_drivers/gcp/api/gcp_structs.go @@ -228,8 +228,7 @@ type SnapshotCreateRequest struct { } type SnapshotRevertRequest struct { - Name string `json:"name"` - Region string `json:"region"` + SnapshotID string `json:"snapshotId"` } type Backup struct { diff --git a/storage_drivers/gcp/gcp_cvs.go b/storage_drivers/gcp/gcp_cvs.go index 3d21517a3..3bf39339a 100644 --- a/storage_drivers/gcp/gcp_cvs.go +++ b/storage_drivers/gcp/gcp_cvs.go @@ -33,8 +33,8 @@ const ( MinimumVolumeSizeBytes = uint64(1073741824) // 1 GiB MinimumCVSVolumeSizeBytesHW = uint64(107374182400) // 100 GiB MaximumVolumesPerStoragePool = 50 - MinimumAPIVersion = "1.1.26" - MinimumSDEVersion = "2022.9.0" + MinimumAPIVersion = "1.4.0" + MinimumSDEVersion = "2023.1.2" defaultHWServiceLevel = api.UserServiceLevel1 defaultSWServiceLevel = api.PoolServiceLevel1 @@ -1627,15 +1627,13 @@ func (d *NFSStorageDriver) RestoreSnapshot( return fmt.Errorf("could not find volume %s: %v", creationToken, err) } - if volume.StorageClass == api.StorageClassSoftware { - return errors.New("software volumes do not support snapshot restore") - } - + // Get the snapshot snapshot, err := d.API.GetSnapshotForVolume(ctx, volume, internalSnapName) if err != nil { return fmt.Errorf("unable to find snapshot %s: %v", internalSnapName, err) } + // Do the restore return d.API.RestoreSnapshot(ctx, volume, snapshot) } diff --git a/storage_drivers/gcp/gcp_cvs_test.go b/storage_drivers/gcp/gcp_cvs_test.go index 19643b27f..f201e1d23 100644 --- a/storage_drivers/gcp/gcp_cvs_test.go +++ b/storage_drivers/gcp/gcp_cvs_test.go @@ -670,8 +670,8 @@ func TestInitialize(t *testing.T) { gcpClient, d := newMockGCPDriver(t) - apiVersion, _ := versionutils.ParseSemantic("1.1.26") - sdeVersion, _ := versionutils.ParseSemantic("2022.9.0") + apiVersion, _ := versionutils.ParseSemantic("1.4.0") + sdeVersion, _ := versionutils.ParseSemantic("2023.1.2") gcpClient.EXPECT().GetVersion(gomock.Any()).Return(apiVersion, sdeVersion, nil) gcpClient.EXPECT().GetVolumes(gomock.Any()).Return(nil, nil) gcpClient.EXPECT().GetServiceLevels(gomock.Any()).Return(nil, nil) @@ -716,8 +716,8 @@ func TestInitialize_MultipleVPools(t *testing.T) { gcpClient, d := newMockGCPDriver(t) - apiVersion, _ := versionutils.ParseSemantic("1.1.26") - sdeVersion, _ := versionutils.ParseSemantic("2022.9.0") + apiVersion, _ := versionutils.ParseSemantic("1.4.0") + sdeVersion, _ := versionutils.ParseSemantic("2023.1.2") gcpClient.EXPECT().GetVersion(gomock.Any()).Return(apiVersion, sdeVersion, nil) gcpClient.EXPECT().GetVolumes(gomock.Any()).Return(nil, nil) gcpClient.EXPECT().GetServiceLevels(gomock.Any()).Return(nil, nil) @@ -2004,18 +2004,6 @@ func TestRestoreSnapshot_GetVolumeError(t *testing.T) { assert.ErrorContains(t, err, "failed to get volume", "Found volume") } -func TestRestoreSnapshot_SOVolumeError(t *testing.T) { - snapConfig, _ := getSnapshotVolumeStructs() - gcpClient, d := newMockGCPDriver(t) - - gcpClient.EXPECT().GetVolumeByCreationToken(ctx(), gomock.Any()). - Return(&api.Volume{StorageClass: api.StorageClassSoftware}, nil) - - err := d.RestoreSnapshot(ctx(), snapConfig, nil) - assert.ErrorContains(t, err, "software volumes do not support snapshot restore", - "Volume belongs to hardware storage class") -} - func TestRestoreSnapshot_GetSnapshotError(t *testing.T) { snapConfig, _ := getSnapshotVolumeStructs() gcpClient, d := newMockGCPDriver(t) diff --git a/storage_drivers/ontap/ontap_common.go b/storage_drivers/ontap/ontap_common.go index 7a83fc2f5..93abf152d 100644 --- a/storage_drivers/ontap/ontap_common.go +++ b/storage_drivers/ontap/ontap_common.go @@ -2691,7 +2691,7 @@ func createFlexvolSnapshot( }, nil } } - return nil, fmt.Errorf("could not find snapshot %s for souce volume %s", internalSnapName, internalVolName) + return nil, fmt.Errorf("could not find snapshot %s for source volume %s", internalSnapName, internalVolName) } // cloneFlexvol creates a volume clone diff --git a/storage_drivers/ontap/ontap_nas_flexgroup.go b/storage_drivers/ontap/ontap_nas_flexgroup.go index 4e73bda65..ba38e042d 100644 --- a/storage_drivers/ontap/ontap_nas_flexgroup.go +++ b/storage_drivers/ontap/ontap_nas_flexgroup.go @@ -1188,7 +1188,7 @@ func createFlexgroupSnapshot( }, nil } } - return nil, fmt.Errorf("could not find snapshot %s for souce volume %s", internalSnapName, internalVolName) + return nil, fmt.Errorf("could not find snapshot %s for source volume %s", internalSnapName, internalVolName) } // CreateSnapshot creates a snapshot for the given volume diff --git a/storage_drivers/ontap/ontap_nas_flexgroup_test.go b/storage_drivers/ontap/ontap_nas_flexgroup_test.go index 9d544783b..8ad11418f 100644 --- a/storage_drivers/ontap/ontap_nas_flexgroup_test.go +++ b/storage_drivers/ontap/ontap_nas_flexgroup_test.go @@ -523,7 +523,7 @@ func TestOntapNasFlexgroupStorageDriverInitialize_ValidationFailed(t *testing.T) map[string]string{ONTAPTEST_VSERVER_AGGR_NAME: "vmdisk"}, nil, ) mockAPI.EXPECT().NetInterfaceGetDataLIFs(ctx, "nfs").Return(nil, - fmt.Errorf("Failed to get data Lifs")) // failed to get network interface + fmt.Errorf("failed to get data LIFs")) // failed to get network interface result := driver.Initialize(ctx, "CSI", string(configJSON), commonConfig, secrets, BackendUUID) assert.Error(t, result, "FlexGroup driver initialization succeeded even with failed to get data lifs") diff --git a/storage_drivers/ontap/ontap_nas_qtree_test.go b/storage_drivers/ontap/ontap_nas_qtree_test.go index 9bbb6a046..483ea0859 100644 --- a/storage_drivers/ontap/ontap_nas_qtree_test.go +++ b/storage_drivers/ontap/ontap_nas_qtree_test.go @@ -3461,3 +3461,23 @@ func TestGetCommonConfig_Success(t *testing.T) { assert.NotNil(t, result, "Expected not nil config, got nil") } + +func TestNASQtreeStorageDriver_RestoreSnapshot(t *testing.T) { + _, driver := newMockOntapNasQtreeDriver(t) + + volConfig := &storage.VolumeConfig{ + Size: "1g", + Encryption: "false", + FileSystem: "nfs", + InternalName: "vol1", + } + + snapConfig := &storage.SnapshotConfig{ + InternalName: "snap1", + VolumeInternalName: "vol1", + } + + result := driver.RestoreSnapshot(ctx, snapConfig, volConfig) + + assert.Error(t, result) +} diff --git a/storage_drivers/ontap/ontap_nas_test.go b/storage_drivers/ontap/ontap_nas_test.go index e46c0a02c..cbda19b5b 100644 --- a/storage_drivers/ontap/ontap_nas_test.go +++ b/storage_drivers/ontap/ontap_nas_test.go @@ -1305,7 +1305,6 @@ func TestOntapNasStorageDriverVolumeRestoreSnapshot(t *testing.T) { VolumeInternalName: "vol1", } - mockAPI.EXPECT().SVMName().AnyTimes().Return("SVM1") mockAPI.EXPECT().SnapshotRestoreVolume(ctx, "snap1", "vol1").Return(nil) result := driver.RestoreSnapshot(ctx, snapConfig, volConfig) @@ -1328,7 +1327,6 @@ func TestOntapNasStorageDriverVolumeRestoreSnapshot_Failure(t *testing.T) { VolumeInternalName: "vol1", } - mockAPI.EXPECT().SVMName().AnyTimes().Return("SVM1") mockAPI.EXPECT().SnapshotRestoreVolume(ctx, "snap1", "vol1").Return(fmt.Errorf("failed to restore volume")) result := driver.RestoreSnapshot(ctx, snapConfig, volConfig) diff --git a/storage_drivers/ontap/ontap_san_economy.go b/storage_drivers/ontap/ontap_san_economy.go index d0ef9d648..923fff634 100644 --- a/storage_drivers/ontap/ontap_san_economy.go +++ b/storage_drivers/ontap/ontap_san_economy.go @@ -24,11 +24,12 @@ import ( ) const ( - maxLunNameLength = 254 - minLUNsPerFlexvol = 50 - defaultLUNsPerFlexvol = 100 - maxLUNsPerFlexvol = 200 - snapshotNameSeparator = "_snapshot_" + maxLunNameLength = 254 + minLUNsPerFlexvol = 50 + defaultLUNsPerFlexvol = 100 + maxLUNsPerFlexvol = 200 + snapshotNameSeparator = "_snapshot_" + snapshotCopyNameSuffix = "_copy" ) func GetLUNPathEconomy(bucketName, volNameInternal string) string { @@ -1277,13 +1278,17 @@ func (d *SANEconomyStorageDriver) CreateSnapshot( State: storage.SnapshotStateOnline, }, nil } - return nil, fmt.Errorf("could not find snapshot %s for souce volume %s", internalSnapName, internalVolumeName) + return nil, fmt.Errorf("could not find snapshot %s for source volume %s", internalSnapName, internalVolumeName) } // RestoreSnapshot restores a volume (in place) from a snapshot. func (d *SANEconomyStorageDriver) RestoreSnapshot( ctx context.Context, snapConfig *storage.SnapshotConfig, _ *storage.VolumeConfig, ) error { + volLunName := snapConfig.VolumeInternalName + snapLunName := d.helper.GetSnapshotName(snapConfig.VolumeInternalName, snapConfig.InternalName) + snapLunCopyName := snapLunName + snapshotCopyNameSuffix + fields := LogFields{ "Method": "RestoreSnapshot", "Type": "SANEconomyStorageDriver", @@ -1293,7 +1298,105 @@ func (d *SANEconomyStorageDriver) RestoreSnapshot( Logd(ctx, d.Name(), d.Config.DebugTraceFlags["method"]).WithFields(fields).Trace(">>>> RestoreSnapshot") defer Logd(ctx, d.Name(), d.Config.DebugTraceFlags["method"]).WithFields(fields).Trace("<<<< RestoreSnapshot") - return errors.UnsupportedError(fmt.Sprintf("restoring snapshots is not supported by backend type %s", d.Name())) + // Check to see if volume LUN exists + volLunExists, volBucketVol, err := d.LUNExists(ctx, volLunName, d.FlexvolNamePrefix()) + if err != nil { + Logc(ctx).WithError(err).Errorf("Error checking for existing volume LUN: %v", err) + return err + } + if !volLunExists { + message := fmt.Sprintf("volume LUN %v does not exist", volLunName) + Logc(ctx).Warnf(message) + return errors.NotFoundError(message) + } + + // Check to see if the snapshot LUN exists + snapLunExists, snapBucketVol, err := d.LUNExists(ctx, snapLunName, d.FlexvolNamePrefix()) + if err != nil { + Logc(ctx).Errorf("Error checking for existing snapshot LUN: %v", err) + return err + } + if !snapLunExists { + return fmt.Errorf("snapshot LUN %v does not exist", snapLunName) + } + + // Sanity check to ensure both LUNs are in the same Flexvol + if volBucketVol != snapBucketVol { + return fmt.Errorf("snapshot LUN %s and volume LUN %s are in different Flexvols", snapLunName, volLunName) + } + + // Check to see if the snapshot LUN copy exists + snapLunCopyExists, snapCopyBucketVol, err := d.LUNExists(ctx, snapLunCopyName, d.FlexvolNamePrefix()) + if err != nil { + Logc(ctx).Errorf("Error checking for existing snapshot copy LUN: %v", err) + return err + } + + // Delete any copy so we can create a fresh one + if snapLunCopyExists { + + // Sanity check to ensure both LUNs are in the same Flexvol + if snapCopyBucketVol != snapBucketVol { + return fmt.Errorf("snapshot LUN %s and snapshot copy LUN %s are in different Flexvols", + snapLunName, snapLunCopyName) + } + + snapLunCopyPath := GetLUNPathEconomy(snapCopyBucketVol, snapLunCopyName) + + if err = LunUnmapAllIgroups(ctx, d.GetAPI(), snapLunCopyPath); err != nil { + msg := "error removing all mappings from LUN" + Logc(ctx).WithError(err).Error(msg) + return fmt.Errorf(msg) + } + + if err = d.API.LunDestroy(ctx, snapLunCopyPath); err != nil { + return fmt.Errorf("could not delete snapshot copy LUN %s", snapLunCopyName) + } + } + + // Get the snapshot LUN + snapLunPath := GetLUNPathEconomy(snapBucketVol, snapLunName) + snapLunInfo, err := d.API.LunGetByName(ctx, snapLunPath) + if err != nil { + return fmt.Errorf("could not get existing LUN %s; %v", snapLunPath, err) + } + + // Clone the snapshot LUN + if err = d.API.LunCloneCreate(ctx, snapBucketVol, snapLunName, snapLunCopyName, snapLunInfo.Qos); err != nil { + return fmt.Errorf("could not clone snapshot LUN %s: %v", snapLunPath, err) + } + + // Rename the original LUN + volLunPath := GetLUNPathEconomy(volBucketVol, volLunName) + tempVolLunPath := volLunPath + "_original" + if err = d.API.LunRename(ctx, volLunPath, tempVolLunPath); err != nil { + return fmt.Errorf("could not rename LUN %s: %v", volLunPath, err) + } + + // Rename snapshot copy to original LUN path + snapLunCopyPath := GetLUNPathEconomy(snapBucketVol, snapLunCopyName) + if err = d.API.LunRename(ctx, snapLunCopyPath, volLunPath); err != nil { + + // Attempt to recover by restoring the original LUN + if recoverErr := d.API.LunRename(ctx, tempVolLunPath, volLunPath); recoverErr != nil { + Logc(ctx).WithError(recoverErr).Errorf("Could not recover RestoreSnapshot by renaming LUN %s to %s.", + tempVolLunPath, volLunPath) + } + + return fmt.Errorf("could not rename LUN %s: %v", snapLunCopyPath, err) + } + + if err = LunUnmapAllIgroups(ctx, d.GetAPI(), tempVolLunPath); err != nil { + Logc(ctx).WithError(err).Warning("Could not remove all mappings from original LUN %s after RestoreSnapshot", + tempVolLunPath) + } + + // Delete original LUN + if err = d.API.LunDestroy(ctx, tempVolLunPath); err != nil { + Logc(ctx).WithError(err).Warningf("Could not delete original LUN %s after RestoreSnapshot", tempVolLunPath) + } + + return nil } // DeleteSnapshot deletes a LUN snapshot. diff --git a/storage_drivers/ontap/ontap_san_economy_test.go b/storage_drivers/ontap/ontap_san_economy_test.go index b2f99a800..31d591378 100644 --- a/storage_drivers/ontap/ontap_san_economy_test.go +++ b/storage_drivers/ontap/ontap_san_economy_test.go @@ -5,6 +5,7 @@ package ontap import ( "context" "encoding/json" + "errors" "fmt" "net" "os" @@ -25,6 +26,8 @@ import ( "github.com/netapp/trident/utils" ) +var failed = errors.New("failed") + func NewTestLUNHelper(storagePrefix string, driverContext tridentconfig.DriverContext) *LUNHelper { commonConfigJSON := fmt.Sprintf(` { @@ -2510,18 +2513,326 @@ func TestOntapSanEconomyCreateSnapshot_SnapshotNotFound(t *testing.T) { assert.Nil(t, snap, "snapshots are nil") } -func TestOntapSanEconomyRestoreSnapshot(t *testing.T) { - _, d := newMockOntapSanEcoDriver(t) +func getStructsForSnapshotRestore() ( + *storage.SnapshotConfig, string, api.Lun, string, string, api.Lun, string, api.Lun, string, +) { snapConfig := &storage.SnapshotConfig{ - InternalName: "snap_1", - VolumeName: "my_Bucket", - Name: "/vol/my_Bucket/storagePrefix_my_Lun", - VolumeInternalName: "storagePrefix_my_Lun_my_Bucket", + InternalName: "snapshot-00c6b55c-afeb-4657-b86e-156d66c0c9fc", + VolumeName: "pvc-ff297a18-921a-4435-b679-c3fea351f92c", + Name: "snapshot-00c6b55c-afeb-4657-b86e-156d66c0c9fc", + VolumeInternalName: "storagePrefix_pvc_ff297a18_921a_4435_b679_c3fea351f92c", + } + + bucketVol := "trident_lun_pool_storagePrefix_MGURDMZTKA" + + qosPolicy := api.QosPolicyGroup{ + Name: "extreme", + Kind: api.QosPolicyGroupKind, + } + + lun := api.Lun{ + Size: "1073741824", + Name: "storagePrefix_pvc_ff297a18_921a_4435_b679_c3fea351f92c", + VolumeName: "trident_lun_pool_storagePrefix_MGURDMZTKA", + Qos: qosPolicy, + } + volLunPath := "/vol/trident_lun_pool_storagePrefix_MGURDMZTKA/storagePrefix_pvc_ff297a18_921a_4435_b679_c3fea351f92c" + tempVolLunPath := volLunPath + "_original" + + snapLun := api.Lun{ + Size: "1073741824", + Name: "storagePrefix_pvc_ff297a18_921a_4435_b679_c3fea351f92c_snapshot_snapshot_00c6b55c_afeb_4657_b86e_156d66c0c9fc", + VolumeName: "trident_lun_pool_storagePrefix_MGURDMZTKA", + Qos: qosPolicy, } + snapLunPath := "/vol/trident_lun_pool_storagePrefix_MGURDMZTKA/storagePrefix_pvc_ff297a18_921a_4435_b679_c3fea351f92c_snapshot_snapshot_00c6b55c_afeb_4657_b86e_156d66c0c9fc" + + snapLunCopy := api.Lun{ + Size: "1073741824", + Name: "storagePrefix_pvc_ff297a18_921a_4435_b679_c3fea351f92c_snapshot_snapshot_00c6b55c_afeb_4657_b86e_156d66c0c9fc_copy", + VolumeName: "trident_lun_pool_storagePrefix_MGURDMZTKA", + Qos: qosPolicy, + } + snapLunCopyPath := "/vol/trident_lun_pool_storagePrefix_MGURDMZTKA/storagePrefix_pvc_ff297a18_921a_4435_b679_c3fea351f92c_snapshot_snapshot_00c6b55c_afeb_4657_b86e_156d66c0c9fc_copy" + + return snapConfig, bucketVol, lun, volLunPath, tempVolLunPath, snapLun, snapLunPath, snapLunCopy, snapLunCopyPath +} + +func TestOntapSanEconomyVolumeRestoreSnapshot(t *testing.T) { + mockAPI, d := newMockOntapSanEcoDriver(t) + + snapConfig, bucketVol, lun, volLunPath, tempVolLunPath, snapLun, snapLunPath, snapLunCopy, snapLunCopyPath := getStructsForSnapshotRestore() + + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(2).Return(api.Luns{lun, snapLun}, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return(api.Luns{}, nil) + mockAPI.EXPECT().LunGetByName(ctx, snapLunPath).Times(1).Return(&snapLun, nil) + mockAPI.EXPECT().LunCloneCreate(ctx, bucketVol, snapLun.Name, snapLunCopy.Name, snapLun.Qos).Times(1).Return(nil) + mockAPI.EXPECT().LunRename(ctx, volLunPath, tempVolLunPath).Times(1).Return(nil) + mockAPI.EXPECT().LunRename(ctx, snapLunCopyPath, volLunPath).Times(1).Return(nil) + mockAPI.EXPECT().LunListIgroupsMapped(ctx, tempVolLunPath).Times(1).Return([]string{}, nil) + mockAPI.EXPECT().LunDestroy(ctx, tempVolLunPath).Times(1).Return(nil) + + result := d.RestoreSnapshot(ctx, snapConfig, nil) + + assert.NoError(t, result) +} + +func TestOntapSanEconomyVolumeRestoreSnapshot_VolLunListFailed(t *testing.T) { + mockAPI, d := newMockOntapSanEcoDriver(t) + + snapConfig, _, _, _, _, _, _, _, _ := getStructsForSnapshotRestore() + + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return(nil, failed) + + result := d.RestoreSnapshot(ctx, snapConfig, nil) + + assert.Error(t, result) +} + +func TestOntapSanEconomyVolumeRestoreSnapshot_NonexistentVolLun(t *testing.T) { + mockAPI, d := newMockOntapSanEcoDriver(t) + + snapConfig, _, _, _, _, _, _, _, _ := getStructsForSnapshotRestore() + + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return(api.Luns{}, nil) - err := d.RestoreSnapshot(ctx, snapConfig, nil) + result := d.RestoreSnapshot(ctx, snapConfig, nil) + + assert.Error(t, result) +} + +func TestOntapSanEconomyVolumeRestoreSnapshot_SnapLunListFailed(t *testing.T) { + mockAPI, d := newMockOntapSanEcoDriver(t) + + snapConfig, _, lun, _, _, _, _, _, _ := getStructsForSnapshotRestore() + + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return(api.Luns{lun}, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return(nil, failed) + + result := d.RestoreSnapshot(ctx, snapConfig, nil) + + assert.Error(t, result) +} + +func TestOntapSanEconomyVolumeRestoreSnapshot_NonexistentSnapLun(t *testing.T) { + mockAPI, d := newMockOntapSanEcoDriver(t) + + snapConfig, _, lun, _, _, _, _, _, _ := getStructsForSnapshotRestore() + + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(2).Return(api.Luns{lun}, nil) + + result := d.RestoreSnapshot(ctx, snapConfig, nil) + + assert.Error(t, result) +} + +func TestOntapSanEconomyVolumeRestoreSnapshot_DifferentFlexvols(t *testing.T) { + mockAPI, d := newMockOntapSanEcoDriver(t) + + snapConfig, _, lun, _, _, snapLun, _, _, _ := getStructsForSnapshotRestore() + snapLun.VolumeName = "otherFlexvol" + + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(2).Return(api.Luns{lun, snapLun}, nil) + + result := d.RestoreSnapshot(ctx, snapConfig, nil) + + assert.Error(t, result) +} + +func TestOntapSanEconomyVolumeRestoreSnapshot_SnapLunCopyGetFailed(t *testing.T) { + mockAPI, d := newMockOntapSanEcoDriver(t) + + snapConfig, _, lun, _, _, snapLun, _, _, _ := getStructsForSnapshotRestore() + + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(2).Return(api.Luns{lun, snapLun}, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return(nil, failed) + + result := d.RestoreSnapshot(ctx, snapConfig, nil) + + assert.Error(t, result) +} + +func TestOntapSanEconomyVolumeRestoreSnapshot_SnapLunCopyExists(t *testing.T) { + mockAPI, d := newMockOntapSanEcoDriver(t) + + snapConfig, bucketVol, lun, volLunPath, tempVolLunPath, snapLun, snapLunPath, snapLunCopy, snapLunCopyPath := getStructsForSnapshotRestore() + + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(2).Return(api.Luns{lun, snapLun}, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return(api.Luns{snapLunCopy}, nil) + mockAPI.EXPECT().LunListIgroupsMapped(ctx, snapLunCopyPath).Times(1).Return([]string{}, nil) + mockAPI.EXPECT().LunDestroy(ctx, snapLunCopyPath).Times(1).Return(nil) + mockAPI.EXPECT().LunGetByName(ctx, snapLunPath).Times(1).Return(&snapLun, nil) + mockAPI.EXPECT().LunCloneCreate(ctx, bucketVol, snapLun.Name, snapLunCopy.Name, snapLun.Qos).Times(1).Return(nil) + mockAPI.EXPECT().LunRename(ctx, volLunPath, tempVolLunPath).Times(1).Return(nil) + mockAPI.EXPECT().LunRename(ctx, snapLunCopyPath, volLunPath).Times(1).Return(nil) + mockAPI.EXPECT().LunListIgroupsMapped(ctx, tempVolLunPath).Times(1).Return([]string{}, nil) + mockAPI.EXPECT().LunDestroy(ctx, tempVolLunPath).Times(1).Return(nil) + + result := d.RestoreSnapshot(ctx, snapConfig, nil) + + assert.NoError(t, result) +} - assert.EqualError(t, err, fmt.Sprintf("restoring snapshots is not supported by backend type %s", d.Name())) +func TestOntapSanEconomyVolumeRestoreSnapshot_SnapLunCopyExistsDifferentFlexvols(t *testing.T) { + mockAPI, d := newMockOntapSanEcoDriver(t) + + snapConfig, _, lun, _, _, snapLun, _, snapLunCopy, _ := getStructsForSnapshotRestore() + snapLunCopy.VolumeName = "otherFlexvol" + + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(2).Return(api.Luns{lun, snapLun}, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return(api.Luns{snapLunCopy}, nil) + + result := d.RestoreSnapshot(ctx, snapConfig, nil) + + assert.Error(t, result) +} + +func TestOntapSanEconomyVolumeRestoreSnapshot_SnapLunCopyExistsUnmapFailed(t *testing.T) { + mockAPI, d := newMockOntapSanEcoDriver(t) + + snapConfig, _, lun, _, _, snapLun, _, snapLunCopy, snapLunCopyPath := getStructsForSnapshotRestore() + + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(2).Return(api.Luns{lun, snapLun}, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return(api.Luns{snapLunCopy}, nil) + mockAPI.EXPECT().LunListIgroupsMapped(ctx, snapLunCopyPath).Times(1).Return(nil, failed) + + result := d.RestoreSnapshot(ctx, snapConfig, nil) + + assert.Error(t, result) +} + +func TestOntapSanEconomyVolumeRestoreSnapshot_SnapLunCopyExistsDestroyFailed(t *testing.T) { + mockAPI, d := newMockOntapSanEcoDriver(t) + + snapConfig, _, lun, _, _, snapLun, _, snapLunCopy, snapLunCopyPath := getStructsForSnapshotRestore() + + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(2).Return(api.Luns{lun, snapLun}, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return(api.Luns{snapLunCopy}, nil) + mockAPI.EXPECT().LunListIgroupsMapped(ctx, snapLunCopyPath).Times(1).Return([]string{}, nil) + mockAPI.EXPECT().LunDestroy(ctx, snapLunCopyPath).Times(1).Return(failed) + + result := d.RestoreSnapshot(ctx, snapConfig, nil) + + assert.Error(t, result) +} + +func TestOntapSanEconomyVolumeRestoreSnapshot_SnapLunGetFailed(t *testing.T) { + mockAPI, d := newMockOntapSanEcoDriver(t) + + snapConfig, _, lun, _, _, snapLun, snapLunPath, _, _ := getStructsForSnapshotRestore() + + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(2).Return(api.Luns{lun, snapLun}, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return(api.Luns{}, nil) + mockAPI.EXPECT().LunGetByName(ctx, snapLunPath).Times(1).Return(nil, failed) + + result := d.RestoreSnapshot(ctx, snapConfig, nil) + + assert.Error(t, result) +} + +func TestOntapSanEconomyVolumeRestoreSnapshot_SnapLunCloneFailed(t *testing.T) { + mockAPI, d := newMockOntapSanEcoDriver(t) + + snapConfig, bucketVol, lun, _, _, snapLun, snapLunPath, snapLunCopy, _ := getStructsForSnapshotRestore() + + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(2).Return(api.Luns{lun, snapLun}, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return(api.Luns{}, nil) + mockAPI.EXPECT().LunGetByName(ctx, snapLunPath).Times(1).Return(&snapLun, nil) + mockAPI.EXPECT().LunCloneCreate(ctx, bucketVol, snapLun.Name, snapLunCopy.Name, snapLun.Qos).Times(1).Return(failed) + + result := d.RestoreSnapshot(ctx, snapConfig, nil) + + assert.Error(t, result) +} + +func TestOntapSanEconomyVolumeRestoreSnapshot_VolLunRenameFailed(t *testing.T) { + mockAPI, d := newMockOntapSanEcoDriver(t) + + snapConfig, bucketVol, lun, volLunPath, tempVolLunPath, snapLun, snapLunPath, snapLunCopy, _ := getStructsForSnapshotRestore() + + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(2).Return(api.Luns{lun, snapLun}, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return(api.Luns{}, nil) + mockAPI.EXPECT().LunGetByName(ctx, snapLunPath).Times(1).Return(&snapLun, nil) + mockAPI.EXPECT().LunCloneCreate(ctx, bucketVol, snapLun.Name, snapLunCopy.Name, snapLun.Qos).Times(1).Return(nil) + mockAPI.EXPECT().LunRename(ctx, volLunPath, tempVolLunPath).Times(1).Return(failed) + + result := d.RestoreSnapshot(ctx, snapConfig, nil) + + assert.Error(t, result) +} + +func TestOntapSanEconomyVolumeRestoreSnapshot_SnapLunCopyRenameFailed(t *testing.T) { + mockAPI, d := newMockOntapSanEcoDriver(t) + + snapConfig, bucketVol, lun, volLunPath, tempVolLunPath, snapLun, snapLunPath, snapLunCopy, snapLunCopyPath := getStructsForSnapshotRestore() + + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(2).Return(api.Luns{lun, snapLun}, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return(api.Luns{}, nil) + mockAPI.EXPECT().LunGetByName(ctx, snapLunPath).Times(1).Return(&snapLun, nil) + mockAPI.EXPECT().LunCloneCreate(ctx, bucketVol, snapLun.Name, snapLunCopy.Name, snapLun.Qos).Times(1).Return(nil) + mockAPI.EXPECT().LunRename(ctx, volLunPath, tempVolLunPath).Times(1).Return(nil) + mockAPI.EXPECT().LunRename(ctx, snapLunCopyPath, volLunPath).Times(1).Return(failed) + mockAPI.EXPECT().LunRename(ctx, tempVolLunPath, volLunPath).Times(1).Return(nil) + + result := d.RestoreSnapshot(ctx, snapConfig, nil) + + assert.Error(t, result) +} + +func TestOntapSanEconomyVolumeRestoreSnapshot_SnapLunCopyRenamesFailed(t *testing.T) { + mockAPI, d := newMockOntapSanEcoDriver(t) + + snapConfig, bucketVol, lun, volLunPath, tempVolLunPath, snapLun, snapLunPath, snapLunCopy, snapLunCopyPath := getStructsForSnapshotRestore() + + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(2).Return(api.Luns{lun, snapLun}, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return(api.Luns{}, nil) + mockAPI.EXPECT().LunGetByName(ctx, snapLunPath).Times(1).Return(&snapLun, nil) + mockAPI.EXPECT().LunCloneCreate(ctx, bucketVol, snapLun.Name, snapLunCopy.Name, snapLun.Qos).Times(1).Return(nil) + mockAPI.EXPECT().LunRename(ctx, volLunPath, tempVolLunPath).Times(1).Return(nil) + mockAPI.EXPECT().LunRename(ctx, snapLunCopyPath, volLunPath).Times(1).Return(failed) + mockAPI.EXPECT().LunRename(ctx, tempVolLunPath, volLunPath).Times(1).Return(failed) + + result := d.RestoreSnapshot(ctx, snapConfig, nil) + + assert.Error(t, result) +} + +func TestOntapSanEconomyVolumeRestoreSnapshot_UnmapFailed(t *testing.T) { + mockAPI, d := newMockOntapSanEcoDriver(t) + + snapConfig, bucketVol, lun, volLunPath, tempVolLunPath, snapLun, snapLunPath, snapLunCopy, snapLunCopyPath := getStructsForSnapshotRestore() + + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(2).Return(api.Luns{lun, snapLun}, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return(api.Luns{}, nil) + mockAPI.EXPECT().LunGetByName(ctx, snapLunPath).Times(1).Return(&snapLun, nil) + mockAPI.EXPECT().LunCloneCreate(ctx, bucketVol, snapLun.Name, snapLunCopy.Name, snapLun.Qos).Times(1).Return(nil) + mockAPI.EXPECT().LunRename(ctx, volLunPath, tempVolLunPath).Times(1).Return(nil) + mockAPI.EXPECT().LunRename(ctx, snapLunCopyPath, volLunPath).Times(1).Return(nil) + mockAPI.EXPECT().LunListIgroupsMapped(ctx, tempVolLunPath).Times(1).Return(nil, failed) + mockAPI.EXPECT().LunDestroy(ctx, tempVolLunPath).Times(1).Return(nil) + + result := d.RestoreSnapshot(ctx, snapConfig, nil) + + assert.NoError(t, result) +} + +func TestOntapSanEconomyVolumeRestoreSnapshot_DestroyFailed(t *testing.T) { + mockAPI, d := newMockOntapSanEcoDriver(t) + + snapConfig, bucketVol, lun, volLunPath, tempVolLunPath, snapLun, snapLunPath, snapLunCopy, snapLunCopyPath := getStructsForSnapshotRestore() + + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(2).Return(api.Luns{lun, snapLun}, nil) + mockAPI.EXPECT().LunList(ctx, gomock.Any()).Times(1).Return(api.Luns{}, nil) + mockAPI.EXPECT().LunGetByName(ctx, snapLunPath).Times(1).Return(&snapLun, nil) + mockAPI.EXPECT().LunCloneCreate(ctx, bucketVol, snapLun.Name, snapLunCopy.Name, snapLun.Qos).Times(1).Return(nil) + mockAPI.EXPECT().LunRename(ctx, volLunPath, tempVolLunPath).Times(1).Return(nil) + mockAPI.EXPECT().LunRename(ctx, snapLunCopyPath, volLunPath).Times(1).Return(nil) + mockAPI.EXPECT().LunListIgroupsMapped(ctx, tempVolLunPath).Times(1).Return([]string{}, nil) + mockAPI.EXPECT().LunDestroy(ctx, tempVolLunPath).Times(1).Return(failed) + + result := d.RestoreSnapshot(ctx, snapConfig, nil) + + assert.NoError(t, result) } func TestOntapSanEconomyVolumeDeleteSnapshot(t *testing.T) { diff --git a/storage_drivers/ontap/ontap_san_test.go b/storage_drivers/ontap/ontap_san_test.go index 9f185b62b..0ae6b9c14 100644 --- a/storage_drivers/ontap/ontap_san_test.go +++ b/storage_drivers/ontap/ontap_san_test.go @@ -3670,3 +3670,46 @@ func TestOntapSanVolumeValidate_ValidateStoragePools(t *testing.T) { err := driver.validate(ctx) assert.Error(t, err, "Unexpected error") } + +func TestOntapSanStorageDriverVolumeRestoreSnapshot(t *testing.T) { + mockAPI, driver := newMockOntapSANDriver(t) + volConfig := &storage.VolumeConfig{ + Size: "1g", + Encryption: "false", + FileSystem: "nfs", + InternalName: "vol1", + } + + snapConfig := &storage.SnapshotConfig{ + InternalName: "snap1", + VolumeInternalName: "vol1", + } + + mockAPI.EXPECT().SnapshotRestoreVolume(ctx, "snap1", "vol1").Return(nil) + + result := driver.RestoreSnapshot(ctx, snapConfig, volConfig) + + assert.NoError(t, result) +} + +func TestOntapSanStorageDriverVolumeRestoreSnapshot_Failure(t *testing.T) { + mockAPI, driver := newMockOntapSANDriver(t) + + volConfig := &storage.VolumeConfig{ + Size: "1g", + Encryption: "false", + FileSystem: "nfs", + InternalName: "vol1", + } + + snapConfig := &storage.SnapshotConfig{ + InternalName: "snap1", + VolumeInternalName: "vol1", + } + + mockAPI.EXPECT().SnapshotRestoreVolume(ctx, "snap1", "vol1").Return(fmt.Errorf("failed to restore volume")) + + result := driver.RestoreSnapshot(ctx, snapConfig, volConfig) + + assert.Error(t, result) +} diff --git a/testing/fixture.go b/testing/fixture.go index c33211518..5d6d544ba 100644 --- a/testing/fixture.go +++ b/testing/fixture.go @@ -29,6 +29,7 @@ import ( jsonpatch "github.com/evanphx/json-patch/v5" "github.com/google/go-cmp/cmp" + "github.com/google/uuid" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -421,6 +422,11 @@ func (t *tracker) add( } } + // Set an initial UID if not set + if newMeta.GetUID() == "" { + newMeta.SetUID(types.UID(uuid.NewString())) + } + // Set an initial resource version if not set if newMeta.GetResourceVersion() == "" { newMeta.SetResourceVersion("1")