From 0abc731c8242a5af8682e46607f429563683da8e Mon Sep 17 00:00:00 2001 From: Prasad Ghangal Date: Mon, 6 Jan 2025 23:38:53 +0530 Subject: [PATCH] Remove support for Snapshot alpha and beta APIs (#3309) Signed-off-by: Prasad Ghangal Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- pkg/function/create_csi_snapshot.go | 10 +- pkg/function/create_csi_snapshot_static.go | 6 +- .../create_csi_snapshot_static_test.go | 108 ++- pkg/function/create_csi_snapshot_test.go | 56 +- pkg/function/delete_csi_snapshot.go | 5 +- pkg/function/delete_csi_snapshot_content.go | 6 +- .../delete_csi_snapshot_content_test.go | 88 +-- pkg/function/delete_csi_snapshot_test.go | 3 +- pkg/function/restore_csi_snapshot_test.go | 3 +- pkg/kube/snapshot/apis/v1alpha1/types.go | 219 ------ pkg/kube/snapshot/apis/v1beta1/types.go | 330 -------- pkg/kube/snapshot/snapshot.go | 35 - pkg/kube/snapshot/snapshot_alpha.go | 519 ------------- pkg/kube/snapshot/snapshot_beta.go | 446 ----------- pkg/kube/snapshot/snapshot_stable.go | 419 +++++++++- pkg/kube/snapshot/snapshot_test.go | 725 ++++-------------- pkg/kube/volume/volume.go | 5 +- 17 files changed, 636 insertions(+), 2347 deletions(-) delete mode 100644 pkg/kube/snapshot/apis/v1alpha1/types.go delete mode 100644 pkg/kube/snapshot/apis/v1beta1/types.go delete mode 100644 pkg/kube/snapshot/snapshot_alpha.go delete mode 100644 pkg/kube/snapshot/snapshot_beta.go diff --git a/pkg/function/create_csi_snapshot.go b/pkg/function/create_csi_snapshot.go index b660c914a3..d3c484f25b 100644 --- a/pkg/function/create_csi_snapshot.go +++ b/pkg/function/create_csi_snapshot.go @@ -19,7 +19,6 @@ import ( "fmt" "time" - "github.com/kanisterio/errkit" v1 "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/rand" @@ -100,14 +99,7 @@ func (c *createCSISnapshotFunc) Exec(ctx context.Context, tp param.TemplateParam if err != nil { return nil, err } - snapshotter, err := snapshot.NewSnapshotter(kubeCli, dynCli) - if err != nil { - if errkit.Is(context.DeadlineExceeded, err) { - timeoutMsg := "SnapshotContent not provisioned within given timeout. Please check if CSI driver is installed correctly and supports VolumeSnapshot feature" - return nil, errkit.Wrap(err, timeoutMsg) - } - return nil, err - } + snapshotter := snapshot.NewSnapshotter(kubeCli, dynCli) // waitForReady is set to true by default because snapshot information is needed as output artifacts waitForReady := true vs, err := createCSISnapshot(ctx, snapshotter, name, namespace, pvc, snapshotClass, waitForReady, labels) diff --git a/pkg/function/create_csi_snapshot_static.go b/pkg/function/create_csi_snapshot_static.go index b31e7167dd..14b1aae116 100644 --- a/pkg/function/create_csi_snapshot_static.go +++ b/pkg/function/create_csi_snapshot_static.go @@ -112,11 +112,7 @@ func (c *createCSISnapshotStaticFunc) Exec(ctx context.Context, tp param.Templat return nil, err } - snapshotter, err := snapshot.NewSnapshotter(kubeCli, dynCli) - if err != nil { - return nil, err - } - + snapshotter := snapshot.NewSnapshotter(kubeCli, dynCli) // waitForReady is set to true by default because snapshot information is needed as output artifacts waitForReady := true vs, err := createCSISnapshotStatic(ctx, snapshotter, name, namespace, driver, snapshotHandle, snapshotClass, waitForReady) diff --git a/pkg/function/create_csi_snapshot_static_test.go b/pkg/function/create_csi_snapshot_static_test.go index 53ccd0a919..23fc2fdaf3 100644 --- a/pkg/function/create_csi_snapshot_static_test.go +++ b/pkg/function/create_csi_snapshot_static_test.go @@ -39,80 +39,60 @@ func (testSuite *CreateCSISnapshotStaticTestSuite) SetUpSuite(c *check.C) {} func (testSuite *CreateCSISnapshotStaticTestSuite) TestCreateCSISnapshotStatic(c *check.C) { const ( snapshotName = "test-snapshot" - namespace = "test-namespace" deletionPolicy = "Retain" driver = "test-driver" - snapshotClass = "test-snapshot-class" snapshotHandle = "test-snapshot-handle" ) - for _, api := range []*metav1.APIResourceList{ - { - TypeMeta: metav1.TypeMeta{ - Kind: snapshot.VolSnapKind, - APIVersion: "v1alpha1", - }, - GroupVersion: fmt.Sprintf("%s/v1alpha1", snapshot.GroupName), + api := &metav1.APIResourceList{ + TypeMeta: metav1.TypeMeta{ + Kind: snapshot.VolSnapKind, + APIVersion: "v1", }, - { - TypeMeta: metav1.TypeMeta{ - Kind: snapshot.VolSnapKind, - APIVersion: "v1beta1", - }, - GroupVersion: fmt.Sprintf("%s/v1beta1", snapshot.GroupName), - }, - { - TypeMeta: metav1.TypeMeta{ - Kind: snapshot.VolSnapKind, - APIVersion: "v1", - }, - GroupVersion: fmt.Sprintf("%s/%s", snapshot.GroupName, snapshot.Version), - }, - } { - ctx := context.Background() - fakeCli := fake.NewSimpleClientset() - fakeCli.Resources = []*metav1.APIResourceList{api} + GroupVersion: fmt.Sprintf("%s/%s", snapshot.GroupName, snapshot.Version), + } + ctx := context.Background() + fakeCli := fake.NewSimpleClientset() + fakeCli.Resources = []*metav1.APIResourceList{api} - scheme := runtime.NewScheme() - dynCli := dynfake.NewSimpleDynamicClient(scheme) - fakeSnapshotter, err := snapshot.NewSnapshotter(fakeCli, dynCli) - c.Assert(err, check.IsNil) + scheme := runtime.NewScheme() + dynCli := dynfake.NewSimpleDynamicClient(scheme) + fakeSnapshotter := snapshot.NewSnapshotter(fakeCli, dynCli) - namespace := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: namespace, - }, - } - _, err = fakeCli.CoreV1().Namespaces().Create(ctx, namespace, metav1.CreateOptions{}) - c.Assert(err, check.IsNil) + namespace := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-namespace", + }, + } + _, err := fakeCli.CoreV1().Namespaces().Create(ctx, namespace, metav1.CreateOptions{}) + c.Assert(err, check.IsNil) - gv := strings.Split(api.GroupVersion, "/") - gvr := schema.GroupVersionResource{ - Group: gv[0], - Version: gv[1], - Resource: snapshot.VolumeSnapshotClassResourcePlural, - } + gv := strings.Split(api.GroupVersion, "/") + gvr := schema.GroupVersionResource{ + Group: gv[0], + Version: gv[1], + Resource: snapshot.VolumeSnapshotClassResourcePlural, + } - snapshotClass := snapshot.UnstructuredVolumeSnapshotClass( - gvr, - snapshotClass, - driver, - deletionPolicy, - nil) - _, err = dynCli.Resource(gvr).Create(ctx, snapshotClass, metav1.CreateOptions{}) - c.Assert(err, check.IsNil) + snapshotClass := snapshot.UnstructuredVolumeSnapshotClass( + gvr, + snapshotClass, + driver, + deletionPolicy, + nil) + _, err = dynCli.Resource(gvr).Create(ctx, snapshotClass, metav1.CreateOptions{}) + c.Assert(err, check.IsNil) - _, err = createCSISnapshotStatic( - ctx, - fakeSnapshotter, - snapshotName, - namespace.GetName(), - driver, - snapshotHandle, - snapshotClass.GetName(), false) - c.Assert(err, check.IsNil) + _, err = createCSISnapshotStatic( + ctx, + fakeSnapshotter, + snapshotName, + namespace.GetName(), + driver, + snapshotHandle, + snapshotClass.GetName(), false) + c.Assert(err, check.IsNil) - err = fakeCli.CoreV1().Namespaces().Delete(ctx, namespace.GetName(), metav1.DeleteOptions{}) - c.Assert(err, check.IsNil) - } + err = fakeCli.CoreV1().Namespaces().Delete(ctx, namespace.GetName(), metav1.DeleteOptions{}) + c.Assert(err, check.IsNil) } diff --git a/pkg/function/create_csi_snapshot_test.go b/pkg/function/create_csi_snapshot_test.go index e07511c7db..66c03f68a7 100644 --- a/pkg/function/create_csi_snapshot_test.go +++ b/pkg/function/create_csi_snapshot_test.go @@ -60,49 +60,31 @@ func (testSuite *CreateCSISnapshotTestSuite) SetUpSuite(c *check.C) { } func (testSuite *CreateCSISnapshotTestSuite) TestCreateCSISnapshot(c *check.C) { - for _, apiResourceList := range []*metav1.APIResourceList{ - { - TypeMeta: metav1.TypeMeta{ - Kind: "VolumeSnapshot", - APIVersion: "v1alpha1", - }, - GroupVersion: "snapshot.storage.k8s.io/v1alpha1", - }, - { - TypeMeta: metav1.TypeMeta{ - Kind: "VolumeSnapshot", - APIVersion: "v1beta1", - }, - GroupVersion: "snapshot.storage.k8s.io/v1beta1", + api := &metav1.APIResourceList{ + TypeMeta: metav1.TypeMeta{ + Kind: "VolumeSnapshot", + APIVersion: "v1", }, - { - TypeMeta: metav1.TypeMeta{ - Kind: "VolumeSnapshot", - APIVersion: "v1", - }, - GroupVersion: "snapshot.storage.k8s.io/v1", - }, - } { - ctx := context.Background() - fakeCli := fake.NewSimpleClientset() - fakeCli.Resources = []*metav1.APIResourceList{apiResourceList} + GroupVersion: "snapshot.storage.k8s.io/v1", + } + ctx := context.Background() + fakeCli := fake.NewSimpleClientset() + fakeCli.Resources = []*metav1.APIResourceList{api} - _, err := fakeCli.CoreV1().Namespaces().Create(ctx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: testSuite.namespace}}, metav1.CreateOptions{}) - c.Assert(err, check.IsNil) + _, err := fakeCli.CoreV1().Namespaces().Create(ctx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: testSuite.namespace}}, metav1.CreateOptions{}) + c.Assert(err, check.IsNil) - scheme := runtime.NewScheme() - fakeSnapshotter, err := snapshot.NewSnapshotter(fakeCli, dynfake.NewSimpleDynamicClient(scheme)) - c.Assert(err, check.IsNil) + scheme := runtime.NewScheme() + fakeSnapshotter := snapshot.NewSnapshotter(fakeCli, dynfake.NewSimpleDynamicClient(scheme)) - _, err = fakeCli.CoreV1().PersistentVolumeClaims(testSuite.namespace).Create(ctx, getPVCManifest(testSuite.pvcName, testSuite.storageClass), metav1.CreateOptions{}) - c.Assert(err, check.IsNil) + _, err = fakeCli.CoreV1().PersistentVolumeClaims(testSuite.namespace).Create(ctx, getPVCManifest(testSuite.pvcName, testSuite.storageClass), metav1.CreateOptions{}) + c.Assert(err, check.IsNil) - _, err = createCSISnapshot(ctx, fakeSnapshotter, testSuite.snapName, testSuite.namespace, testSuite.pvcName, testSuite.volumeSnapshotClass, false, nil) - c.Assert(err, check.IsNil) + _, err = createCSISnapshot(ctx, fakeSnapshotter, testSuite.snapName, testSuite.namespace, testSuite.pvcName, testSuite.volumeSnapshotClass, false, nil) + c.Assert(err, check.IsNil) - err = fakeCli.CoreV1().Namespaces().Delete(ctx, testSuite.namespace, metav1.DeleteOptions{}) - c.Assert(err, check.IsNil) - } + err = fakeCli.CoreV1().Namespaces().Delete(ctx, testSuite.namespace, metav1.DeleteOptions{}) + c.Assert(err, check.IsNil) } func getPVCManifest(pvcName, storageClassName string) *corev1.PersistentVolumeClaim { diff --git a/pkg/function/delete_csi_snapshot.go b/pkg/function/delete_csi_snapshot.go index 801fc58b7b..a750010cb7 100644 --- a/pkg/function/delete_csi_snapshot.go +++ b/pkg/function/delete_csi_snapshot.go @@ -77,10 +77,7 @@ func (d *deleteCSISnapshotFunc) Exec(ctx context.Context, tp param.TemplateParam if err != nil { return nil, err } - snapshotter, err := snapshot.NewSnapshotter(kubeCli, dynCli) - if err != nil { - return nil, err - } + snapshotter := snapshot.NewSnapshotter(kubeCli, dynCli) if _, err := deleteCSISnapshot(ctx, snapshotter, name, namespace); err != nil { return nil, err } diff --git a/pkg/function/delete_csi_snapshot_content.go b/pkg/function/delete_csi_snapshot_content.go index cddc21db45..d26c766828 100644 --- a/pkg/function/delete_csi_snapshot_content.go +++ b/pkg/function/delete_csi_snapshot_content.go @@ -73,11 +73,7 @@ func (d *deleteCSISnapshotContentFunc) Exec(ctx context.Context, tp param.Templa return nil, err } - snapshotter, err := snapshot.NewSnapshotter(kubeCli, dynCli) - if err != nil { - return nil, err - } - + snapshotter := snapshot.NewSnapshotter(kubeCli, dynCli) return nil, deleteCSISnapshotContent(ctx, snapshotter, name) } diff --git a/pkg/function/delete_csi_snapshot_content_test.go b/pkg/function/delete_csi_snapshot_content_test.go index 07e29e6ad9..425da0ff59 100644 --- a/pkg/function/delete_csi_snapshot_content_test.go +++ b/pkg/function/delete_csi_snapshot_content_test.go @@ -45,64 +45,46 @@ func (testSuite *DeleteCSISnapshotContentTestSuite) TestDeleteCSISnapshotContent driver = "test-delete-driver" handle = "test-delete-handle" ) - for _, api := range []*metav1.APIResourceList{ - { - TypeMeta: metav1.TypeMeta{ - Kind: snapshot.VolSnapContentKind, - APIVersion: "v1alpha1", - }, - GroupVersion: fmt.Sprintf("%s/v1alpha1", snapshot.GroupName), + api := &metav1.APIResourceList{ + TypeMeta: metav1.TypeMeta{ + Kind: snapshot.VolSnapContentKind, + APIVersion: "v1", }, - { - TypeMeta: metav1.TypeMeta{ - Kind: snapshot.VolSnapContentKind, - APIVersion: "v1beta1", - }, - GroupVersion: fmt.Sprintf("%s/v1beta1", snapshot.GroupName), - }, - { - TypeMeta: metav1.TypeMeta{ - Kind: snapshot.VolSnapContentKind, - APIVersion: "v1", - }, - GroupVersion: fmt.Sprintf("%s/%s", snapshot.GroupName, snapshot.Version), - }, - } { - ctx := context.Background() - fakeCli := fake.NewSimpleClientset() - fakeCli.Resources = []*metav1.APIResourceList{api} + GroupVersion: fmt.Sprintf("%s/%s", snapshot.GroupName, snapshot.Version), + } + ctx := context.Background() + fakeCli := fake.NewSimpleClientset() + fakeCli.Resources = []*metav1.APIResourceList{api} - scheme := runtime.NewScheme() - dynCli := dynfake.NewSimpleDynamicClient(scheme) - fakeSnapshotter, err := snapshot.NewSnapshotter(fakeCli, dynCli) - c.Assert(err, check.IsNil) + scheme := runtime.NewScheme() + dynCli := dynfake.NewSimpleDynamicClient(scheme) + fakeSnapshotter := snapshot.NewSnapshotter(fakeCli, dynCli) - source := &snapshot.Source{ - Handle: snapshotNamespace, - Driver: driver, - VolumeSnapshotClassName: snapshotClassName, - } - fakeSnapshotContentMeta := snapshot.ObjectMeta{ - Name: snapshotContentName, - } - err = fakeSnapshotter.CreateContentFromSource(ctx, source, snapshotName, - snapshotNamespace, deletionPolicy, fakeSnapshotContentMeta) - c.Assert(err, check.IsNil) + source := &snapshot.Source{ + Handle: snapshotNamespace, + Driver: driver, + VolumeSnapshotClassName: snapshotClassName, + } + fakeSnapshotContentMeta := snapshot.ObjectMeta{ + Name: snapshotContentName, + } + err := fakeSnapshotter.CreateContentFromSource(ctx, source, snapshotName, + snapshotNamespace, deletionPolicy, fakeSnapshotContentMeta) + c.Assert(err, check.IsNil) - gv := strings.Split(api.GroupVersion, "/") - gvr := schema.GroupVersionResource{ - Group: gv[0], - Version: gv[1], - Resource: snapshot.VolumeSnapshotContentResourcePlural, - } + gv := strings.Split(api.GroupVersion, "/") + gvr := schema.GroupVersionResource{ + Group: gv[0], + Version: gv[1], + Resource: snapshot.VolumeSnapshotContentResourcePlural, + } - _, err = dynCli.Resource(gvr).Get(ctx, snapshotContentName, metav1.GetOptions{}) - c.Assert(err, check.IsNil) + _, err = dynCli.Resource(gvr).Get(ctx, snapshotContentName, metav1.GetOptions{}) + c.Assert(err, check.IsNil) - err = deleteCSISnapshotContent(ctx, fakeSnapshotter, snapshotContentName) - c.Assert(err, check.IsNil) + err = deleteCSISnapshotContent(ctx, fakeSnapshotter, snapshotContentName) + c.Assert(err, check.IsNil) - _, err = dynCli.Resource(gvr).Get(ctx, snapshotContentName, metav1.GetOptions{}) - c.Assert(err, check.NotNil) - } + _, err = dynCli.Resource(gvr).Get(ctx, snapshotContentName, metav1.GetOptions{}) + c.Assert(err, check.NotNil) } diff --git a/pkg/function/delete_csi_snapshot_test.go b/pkg/function/delete_csi_snapshot_test.go index a93021bbef..1128d07ca1 100644 --- a/pkg/function/delete_csi_snapshot_test.go +++ b/pkg/function/delete_csi_snapshot_test.go @@ -85,8 +85,7 @@ func (testSuite *DeleteCSISnapshotTestSuite) TestDeleteCSISnapshot(c *check.C) { c.Assert(err, check.IsNil) scheme := runtime.NewScheme() - fakeSnapshotter, err := snapshot.NewSnapshotter(fakeCli, dynfake.NewSimpleDynamicClient(scheme)) - c.Assert(err, check.IsNil) + fakeSnapshotter := snapshot.NewSnapshotter(fakeCli, dynfake.NewSimpleDynamicClient(scheme)) originalPVC := &corev1.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ diff --git a/pkg/function/restore_csi_snapshot_test.go b/pkg/function/restore_csi_snapshot_test.go index 6ed1104e32..c65d9372fb 100644 --- a/pkg/function/restore_csi_snapshot_test.go +++ b/pkg/function/restore_csi_snapshot_test.go @@ -89,8 +89,7 @@ func (testSuite *RestoreCSISnapshotTestSuite) TestRestoreCSISnapshot(c *check.C) c.Assert(err, check.IsNil) scheme := runtime.NewScheme() - fakeSnapshotter, err := snapshot.NewSnapshotter(fakeCli, dynfake.NewSimpleDynamicClient(scheme)) - c.Assert(err, check.IsNil) + fakeSnapshotter := snapshot.NewSnapshotter(fakeCli, dynfake.NewSimpleDynamicClient(scheme)) originalPVC := getOriginalPVCManifest(testSuite.pvcName, testSuite.storageClass) createPVC(c, testSuite.namespace, originalPVC, fakeCli) diff --git a/pkg/kube/snapshot/apis/v1alpha1/types.go b/pkg/kube/snapshot/apis/v1alpha1/types.go deleted file mode 100644 index 6cb15190f3..0000000000 --- a/pkg/kube/snapshot/apis/v1alpha1/types.go +++ /dev/null @@ -1,219 +0,0 @@ -/* -Copyright 2020 The Kanister Authors. - -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1alpha1 - -import ( - corev1 "k8s.io/api/core/v1" - storage "k8s.io/api/storage/v1beta1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -const ( - // VolumeSnapshotContentResourcePlural is "volumesnapshotcontents" - VolumeSnapshotContentResourcePlural = "volumesnapshotcontents" - // VolumeSnapshotResourcePlural is "volumesnapshots" - VolumeSnapshotResourcePlural = "volumesnapshots" - // VolumeSnapshotClassResourcePlural is "volumesnapshotclasses" - VolumeSnapshotClassResourcePlural = "volumesnapshotclasses" - - GroupName = "snapshot.storage.k8s.io" - Version = "v1alpha1" -) - -var ( - // VolSnapGVR specifies GVR schema for VolumeSnapshots - VolSnapGVR = schema.GroupVersionResource{Group: GroupName, Version: Version, Resource: VolumeSnapshotResourcePlural} - // VolSnapClassGVR specifies GVR schema for VolumeSnapshotClasses - VolSnapClassGVR = schema.GroupVersionResource{Group: GroupName, Version: Version, Resource: VolumeSnapshotClassResourcePlural} - // VolSnapContentGVR specifies GVR schema for VolumeSnapshotContents - VolSnapContentGVR = schema.GroupVersionResource{Group: GroupName, Version: Version, Resource: VolumeSnapshotContentResourcePlural} -) - -// VolumeSnapshot is a user's request for taking a snapshot. Upon successful creation of the actual -// snapshot by the volume provider it is bound to the corresponding VolumeSnapshotContent. -// Only the VolumeSnapshot object is accessible to the user in the namespace. -type VolumeSnapshot struct { - metav1.TypeMeta `json:",inline"` - // Standard object's metadata. - // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata - metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - - // Spec defines the desired characteristics of a snapshot requested by a user. - Spec VolumeSnapshotSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` - - // Status represents the latest observed state of the snapshot - Status VolumeSnapshotStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` -} - -// VolumeSnapshotList is a list of VolumeSnapshot objects -type VolumeSnapshotList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - - // Items is the list of VolumeSnapshots - Items []VolumeSnapshot `json:"items" protobuf:"bytes,2,rep,name=items"` -} - -// VolumeSnapshotSpec describes the common attributes of a volume snapshot -type VolumeSnapshotSpec struct { - // Source has the information about where the snapshot is created from. - // In Alpha version, only PersistentVolumeClaim is supported as the source. - // If not specified, user can create VolumeSnapshotContent and bind it with VolumeSnapshot manually. - Source *corev1.TypedLocalObjectReference `json:"source" protobuf:"bytes,1,opt,name=source"` - - // SnapshotContentName binds the VolumeSnapshot object with the VolumeSnapshotContent - SnapshotContentName string `json:"snapshotContentName" protobuf:"bytes,2,opt,name=snapshotContentName"` - - // Name of the VolumeSnapshotClass used by the VolumeSnapshot. If not specified, a default snapshot class will - // be used if it is available. - VolumeSnapshotClassName string `json:"snapshotClassName" protobuf:"bytes,3,opt,name=snapshotClassName"` -} - -// VolumeSnapshotStatus is the status of the VolumeSnapshot -type VolumeSnapshotStatus struct { - // CreationTime is the time the snapshot was successfully created. If it is set, - // it means the snapshot was created; Otherwise the snapshot was not created. - CreationTime *metav1.Time `json:"creationTime" protobuf:"bytes,1,opt,name=creationTime"` - - // When restoring volume from the snapshot, the volume size should be equal to or - // larger than the RestoreSize if it is specified. If RestoreSize is set to nil, it means - // that the storage plugin does not have this information available. - RestoreSize *resource.Quantity `json:"restoreSize" protobuf:"bytes,2,opt,name=restoreSize"` - - // ReadyToUse is set to true only if the snapshot is ready to use (e.g., finish uploading if - // there is an uploading phase) and also VolumeSnapshot and its VolumeSnapshotContent - // bind correctly with each other. If any of the above condition is not true, ReadyToUse is - // set to false - ReadyToUse bool `json:"readyToUse" protobuf:"varint,3,opt,name=readyToUse"` - - // The last error encountered during create snapshot operation, if any. - // This field must only be set by the entity completing the create snapshot - // operation, i.e. the external-snapshotter. - Error *storage.VolumeError `json:"error,omitempty" protobuf:"bytes,4,opt,name=error,casttype=VolumeError"` -} - -// VolumeSnapshotClass describes the parameters used by storage system when -// provisioning VolumeSnapshots from PVCs. -// The name of a VolumeSnapshotClass object is significant, and is how users can request a particular class. -type VolumeSnapshotClass struct { - metav1.TypeMeta `json:",inline"` - // Standard object's metadata. - // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata - metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - - // Snapshotter is the driver expected to handle this VolumeSnapshotClass. - Snapshotter string `json:"snapshotter" protobuf:"bytes,2,opt,name=snapshotter"` - - // Parameters holds parameters for the snapshotter. - // These values are opaque to the system and are passed directly - // to the snapshotter. - Parameters map[string]string `json:"parameters,omitempty" protobuf:"bytes,3,rep,name=parameters"` - - // Optional: what happens to a snapshot content when released from its snapshot. - // The default policy is Delete if not specified. - DeletionPolicy string `json:"deletionPolicy,omitempty" protobuf:"bytes,4,opt,name=deletionPolicy"` -} - -// VolumeSnapshotClassList is a collection of snapshot classes. -type VolumeSnapshotClassList struct { - metav1.TypeMeta `json:",inline"` - // Standard list metadata - // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata - metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - - // Items is the list of VolumeSnapshotClasses - Items []VolumeSnapshotClass `json:"items" protobuf:"bytes,2,rep,name=items"` -} - -// VolumeSnapshotContent represents the actual "on-disk" snapshot object -type VolumeSnapshotContent struct { - metav1.TypeMeta `json:",inline"` - // Standard object's metadata. - // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata - metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - - // Spec represents the desired state of the snapshot content - Spec VolumeSnapshotContentSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` -} - -// VolumeSnapshotContentList is a list of VolumeSnapshotContent objects -type VolumeSnapshotContentList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - - // Items is the list of VolumeSnapshotContents - Items []VolumeSnapshotContent `json:"items" protobuf:"bytes,2,rep,name=items"` -} - -// VolumeSnapshotContentSpec is the spec of the volume snapshot content -type VolumeSnapshotContentSpec struct { - // Source represents the location and type of the volume snapshot - VolumeSnapshotSource `json:",inline" protobuf:"bytes,1,opt,name=volumeSnapshotSource"` - - // VolumeSnapshotRef is part of bi-directional binding between VolumeSnapshot - // and VolumeSnapshotContent. It becomes non-nil when bound. - VolumeSnapshotRef *corev1.ObjectReference `json:"volumeSnapshotRef" protobuf:"bytes,2,opt,name=volumeSnapshotRef"` - - // PersistentVolumeRef represents the PersistentVolume that the snapshot has been - // taken from. It becomes non-nil when VolumeSnapshot and VolumeSnapshotContent are bound. - PersistentVolumeRef *corev1.ObjectReference `json:"persistentVolumeRef" protobuf:"bytes,3,opt,name=persistentVolumeRef"` - - // Name of the VolumeSnapshotClass used by the VolumeSnapshot. If not specified, a default snapshot class will - // be used if it is available. - VolumeSnapshotClassName string `json:"snapshotClassName" protobuf:"bytes,4,opt,name=snapshotClassName"` - - // Optional: what happens to a snapshot content when released from its snapshot. It will be set to Delete by default - // if not specified - DeletionPolicy string `json:"deletionPolicy" protobuf:"bytes,5,opt,name=deletionPolicy"` -} - -// VolumeSnapshotSource represents the actual location and type of the snapshot. Only one of its members may be specified. -type VolumeSnapshotSource struct { - // CSI (Container Storage Interface) represents storage that handled by an external CSI Volume Driver (Alpha feature). - CSI *CSIVolumeSnapshotSource `json:"csiVolumeSnapshotSource,omitempty"` -} - -// CSIVolumeSnapshotSource represents the source from CSI volume snapshot -type CSIVolumeSnapshotSource struct { - // Driver is the name of the driver to use for this snapshot. - // This MUST be the same name returned by the CSI GetPluginName() call for - // that driver. - // Required. - Driver string `json:"driver" protobuf:"bytes,1,opt,name=driver"` - - // SnapshotHandle is the unique snapshot id returned by the CSI volume - // plugin’s CreateSnapshot to refer to the snapshot on all subsequent calls. - // Required. - SnapshotHandle string `json:"snapshotHandle" protobuf:"bytes,2,opt,name=snapshotHandle"` - - // Timestamp when the point-in-time snapshot is taken on the storage - // system. This timestamp will be generated by the CSI volume driver after - // the snapshot is cut. The format of this field should be a Unix nanoseconds - // time encoded as an int64. On Unix, the command `date +%s%N` returns - // the current time in nanoseconds since 1970-01-01 00:00:00 UTC. - // This field is required in the CSI spec but optional here to support static binding. - CreationTime *int64 `json:"creationTime,omitempty" protobuf:"varint,3,opt,name=creationTime"` - - // When restoring volume from the snapshot, the volume size should be equal to or - // larger than the RestoreSize if it is specified. If RestoreSize is set to nil, it means - // that the storage plugin does not have this information available. - RestoreSize *int64 `json:"restoreSize,omitempty" protobuf:"bytes,4,opt,name=restoreSize"` -} diff --git a/pkg/kube/snapshot/apis/v1beta1/types.go b/pkg/kube/snapshot/apis/v1beta1/types.go deleted file mode 100644 index e2094f6431..0000000000 --- a/pkg/kube/snapshot/apis/v1beta1/types.go +++ /dev/null @@ -1,330 +0,0 @@ -/* -Copyright 2020 The Kanister Authors. - -Copyright 2019 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package v1beta1 - -import ( - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -const ( - // VolumeSnapshotContentResourcePlural is "volumesnapshotcontents" - VolumeSnapshotContentResourcePlural = "volumesnapshotcontents" - // VolumeSnapshotResourcePlural is "volumesnapshots" - VolumeSnapshotResourcePlural = "volumesnapshots" - // VolumeSnapshotClassResourcePlural is "volumesnapshotclasses" - VolumeSnapshotClassResourcePlural = "volumesnapshotclasses" - - GroupName = "snapshot.storage.k8s.io" - Version = "v1beta1" -) - -var ( - // VolSnapGVR specifies GVR schema for VolumeSnapshots - VolSnapGVR = schema.GroupVersionResource{Group: GroupName, Version: Version, Resource: VolumeSnapshotResourcePlural} - // VolSnapClassGVR specifies GVR schema for VolumeSnapshotClasses - VolSnapClassGVR = schema.GroupVersionResource{Group: GroupName, Version: Version, Resource: VolumeSnapshotClassResourcePlural} - // VolSnapContentGVR specifies GVR schema for VolumeSnapshotContents - VolSnapContentGVR = schema.GroupVersionResource{Group: GroupName, Version: Version, Resource: VolumeSnapshotContentResourcePlural} -) - -// VolumeSnapshot is a user's request for either creating a point-in-time -// snapshot of a persistent volume, or binding to a pre-existing snapshot. -type VolumeSnapshot struct { - metav1.TypeMeta `json:",inline"` - // Standard object's metadata. - // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata - metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - - // spec defines the desired characteristics of a snapshot requested by a user. - // More info: https://kubernetes.io/docs/concepts/storage/volume-snapshots#volumesnapshots - // Required. - Spec VolumeSnapshotSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` - - // status represents the current information of a snapshot. - // NOTE: status can be modified by sources other than system controllers, - // and must not be depended upon for accuracy. - // Controllers should only use information from the VolumeSnapshotContent object - // after verifying that the binding is accurate and complete. - Status *VolumeSnapshotStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` -} - -// VolumeSnapshotList is a list of VolumeSnapshot objects -type VolumeSnapshotList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - - // List of VolumeSnapshots - Items []VolumeSnapshot `json:"items" protobuf:"bytes,2,rep,name=items"` -} - -// VolumeSnapshotSpec describes the common attributes of a volume snapshot. -type VolumeSnapshotSpec struct { - // source specifies where a snapshot will be created from. - // This field is immutable after creation. - // Required. - Source VolumeSnapshotSource `json:"source" protobuf:"bytes,1,opt,name=source"` - - // volumeSnapshotClassName is the name of the VolumeSnapshotClass requested by the VolumeSnapshot. - // If not specified, the default snapshot class will be used if one exists. - // If not specified, and there is no default snapshot class, dynamic snapshot creation will fail. - // Empty string is not allowed for this field. - // TODO(xiangqian): a webhook validation on empty string. - // More info: https://kubernetes.io/docs/concepts/storage/volume-snapshot-classes - VolumeSnapshotClassName *string `json:"volumeSnapshotClassName,omitempty" protobuf:"bytes,2,opt,name=volumeSnapshotClassName"` -} - -// VolumeSnapshotSource specifies whether the underlying snapshot should be -// dynamically taken upon creation or if a pre-existing VolumeSnapshotContent -// object should be used. -// Exactly one of its members must be set. -// Members in VolumeSnapshotSource are immutable. -// TODO(xiangqian): Add a webhook to ensure that VolumeSnapshotSource members -// will not be updated once specified. -type VolumeSnapshotSource struct { - // persistentVolumeClaimName specifies the name of the PersistentVolumeClaim - // object in the same namespace as the VolumeSnapshot object where the - // snapshot should be dynamically taken from. - // This field is immutable. - PersistentVolumeClaimName *string `json:"persistentVolumeClaimName,omitempty" protobuf:"bytes,1,opt,name=persistentVolumeClaimName"` - - // volumeSnapshotContentName specifies the name of a pre-existing VolumeSnapshotContent - // object. - // This field is immutable. - VolumeSnapshotContentName *string `json:"volumeSnapshotContentName,omitempty" protobuf:"bytes,2,opt,name=volumeSnapshotContentName"` -} - -// VolumeSnapshotStatus is the status of the VolumeSnapshot -type VolumeSnapshotStatus struct { - // boundVolumeSnapshotContentName represents the name of the VolumeSnapshotContent - // object to which the VolumeSnapshot object is bound. - // If not specified, it indicates that the VolumeSnapshot object has not been - // successfully bound to a VolumeSnapshotContent object yet. - // NOTE: Specified boundVolumeSnapshotContentName alone does not mean binding - // is valid. Controllers MUST always verify bidirectional binding between - // VolumeSnapshot and VolumeSnapshotContent to avoid possible security issues. - BoundVolumeSnapshotContentName *string `json:"boundVolumeSnapshotContentName,omitempty" protobuf:"bytes,1,opt,name=boundVolumeSnapshotContentName"` - - // creationTime is the timestamp when the point-in-time snapshot is taken - // by the underlying storage system. - // In dynamic snapshot creation case, this field will be filled in with the - // "creation_time" value returned from CSI "CreateSnapshotRequest" gRPC call. - // For a pre-existing snapshot, this field will be filled with the "creation_time" - // value returned from the CSI "ListSnapshots" gRPC call if the driver supports it. - // If not specified, it indicates that the creation time of the snapshot is unknown. - CreationTime *metav1.Time `json:"creationTime,omitempty" protobuf:"bytes,2,opt,name=creationTime"` - - // readyToUse indicates if a snapshot is ready to be used to restore a volume. - // In dynamic snapshot creation case, this field will be filled in with the - // "ready_to_use" value returned from CSI "CreateSnapshotRequest" gRPC call. - // For a pre-existing snapshot, this field will be filled with the "ready_to_use" - // value returned from the CSI "ListSnapshots" gRPC call if the driver supports it, - // otherwise, this field will be set to "True". - // If not specified, it means the readiness of a snapshot is unknown. - ReadyToUse *bool `json:"readyToUse,omitempty" protobuf:"varint,3,opt,name=readyToUse"` - - // restoreSize represents the complete size of the snapshot in bytes. - // In dynamic snapshot creation case, this field will be filled in with the - // "size_bytes" value returned from CSI "CreateSnapshotRequest" gRPC call. - // For a pre-existing snapshot, this field will be filled with the "size_bytes" - // value returned from the CSI "ListSnapshots" gRPC call if the driver supports it. - // When restoring a volume from this snapshot, the size of the volume MUST NOT - // be smaller than the restoreSize if it is specified, otherwise the restoration will fail. - // If not specified, it indicates that the size is unknown. - RestoreSize *resource.Quantity `json:"restoreSize,omitempty" protobuf:"bytes,4,opt,name=restoreSize"` - - // error is the last observed error during snapshot creation, if any. - // This field could be helpful to upper level controllers(i.e., application controller) - // to decide whether they should continue on waiting for the snapshot to be created - // based on the type of error reported. - Error *VolumeSnapshotError `json:"error,omitempty" protobuf:"bytes,5,opt,name=error,casttype=VolumeSnapshotError"` -} - -// VolumeSnapshotClass specifies parameters that a underlying storage system uses when -// creating a volume snapshot. A specific VolumeSnapshotClass is used by specifying its -// name in a VolumeSnapshot object. -// VolumeSnapshotClasses are non-namespaced -type VolumeSnapshotClass struct { - metav1.TypeMeta `json:",inline"` - // Standard object's metadata. - // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata - metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - - // driver is the name of the storage driver that handles this VolumeSnapshotClass. - // Required. - Driver string `json:"driver" protobuf:"bytes,2,opt,name=driver"` - - // parameters is a key-value map with storage driver specific parameters for creating snapshots. - // These values are opaque to Kubernetes. - Parameters map[string]string `json:"parameters,omitempty" protobuf:"bytes,3,rep,name=parameters"` - - // deletionPolicy determines whether a VolumeSnapshotContent created through - // the VolumeSnapshotClass should be deleted when its bound VolumeSnapshot is deleted. - // Supported values are "Retain" and "Delete". - // "Retain" means that the VolumeSnapshotContent and its physical snapshot on underlying storage system are kept. - // "Delete" means that the VolumeSnapshotContent and its physical snapshot on underlying storage system are deleted. - // Required. - DeletionPolicy string `json:"deletionPolicy" protobuf:"bytes,4,opt,name=deletionPolicy"` -} - -// VolumeSnapshotClassList is a collection of VolumeSnapshotClasses. -type VolumeSnapshotClassList struct { - metav1.TypeMeta `json:",inline"` - // Standard list metadata - // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata - metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - - // items is the list of VolumeSnapshotClasses - Items []VolumeSnapshotClass `json:"items" protobuf:"bytes,2,rep,name=items"` -} - -// VolumeSnapshotContent represents the actual "on-disk" snapshot object in the -// underlying storage system -type VolumeSnapshotContent struct { - metav1.TypeMeta `json:",inline"` - // Standard object's metadata. - // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata - metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - - // spec defines properties of a VolumeSnapshotContent created by the underlying storage system. - // Required. - Spec VolumeSnapshotContentSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` - - // status represents the current information of a snapshot. - Status *VolumeSnapshotContentStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` -} - -// VolumeSnapshotContentList is a list of VolumeSnapshotContent objects -type VolumeSnapshotContentList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` - - // items is the list of VolumeSnapshotContents - Items []VolumeSnapshotContent `json:"items" protobuf:"bytes,2,rep,name=items"` -} - -// VolumeSnapshotContentSpec is the specification of a VolumeSnapshotContent -type VolumeSnapshotContentSpec struct { - // volumeSnapshotRef specifies the VolumeSnapshot object to which this - // VolumeSnapshotContent object is bound. - // VolumeSnapshot.Spec.VolumeSnapshotContentName field must reference to - // this VolumeSnapshotContent's name for the bidirectional binding to be valid. - // For a pre-existing VolumeSnapshotContent object, name and namespace of the - // VolumeSnapshot object MUST be provided for binding to happen. - // This field is immutable after creation. - // Required. - VolumeSnapshotRef corev1.ObjectReference `json:"volumeSnapshotRef" protobuf:"bytes,1,opt,name=volumeSnapshotRef"` - - // deletionPolicy determines whether this VolumeSnapshotContent and its physical snapshot on - // the underlying storage system should be deleted when its bound VolumeSnapshot is deleted. - // Supported values are "Retain" and "Delete". - // "Retain" means that the VolumeSnapshotContent and its physical snapshot on underlying storage system are kept. - // "Delete" means that the VolumeSnapshotContent and its physical snapshot on underlying storage system are deleted. - // In dynamic snapshot creation case, this field will be filled in with the "DeletionPolicy" field defined in the - // VolumeSnapshotClass the VolumeSnapshot refers to. - // For pre-existing snapshots, users MUST specify this field when creating the VolumeSnapshotContent object. - // Required. - DeletionPolicy string `json:"deletionPolicy" protobuf:"bytes,2,opt,name=deletionPolicy"` - - // driver is the name of the CSI driver used to create the physical snapshot on - // the underlying storage system. - // This MUST be the same as the name returned by the CSI GetPluginName() call for - // that driver. - // Required. - Driver string `json:"driver" protobuf:"bytes,3,opt,name=driver"` - - // name of the VolumeSnapshotClass to which this snapshot belongs. - VolumeSnapshotClassName *string `json:"volumeSnapshotClassName,omitempty" protobuf:"bytes,4,opt,name=volumeSnapshotClassName"` - - // source specifies from where a snapshot will be created. - // This field is immutable after creation. - // Required. - Source VolumeSnapshotContentSource `json:"source" protobuf:"bytes,5,opt,name=source"` -} - -// VolumeSnapshotContentSource represents the CSI source of a snapshot. -// Exactly one of its members must be set. -// Members in VolumeSnapshotContentSource are immutable. -// TODO(xiangqian): Add a webhook to ensure that VolumeSnapshotContentSource members -// will be immutable once specified. -type VolumeSnapshotContentSource struct { - // volumeHandle specifies the CSI "volume_id" of the volume from which a snapshot - // should be dynamically taken from. - // This field is immutable. - VolumeHandle *string `json:"volumeHandle,omitempty" protobuf:"bytes,1,opt,name=volumeHandle"` - - // snapshotHandle specifies the CSI "snapshot_id" of a pre-existing snapshot on - // the underlying storage system. - // This field is immutable. - SnapshotHandle *string `json:"snapshotHandle,omitempty" protobuf:"bytes,2,opt,name=snapshotHandle"` -} - -// VolumeSnapshotContentStatus is the status of a VolumeSnapshotContent object -type VolumeSnapshotContentStatus struct { - // snapshotHandle is the CSI "snapshot_id" of a snapshot on the underlying storage system. - // If not specified, it indicates that dynamic snapshot creation has either failed - // or it is still in progress. - SnapshotHandle *string `json:"snapshotHandle,omitempty" protobuf:"bytes,1,opt,name=snapshotHandle"` - - // creationTime is the timestamp when the point-in-time snapshot is taken - // by the underlying storage system. - // In dynamic snapshot creation case, this field will be filled in with the - // "creation_time" value returned from CSI "CreateSnapshotRequest" gRPC call. - // For a pre-existing snapshot, this field will be filled with the "creation_time" - // value returned from the CSI "ListSnapshots" gRPC call if the driver supports it. - // If not specified, it indicates the creation time is unknown. - // The format of this field is a Unix nanoseconds time encoded as an int64. - CreationTime *int64 `json:"creationTime,omitempty" protobuf:"varint,2,opt,name=creationTime"` - - // restoreSize represents the complete size of the snapshot in bytes. - // In dynamic snapshot creation case, this field will be filled in with the - // "size_bytes" value returned from CSI "CreateSnapshotRequest" gRPC call. - // For a pre-existing snapshot, this field will be filled with the "size_bytes" - // value returned from the CSI "ListSnapshots" gRPC call if the driver supports it. - // When restoring a volume from this snapshot, the size of the volume MUST NOT - // be smaller than the restoreSize if it is specified, otherwise the restoration will fail. - // If not specified, it indicates that the size is unknown. - RestoreSize *int64 `json:"restoreSize,omitempty" protobuf:"bytes,3,opt,name=restoreSize"` - - // readyToUse indicates if a snapshot is ready to be used to restore a volume. - // In dynamic snapshot creation case, this field will be filled in with the - // "ready_to_use" value returned from CSI "CreateSnapshotRequest" gRPC call. - // For a pre-existing snapshot, this field will be filled with the "ready_to_use" - // value returned from the CSI "ListSnapshots" gRPC call if the driver supports it, - // otherwise, this field will be set to "True". - // If not specified, it means the readiness of a snapshot is unknown. - ReadyToUse *bool `json:"readyToUse,omitempty" protobuf:"varint,4,opt,name=readyToUse"` - - // error is the latest observed error during snapshot creation, if any. - Error *VolumeSnapshotError `json:"error,omitempty" protobuf:"bytes,5,opt,name=error,casttype=VolumeSnapshotError"` -} - -// VolumeSnapshotError describes an error encountered during snapshot creation. -type VolumeSnapshotError struct { - // time is the timestamp when the error was encountered. - Time *metav1.Time `json:"time,omitempty" protobuf:"bytes,1,opt,name=time"` - - // message is a string detailing the encountered error during snapshot - // creation if specified. - // NOTE: message may be logged, and it should not contain sensitive - // information. - Message *string `json:"message,omitempty" protobuf:"bytes,2,opt,name=message"` -} diff --git a/pkg/kube/snapshot/snapshot.go b/pkg/kube/snapshot/snapshot.go index 8228c9954e..0bdf23c1d2 100644 --- a/pkg/kube/snapshot/snapshot.go +++ b/pkg/kube/snapshot/snapshot.go @@ -18,17 +18,12 @@ import ( "context" "regexp" - "github.com/kanisterio/errkit" v1 "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/dynamic" - "k8s.io/client-go/kubernetes" - "github.com/kanisterio/kanister/pkg/kube" - "github.com/kanisterio/kanister/pkg/kube/snapshot/apis/v1alpha1" - "github.com/kanisterio/kanister/pkg/kube/snapshot/apis/v1beta1" "github.com/kanisterio/kanister/pkg/poll" ) @@ -124,36 +119,6 @@ type Source struct { VolumeSnapshotClassName string } -// NewSnapshotter creates and return new Snapshotter object -func NewSnapshotter(kubeCli kubernetes.Interface, dynCli dynamic.Interface) (Snapshotter, error) { - ctx := context.Background() - // Check if v1 (stable) snapshot API exists - exists, err := kube.IsGroupVersionAvailable(ctx, kubeCli.Discovery(), GroupName, Version) - if err != nil { - return nil, errkit.Wrap(err, "Failed to call discovery APIs") - } - if exists { - return NewSnapshotStable(kubeCli, dynCli), nil - } - // Check if v1beta1 snapshot API exists - exists, err = kube.IsGroupVersionAvailable(ctx, kubeCli.Discovery(), v1beta1.GroupName, v1beta1.Version) - if err != nil { - return nil, errkit.Wrap(err, "Failed to call discovery APIs") - } - if exists { - return NewSnapshotBeta(kubeCli, dynCli), nil - } - // Check if v1alpha1 snapshot API exists - exists, err = kube.IsGroupVersionAvailable(ctx, kubeCli.Discovery(), v1alpha1.GroupName, v1alpha1.Version) - if err != nil { - return nil, errkit.Wrap(err, "Failed to call discovery APIs") - } - if exists { - return NewSnapshotAlpha(kubeCli, dynCli), nil - } - return nil, errkit.New("Snapshot resources not supported") -} - // We use regexp to match because errors written in vs.Status.Error.Message are strings // and we don't have any status code or other metadata in there. var transientErrorRegexp = regexp.MustCompile("the object has been modified; please apply your changes to the latest version and try again") diff --git a/pkg/kube/snapshot/snapshot_alpha.go b/pkg/kube/snapshot/snapshot_alpha.go deleted file mode 100644 index edd818de86..0000000000 --- a/pkg/kube/snapshot/snapshot_alpha.go +++ /dev/null @@ -1,519 +0,0 @@ -// Copyright 2020 The Kanister Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package snapshot - -import ( - "context" - "encoding/json" - "fmt" - - "github.com/kanisterio/errkit" - v1 "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - pkglabels "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/uuid" - "k8s.io/client-go/dynamic" - "k8s.io/client-go/kubernetes" - - "github.com/kanisterio/kanister/pkg/blockstorage" - "github.com/kanisterio/kanister/pkg/kube/snapshot/apis/v1alpha1" -) - -const ( - PVCKind = "PersistentVolumeClaim" - - // Snapshot resource Kinds - VolSnapClassKind = "VolumeSnapshotClass" - VolSnapKind = "VolumeSnapshot" - VolSnapContentKind = "VolumeSnapshotContent" - VolSnapClassAlphaDriverKey = "snapshotter" - VolSnapClassBetaDriverKey = "driver" - DeletionPolicyDelete = "Delete" - DeletionPolicyRetain = "Retain" - CloneVolumeSnapshotClassLabelName = "kanister-cloned-from" -) - -type SnapshotAlpha struct { - kubeCli kubernetes.Interface - dynCli dynamic.Interface -} - -func NewSnapshotAlpha(kubeCli kubernetes.Interface, dynCli dynamic.Interface) Snapshotter { - return &SnapshotAlpha{kubeCli: kubeCli, dynCli: dynCli} -} - -// CloneVolumeSnapshotClass creates a copy of the source volume snapshot class -func (sna *SnapshotAlpha) CloneVolumeSnapshotClass(ctx context.Context, sourceClassName, targetClassName, newDeletionPolicy string, excludeAnnotations []string) error { - usSourceSnapClass, err := sna.dynCli.Resource(v1alpha1.VolSnapClassGVR).Get(ctx, sourceClassName, metav1.GetOptions{}) - if err != nil { - return errkit.Wrap(err, "Failed to find source VolumeSnapshotClass", "className", sourceClassName) - } - sourceSnapClass := v1alpha1.VolumeSnapshotClass{} - if err := TransformUnstructured(usSourceSnapClass, &sourceSnapClass); err != nil { - return err - } - existingAnnotations := sourceSnapClass.GetAnnotations() - for _, key := range excludeAnnotations { - delete(existingAnnotations, key) - } - usNew := UnstructuredVolumeSnapshotClassAlpha(targetClassName, sourceSnapClass.Snapshotter, newDeletionPolicy, sourceSnapClass.Parameters) - // Set Annotations/Labels - usNew.SetAnnotations(existingAnnotations) - usNew.SetLabels(map[string]string{CloneVolumeSnapshotClassLabelName: sourceClassName}) - if _, err = sna.dynCli.Resource(v1alpha1.VolSnapClassGVR).Create(ctx, usNew, metav1.CreateOptions{}); err != nil && !apierrors.IsAlreadyExists(err) { - return errkit.Wrap(err, "Failed to create VolumeSnapshotClass", "className", targetClassName) - } - return nil -} - -// GetVolumeSnapshotClass returns VolumeSnapshotClass name which is annotated with given key. -func (sna *SnapshotAlpha) GetVolumeSnapshotClass(ctx context.Context, annotationKey, annotationValue, storageClassName string) (string, error) { - return GetSnapshotClassbyAnnotation(ctx, sna.dynCli, sna.kubeCli, v1alpha1.VolSnapClassGVR, annotationKey, annotationValue, storageClassName) -} - -// Create creates a VolumeSnapshot and returns it or any error that happened meanwhile. -func (sna *SnapshotAlpha) Create(ctx context.Context, pvcName string, snapshotClass *string, waitForReady bool, snapshotMeta ObjectMeta) error { - if _, err := sna.kubeCli.CoreV1().PersistentVolumeClaims(snapshotMeta.Namespace).Get(ctx, pvcName, metav1.GetOptions{}); err != nil { - if apierrors.IsNotFound(err) { - return errkit.New("Failed to find PVC", "pvc", pvcName, "namespace", snapshotMeta.Namespace) - } - return errkit.Wrap(err, "Failed to query PVC", "pvc", pvcName, "namespace", snapshotMeta.Namespace) - } - snapshotMeta.Labels = blockstorage.SanitizeTags(snapshotMeta.Labels) - snap := UnstructuredVolumeSnapshotAlpha(pvcName, *snapshotClass, snapshotMeta, ObjectMeta{}) - if _, err := sna.dynCli.Resource(v1alpha1.VolSnapGVR).Namespace(snapshotMeta.Namespace).Create(ctx, snap, metav1.CreateOptions{}); err != nil { - return err - } - - if !waitForReady { - return nil - } - - if err := sna.WaitOnReadyToUse(ctx, snapshotMeta.Name, snapshotMeta.Namespace); err != nil { - return err - } - - _, err := sna.Get(ctx, snapshotMeta.Name, snapshotMeta.Namespace) - return err -} - -// Get will return the VolumeSnapshot in the 'namespace' with given 'name'. -func (sna *SnapshotAlpha) Get(ctx context.Context, name, namespace string) (*v1.VolumeSnapshot, error) { - us, err := sna.dynCli.Resource(v1alpha1.VolSnapGVR).Namespace(namespace).Get(ctx, name, metav1.GetOptions{}) - if err != nil { - return nil, err - } - return TransformUnstructuredSnaphotV1alphaToV1(us) -} - -func (sna *SnapshotAlpha) List(ctx context.Context, namespace string, labels map[string]string) (*v1.VolumeSnapshotList, error) { - listOptions := metav1.ListOptions{} - if labels != nil { - labelSelector := metav1.LabelSelector{MatchLabels: blockstorage.SanitizeTags(labels)} - listOptions.LabelSelector = pkglabels.Set(labelSelector.MatchLabels).String() - } - usList, err := sna.dynCli.Resource(v1alpha1.VolSnapGVR).Namespace(namespace).List(ctx, listOptions) - if err != nil { - return nil, err - } - vsList := &v1.VolumeSnapshotList{} - for _, us := range usList.Items { - vs, err := TransformUnstructuredSnaphotV1alphaToV1(&us) - if err != nil { - return nil, err - } - vsList.Items = append(vsList.Items, *vs) - } - return vsList, nil -} - -func TransformUnstructuredSnaphotV1alphaToV1(u *unstructured.Unstructured) (*v1.VolumeSnapshot, error) { - vs := &v1alpha1.VolumeSnapshot{} - if err := TransformUnstructured(u, vs); err != nil { - return nil, err - } - - // Populate v1.VolumeSnapshot object from v1alpha1.VolumeSnapshot - vsRet := v1.VolumeSnapshot{} - meta := vs.ObjectMeta.DeepCopy() - if meta == nil { - return nil, errkit.New("Invalid VolumeSnapshotObject: ObjectMeta is nil") - } - vsRet.ObjectMeta = *meta - - if vs.Spec.Source != nil && vs.Spec.Source.Kind == "PersistentVolumeClaim" { - vsRet.Spec.Source.PersistentVolumeClaimName = &vs.Spec.Source.Name - } - if vs.Spec.VolumeSnapshotClassName != "" { - vsRet.Spec.VolumeSnapshotClassName = &vs.Spec.VolumeSnapshotClassName - } - if vs.Spec.SnapshotContentName != "" { - vsRet.Spec.Source.VolumeSnapshotContentName = &vs.Spec.SnapshotContentName - } - - if vs.Status == (v1alpha1.VolumeSnapshotStatus{}) { - return &vsRet, nil - } - - // If Status is not nil, set VolumeSnapshotContentName from status - vsRet.Status = &v1.VolumeSnapshotStatus{ - CreationTime: vs.Status.CreationTime, - RestoreSize: vs.Status.RestoreSize, - } - if vs.Spec.SnapshotContentName != "" { - vsRet.Status.BoundVolumeSnapshotContentName = &vs.Spec.SnapshotContentName - } - if vs.Status.ReadyToUse { - vsRet.Status.ReadyToUse = &vs.Status.ReadyToUse - } - if vs.Status.Error != nil { - vsRet.Status.Error = &v1.VolumeSnapshotError{ - Time: &vs.Status.Error.Time, - Message: &vs.Status.Error.Message, - } - } - return &vsRet, nil -} - -// Delete will delete the VolumeSnapshot and returns any error as a result. -func (sna *SnapshotAlpha) Delete(ctx context.Context, name, namespace string) (*v1.VolumeSnapshot, error) { - snap, err := sna.Get(ctx, name, namespace) - if apierrors.IsNotFound(err) { - return nil, nil - } - if err != nil { - return nil, errkit.Wrap(err, "Failed to find VolumeSnapshot", "namespace", namespace, "name", name) - } - if err := sna.dynCli.Resource(v1alpha1.VolSnapGVR).Namespace(namespace).Delete(ctx, name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) { - return nil, errkit.Wrap(err, "Failed to delete VolumeSnapshot", "namespace", namespace, "name", name) - } - // If the Snapshot does not exist, that's an acceptable error and we ignore it - return snap, nil -} - -// DeleteContent will delete the specified VolumeSnapshotContent -func (sna *SnapshotAlpha) DeleteContent(ctx context.Context, name string) error { - if err := sna.dynCli.Resource(v1alpha1.VolSnapContentGVR).Delete(ctx, name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) { - return errkit.Wrap(err, "Failed to delete VolumeSnapshotContent", "name", name) - } - // If the Snapshot Content does not exist, that's an acceptable error and we ignore it - return nil -} - -// Clone will clone the VolumeSnapshot to namespace 'cloneNamespace'. -// Underlying VolumeSnapshotContent will be cloned with a different name. -func (sna *SnapshotAlpha) Clone(ctx context.Context, name, namespace string, waitForReady bool, snapshotMeta, snapshotContentMeta ObjectMeta) error { - _, err := sna.Get(ctx, snapshotMeta.Name, snapshotMeta.Namespace) - if err == nil { - return errkit.New("Target snapshot already exists in target namespace", "name", snapshotMeta.Name, "namespace", snapshotMeta.Namespace) - } - if !apierrors.IsNotFound(err) { - return errkit.Wrap(err, "Failed to query target Volumesnapshot", "name", snapshotMeta.Name, "namespace", snapshotMeta.Namespace) - } - - src, err := sna.GetSource(ctx, name, namespace) - if err != nil { - return errkit.Wrap(err, "Failed to get source") - } - return sna.CreateFromSource(ctx, src, waitForReady, snapshotMeta, snapshotContentMeta) -} - -// GetSource will return the CSI source that backs the volume snapshot. -func (sna *SnapshotAlpha) GetSource(ctx context.Context, snapshotName, namespace string) (*Source, error) { - snap, err := sna.Get(ctx, snapshotName, namespace) - if err != nil { - return nil, errkit.Wrap(err, "Failed to get snapshot", "name", snapshotName, "namespace", namespace) - } - if snap.Status.ReadyToUse == nil || !*snap.Status.ReadyToUse { - return nil, errkit.New("Snapshot is not ready", "name", snapshotName, "namespace", namespace) - } - if snap.Status.BoundVolumeSnapshotContentName == nil { - return nil, errkit.New("Snapshot does not have content", "name", snapshotName, "namespace", namespace) - } - - cont, err := sna.getContent(ctx, *snap.Status.BoundVolumeSnapshotContentName) - if err != nil { - return nil, errkit.Wrap(err, "Failed to get snapshot content", "name", snapshotName, "contentName", *snap.Status.BoundVolumeSnapshotContentName) - } - src := &Source{ - Handle: cont.Spec.CSI.SnapshotHandle, - Driver: cont.Spec.CSI.Driver, - RestoreSize: cont.Spec.CSI.RestoreSize, - VolumeSnapshotClassName: cont.Spec.VolumeSnapshotClassName, - } - return src, nil -} - -// CreateFromSource will create a 'Volumesnapshot' and 'VolumesnaphotContent' pair for the underlying snapshot source. -func (sna *SnapshotAlpha) CreateFromSource(ctx context.Context, source *Source, waitForReady bool, snapshotMeta, snapshotContentMeta ObjectMeta) error { - deletionPolicy, err := sna.getDeletionPolicyFromClass(source.VolumeSnapshotClassName) - if err != nil { - return errkit.Wrap(err, "Failed to get DeletionPolicy from VolumeSnapshotClass") - } - snapshotContentMeta.Name = snapshotMeta.Name + "-content-" + string(uuid.NewUUID()) - snapshotMeta.Labels = blockstorage.SanitizeTags(snapshotMeta.Labels) - snap := UnstructuredVolumeSnapshotAlpha("", source.VolumeSnapshotClassName, snapshotMeta, snapshotContentMeta) - if err := sna.CreateContentFromSource(ctx, source, snapshotMeta.Name, snapshotMeta.Namespace, deletionPolicy, snapshotContentMeta); err != nil { - return err - } - if _, err := sna.dynCli.Resource(v1alpha1.VolSnapGVR).Namespace(snapshotMeta.Namespace).Create(ctx, snap, metav1.CreateOptions{}); err != nil { - return errkit.Wrap(err, "Failed to create content", "name", snap.GetName(), "namespace", snapshotMeta.Namespace) - } - if !waitForReady { - return nil - } - return sna.WaitOnReadyToUse(ctx, snapshotMeta.Name, snapshotMeta.Namespace) -} - -// UpdateVolumeSnapshotStatusAlpha sets the readyToUse valuse of a VolumeSnapshot. -func (sna *SnapshotAlpha) UpdateVolumeSnapshotStatusAlpha(ctx context.Context, namespace string, snapshotName string, readyToUse bool) error { - us, err := sna.dynCli.Resource(v1alpha1.VolSnapGVR).Namespace(namespace).Get(ctx, snapshotName, metav1.GetOptions{}) - if err != nil { - return err - } - status, ok := us.Object["status"].(map[string]interface{}) - if !ok { - return errkit.New("Failed to convert status to map", "name", snapshotName, "status", status) - } - status["readyToUse"] = readyToUse - us.Object["status"] = status - if _, err := sna.dynCli.Resource(v1alpha1.VolSnapGVR).Namespace(namespace).UpdateStatus(ctx, us, metav1.UpdateOptions{}); err != nil { - return errkit.Wrap(err, "Failed to update status", "name", snapshotName) - } - return nil -} - -// CreateContentFromSource will create a 'VolumesnaphotContent' for the underlying snapshot source. -func (sna *SnapshotAlpha) CreateContentFromSource(ctx context.Context, source *Source, snapshotName, namespace, deletionPolicy string, snapshotContentMeta ObjectMeta) error { - content := UnstructuredVolumeSnapshotContentAlpha(snapshotName, namespace, deletionPolicy, source.Driver, source.Handle, source.VolumeSnapshotClassName, snapshotContentMeta) - if _, err := sna.dynCli.Resource(v1alpha1.VolSnapContentGVR).Create(ctx, content, metav1.CreateOptions{}); err != nil { - return errkit.Wrap(err, "Failed to create content", "contentName", content.GetName()) - } - return nil -} - -func isReadyToUseAlpha(us *unstructured.Unstructured) (bool, error) { - vs := v1alpha1.VolumeSnapshot{} - if err := TransformUnstructured(us, &vs); err != nil { - return false, err - } - // Error can be set while waiting for creation - if vs.Status.Error != nil { - return false, errkit.New(vs.Status.Error.Message) - } - return (vs.Status.ReadyToUse && vs.Status.CreationTime != nil), nil -} - -// WaitOnReadyToUse will block until the Volumesnapshot in namespace 'namespace' with name 'snapshotName' -// has status 'ReadyToUse' or 'ctx.Done()' is signalled. -func (sna *SnapshotAlpha) WaitOnReadyToUse(ctx context.Context, snapshotName, namespace string) error { - return waitOnReadyToUse(ctx, sna.dynCli, v1alpha1.VolSnapGVR, snapshotName, namespace, isReadyToUseAlpha) -} - -func (sna *SnapshotAlpha) GroupVersion(ctx context.Context) schema.GroupVersion { - return schema.GroupVersion{ - Group: v1alpha1.GroupName, - Version: v1alpha1.Version, - } -} -func (sna *SnapshotAlpha) getContent(ctx context.Context, contentName string) (*v1alpha1.VolumeSnapshotContent, error) { - us, err := sna.dynCli.Resource(v1alpha1.VolSnapContentGVR).Get(ctx, contentName, metav1.GetOptions{}) - if err != nil { - return nil, err - } - vsc := v1alpha1.VolumeSnapshotContent{} - if err := TransformUnstructured(us, &vsc); err != nil { - return nil, err - } - return &vsc, nil -} - -func (sna *SnapshotAlpha) getDeletionPolicyFromClass(snapClassName string) (string, error) { - us, err := sna.dynCli.Resource(v1alpha1.VolSnapClassGVR).Get(context.TODO(), snapClassName, metav1.GetOptions{}) - if err != nil { - return "", errkit.Wrap(err, "Failed to find VolumeSnapshotClass", "className", snapClassName) - } - vsc := v1alpha1.VolumeSnapshotClass{} - if err := TransformUnstructured(us, &vsc); err != nil { - return "", err - } - return vsc.DeletionPolicy, nil -} - -// UnstructuredVolumeSnapshotAlpha returns Unstructured object for the VolumeSnapshot resource. -// If snapshotContentMeta has name value set, UnstructuredVolumeSnapshotAlpha will return VolumeSnapshot object with VolumeSnapshotContent information. -func UnstructuredVolumeSnapshotAlpha(pvcName, snapClassName string, snapshotMeta, snapshotContentMeta ObjectMeta) *unstructured.Unstructured { - snap := &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": fmt.Sprintf("%s/%s", v1alpha1.GroupName, v1alpha1.Version), - "kind": VolSnapKind, - "metadata": map[string]interface{}{ - "name": snapshotMeta.Name, - "namespace": snapshotMeta.Namespace, - }, - }, - } - if pvcName != "" { - snap.Object["spec"] = map[string]interface{}{ - "source": map[string]interface{}{ - "kind": PVCKind, - "name": pvcName, - "namespace": snapshotMeta.Namespace, - }, - "snapshotClassName": snapClassName, - "deletionPolicy": "Delete", - } - } - if snapshotContentMeta.Name != "" { - snap.Object["spec"] = map[string]interface{}{ - "snapshotContentName": snapshotContentMeta.Name, - "snapshotClassName": snapClassName, - } - } - if snapshotMeta.Labels != nil { - snap.SetLabels(snapshotMeta.Labels) - } - if snapshotMeta.Annotations != nil { - snap.SetAnnotations(snapshotMeta.Annotations) - } - return snap -} - -// UnstructuredVolumeSnapshotContentAlpha returns Unstructured object for the VolumeSnapshotContent resource. -func UnstructuredVolumeSnapshotContentAlpha(snapshotName, snapshotNS, deletionPolicy, driver, handle, snapClassName string, snapshotContentMeta ObjectMeta) *unstructured.Unstructured { - snaphotContent := unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": fmt.Sprintf("%s/%s", v1alpha1.GroupName, v1alpha1.Version), - "kind": VolSnapContentKind, - "metadata": map[string]interface{}{ - "name": snapshotContentMeta.Name, - }, - "spec": map[string]interface{}{ - "csiVolumeSnapshotSource": map[string]interface{}{ - "driver": driver, - "snapshotHandle": handle, - }, - "volumeSnapshotRef": map[string]interface{}{ - "kind": VolSnapKind, - "name": snapshotName, - "namespace": snapshotNS, - }, - "snapshotClassName": snapClassName, - "deletionPolicy": deletionPolicy, - }, - }, - } - if snapshotContentMeta.Labels != nil { - snaphotContent.SetLabels(snapshotContentMeta.Labels) - } - if snapshotContentMeta.Annotations != nil { - snaphotContent.SetAnnotations(snapshotContentMeta.Annotations) - } - return &snaphotContent -} - -func UnstructuredVolumeSnapshotClassAlpha(name, driver, deletionPolicy string, params map[string]string) *unstructured.Unstructured { - obj := map[string]interface{}{ - "apiVersion": fmt.Sprintf("%s/%s", v1alpha1.GroupName, v1alpha1.Version), - "kind": VolSnapClassKind, - "metadata": map[string]interface{}{ - "name": name, - }, - VolSnapClassAlphaDriverKey: driver, - "deletionPolicy": deletionPolicy, - } - - if params != nil { - obj["parameters"] = Mss2msi(params) - } - - return &unstructured.Unstructured{ - Object: obj, - } -} - -// TransformUnstructured maps Unstructured object to object pointed by obj -func TransformUnstructured(u *unstructured.Unstructured, obj metav1.Object) error { - if u == nil { - return errkit.New("Cannot deserialize nil unstructured") - } - b, err := json.Marshal(u.Object) - if err != nil { - gvk := u.GetObjectKind().GroupVersionKind() - return errkit.Wrap(err, "Failed to Marshal unstructured object GroupVersionKind", "unstructured", gvk) - } - err = json.Unmarshal(b, obj) - if err != nil { - return errkit.Wrap(err, "Failed to Unmarshal unstructured object") - } - - return nil -} - -// GetSnapshotClassbyAnnotation checks if the provided annotation is present in either the storageclass -// or volumesnapshotclass and returns the volumesnapshotclass. -func GetSnapshotClassbyAnnotation(ctx context.Context, dynCli dynamic.Interface, kubeCli kubernetes.Interface, gvr schema.GroupVersionResource, annotationKey, annotationValue, storageClass string) (string, error) { - // fetch storageClass - sc, err := kubeCli.StorageV1().StorageClasses().Get(ctx, storageClass, metav1.GetOptions{}) - if err != nil { - return "", errkit.Wrap(err, "Failed to find StorageClass in the cluster", "class", storageClass) - } - // Check if storageclass annotation override is present. - if val, ok := sc.Annotations[annotationKey]; ok { - vsc, err := dynCli.Resource(gvr).Get(ctx, val, metav1.GetOptions{}) - if err != nil { - return "", errkit.Wrap(err, "Failed to get VolumeSnapshotClass specified in Storageclass annotations", "snapshotClass", val, "storageClass", sc.Name) - } - return vsc.GetName(), nil - } - us, err := dynCli.Resource(gvr).List(ctx, metav1.ListOptions{}) - if err != nil { - return "", errkit.Wrap(err, "Failed to get VolumeSnapshotClasses in the cluster") - } - if us == nil || len(us.Items) == 0 { - return "", errkit.New("Failed to find any VolumeSnapshotClass in the cluster") - } - for _, vsc := range us.Items { - ans := vsc.GetAnnotations() - driver, err := getDriverFromUnstruturedVSC(vsc) - if err != nil { - return "", errkit.Wrap(err, "Failed to get driver for VolumeSnapshotClass", "className", vsc.GetName()) - } - if val, ok := ans[annotationKey]; ok && val == annotationValue && driver == sc.Provisioner { - return vsc.GetName(), nil - } - } - return "", errkit.New("Failed to find VolumeSnapshotClass with annotation in the cluster", "annotationKey", annotationKey, "annotationValue", annotationValue) -} - -func getDriverFromUnstruturedVSC(uVSC unstructured.Unstructured) (string, error) { - if uVSC.GetKind() != VolSnapClassKind { - return "", errkit.New("Cannot get diver for kind", "kind", uVSC.GetKind()) - } - driver, ok := uVSC.Object[VolSnapClassAlphaDriverKey] - if !ok { - driver, ok = uVSC.Object[VolSnapClassBetaDriverKey] - } - if !ok { - return "", errkit.New("VolumeSnapshotClass missing driver/snapshotter field", "volumeSnapshotClass", uVSC.GetName()) - } - if driverString, ok := driver.(string); ok { - return driverString, nil - } - return "", errkit.New("Failed to convert driver to string") -} diff --git a/pkg/kube/snapshot/snapshot_beta.go b/pkg/kube/snapshot/snapshot_beta.go deleted file mode 100644 index 64fb1213c3..0000000000 --- a/pkg/kube/snapshot/snapshot_beta.go +++ /dev/null @@ -1,446 +0,0 @@ -// Copyright 2020 The Kanister Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package snapshot - -import ( - "context" - "fmt" - - "github.com/kanisterio/errkit" - v1 "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - pkglabels "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/uuid" - "k8s.io/client-go/dynamic" - "k8s.io/client-go/kubernetes" - - "github.com/kanisterio/kanister/pkg/blockstorage" - "github.com/kanisterio/kanister/pkg/kube/snapshot/apis/v1beta1" -) - -type SnapshotBeta struct { - kubeCli kubernetes.Interface - dynCli dynamic.Interface -} - -func NewSnapshotBeta(kubeCli kubernetes.Interface, dynCli dynamic.Interface) Snapshotter { - return &SnapshotBeta{kubeCli: kubeCli, dynCli: dynCli} -} - -// CloneVolumeSnapshotClass creates a copy of the source volume snapshot class -func (sna *SnapshotBeta) CloneVolumeSnapshotClass(ctx context.Context, sourceClassName, targetClassName, newDeletionPolicy string, excludeAnnotations []string) error { - return cloneSnapshotClass(ctx, sna.dynCli, v1beta1.VolSnapClassGVR, sourceClassName, targetClassName, newDeletionPolicy, excludeAnnotations) -} - -func cloneSnapshotClass(ctx context.Context, dynCli dynamic.Interface, snapClassGVR schema.GroupVersionResource, sourceClassName, targetClassName, newDeletionPolicy string, excludeAnnotations []string) error { - usSourceSnapClass, err := dynCli.Resource(snapClassGVR).Get(ctx, sourceClassName, metav1.GetOptions{}) - if err != nil { - return errkit.Wrap(err, "Failed to find source VolumeSnapshotClass", "volumeSnapshotClass", sourceClassName) - } - - sourceSnapClass := v1.VolumeSnapshotClass{} - if err := TransformUnstructured(usSourceSnapClass, &sourceSnapClass); err != nil { - return err - } - existingAnnotations := sourceSnapClass.GetAnnotations() - for _, key := range excludeAnnotations { - delete(existingAnnotations, key) - } - usNew := UnstructuredVolumeSnapshotClass(snapClassGVR, targetClassName, sourceSnapClass.Driver, newDeletionPolicy, sourceSnapClass.Parameters) - // Set Annotations/Labels - usNew.SetAnnotations(existingAnnotations) - usNew.SetLabels(map[string]string{CloneVolumeSnapshotClassLabelName: sourceClassName}) - if _, err = dynCli.Resource(snapClassGVR).Create(ctx, usNew, metav1.CreateOptions{}); err != nil && !apierrors.IsAlreadyExists(err) { - return errkit.Wrap(err, "Failed to create VolumeSnapshotClass", "volumeSnapshotClass", targetClassName) - } - return nil -} - -// GetVolumeSnapshotClass returns VolumeSnapshotClass name which is annotated with given key. -func (sna *SnapshotBeta) GetVolumeSnapshotClass(ctx context.Context, annotationKey, annotationValue, storageClassName string) (string, error) { - return GetSnapshotClassbyAnnotation(ctx, sna.dynCli, sna.kubeCli, v1beta1.VolSnapClassGVR, annotationKey, annotationValue, storageClassName) -} - -// Create creates a VolumeSnapshot and returns it or any error happened meanwhile. -func (sna *SnapshotBeta) Create(ctx context.Context, volumeName string, snapshotClass *string, waitForReady bool, snapshotMeta ObjectMeta) error { - return createSnapshot(ctx, sna.dynCli, sna.kubeCli, v1beta1.VolSnapGVR, volumeName, snapshotClass, waitForReady, snapshotMeta) -} - -func createSnapshot( - ctx context.Context, - dynCli dynamic.Interface, - kubeCli kubernetes.Interface, - snapGVR schema.GroupVersionResource, - volumeName string, - snapshotClass *string, - waitForReady bool, - snapshotMeta ObjectMeta, -) error { - if _, err := kubeCli.CoreV1().PersistentVolumeClaims(snapshotMeta.Namespace).Get(ctx, volumeName, metav1.GetOptions{}); err != nil { - if apierrors.IsNotFound(err) { - return errkit.New("Failed to find PVC", "pvc", volumeName, "namespace", snapshotMeta.Namespace) - } - return errkit.Wrap(err, "Failed to query PVC", "pvc", volumeName, "namespace", snapshotMeta.Namespace) - } - snapshotMeta.Labels = blockstorage.SanitizeTags(snapshotMeta.Labels) - snapshotContentMeta := ObjectMeta{} - snap := UnstructuredVolumeSnapshot(snapGVR, volumeName, *snapshotClass, snapshotMeta, snapshotContentMeta) - if _, err := dynCli.Resource(snapGVR).Namespace(snapshotMeta.Namespace).Create(ctx, snap, metav1.CreateOptions{}); err != nil { - return errkit.Wrap(err, "Failed to create snapshot resource", "name", snapshotMeta.Name, "namespace", snapshotMeta.Namespace) - } - - if !waitForReady { - return nil - } - - if err := waitOnReadyToUse(ctx, dynCli, snapGVR, snapshotMeta.Name, snapshotMeta.Namespace, isReadyToUseBeta); err != nil { - return err - } - - _, err := getSnapshot(ctx, dynCli, snapGVR, snapshotMeta.Name, snapshotMeta.Namespace) - return err -} - -// Get will return the VolumeSnapshot in the 'namespace' with given 'name'. -func (sna *SnapshotBeta) Get(ctx context.Context, name, namespace string) (*v1.VolumeSnapshot, error) { - return getSnapshot(ctx, sna.dynCli, v1beta1.VolSnapGVR, name, namespace) -} - -func getSnapshot(ctx context.Context, dynCli dynamic.Interface, snapGVR schema.GroupVersionResource, name, namespace string) (*v1.VolumeSnapshot, error) { - us, err := dynCli.Resource(snapGVR).Namespace(namespace).Get(ctx, name, metav1.GetOptions{}) - if err != nil { - return nil, err - } - - vs := &v1.VolumeSnapshot{} - if err := TransformUnstructured(us, vs); err != nil { - return nil, err - } - return vs, nil -} - -func (sna *SnapshotBeta) List(ctx context.Context, namespace string, labels map[string]string) (*v1.VolumeSnapshotList, error) { - return listSnapshots(ctx, sna.dynCli, v1beta1.VolSnapGVR, namespace, labels) -} - -func listSnapshots(ctx context.Context, dynCli dynamic.Interface, snapGVR schema.GroupVersionResource, namespace string, labels map[string]string) (*v1.VolumeSnapshotList, error) { - listOptions := metav1.ListOptions{} - if labels != nil { - labelSelector := metav1.LabelSelector{MatchLabels: blockstorage.SanitizeTags(labels)} - listOptions.LabelSelector = pkglabels.Set(labelSelector.MatchLabels).String() - } - usList, err := dynCli.Resource(snapGVR).Namespace(namespace).List(ctx, listOptions) - if err != nil { - return nil, err - } - vsList := &v1.VolumeSnapshotList{} - for _, us := range usList.Items { - vs := &v1.VolumeSnapshot{} - if err := TransformUnstructured(&us, vs); err != nil { - return nil, err - } - vsList.Items = append(vsList.Items, *vs) - } - return vsList, nil -} - -// Delete will delete the VolumeSnapshot and returns any error as a result. -func (sna *SnapshotBeta) Delete(ctx context.Context, name, namespace string) (*v1.VolumeSnapshot, error) { - return deleteSnapshot(ctx, sna.dynCli, v1beta1.VolSnapGVR, name, namespace) -} - -func deleteSnapshot(ctx context.Context, dynCli dynamic.Interface, snapGVR schema.GroupVersionResource, name, namespace string) (*v1.VolumeSnapshot, error) { - snap, err := getSnapshot(ctx, dynCli, snapGVR, name, namespace) - if apierrors.IsNotFound(err) { - return nil, nil - } - if err != nil { - return nil, errkit.Wrap(err, "Failed to find VolumeSnapshot", "namespace", namespace, "name", name) - } - if err := dynCli.Resource(snapGVR).Namespace(namespace).Delete(ctx, name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) { - return nil, errkit.Wrap(err, "Failed to delete VolumeSnapshot", "namespace", namespace, "name", name) - } - // If the Snapshot does not exist, that's an acceptable error and we ignore it - return snap, nil -} - -// DeleteContent will delete the specified VolumeSnapshotContent -func (sna *SnapshotBeta) DeleteContent(ctx context.Context, name string) error { - if err := sna.dynCli.Resource(v1beta1.VolSnapContentGVR).Delete(ctx, name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) { - return errkit.Wrap(err, "Failed to delete VolumeSnapshotContent", "name", name) - } - // If the Snapshot Content does not exist, that's an acceptable error and we ignore it - return nil -} - -// Clone will clone the VolumeSnapshot to namespace 'snapshotMeta.Namespace'. -// Underlying VolumeSnapshotContent will be cloned with a different name. -func (sna *SnapshotBeta) Clone(ctx context.Context, name, namespace string, waitForReady bool, snapshotMeta, snapshotContentMeta ObjectMeta) error { - _, err := sna.Get(ctx, snapshotMeta.Name, snapshotMeta.Namespace) - if err == nil { - return errkit.New("Target snapshot already exists in target namespace", "volumeSnapshot", snapshotMeta.Name, "namespace", snapshotMeta.Namespace) - } - if !apierrors.IsNotFound(err) { - return errkit.Wrap(err, "Failed to query target Volumesnapshot", "volumeSnapshot", snapshotMeta.Name, "namespace", snapshotMeta.Namespace) - } - - src, err := sna.GetSource(ctx, name, namespace) - if err != nil { - return errkit.New("Failed to get source") - } - return sna.CreateFromSource(ctx, src, waitForReady, snapshotMeta, snapshotContentMeta) -} - -// GetSource will return the CSI source that backs the volume snapshot. -func (sna *SnapshotBeta) GetSource(ctx context.Context, snapshotName, namespace string) (*Source, error) { - return getSnapshotSource(ctx, sna.dynCli, v1beta1.VolSnapGVR, v1beta1.VolSnapContentGVR, snapshotName, namespace) -} - -func getSnapshotSource(ctx context.Context, dynCli dynamic.Interface, snapGVR, snapContentGVR schema.GroupVersionResource, snapshotName, namespace string) (*Source, error) { - snap, err := getSnapshot(ctx, dynCli, snapGVR, snapshotName, namespace) - if err != nil { - return nil, errkit.Wrap(err, "Failed to get snapshot", "volumeSnapshot", snapshotName) - } - if snap.Status.ReadyToUse == nil || !*snap.Status.ReadyToUse { - return nil, errkit.New("Snapshot is not ready", "volumeSnapshot", snapshotName, "namespace", namespace) - } - if snap.Status.BoundVolumeSnapshotContentName == nil { - return nil, errkit.New("Snapshot does not have content", "volumeSnapshot", snapshotName, "namespace", namespace) - } - - cont, err := getSnapshotContent(ctx, dynCli, snapContentGVR, *snap.Status.BoundVolumeSnapshotContentName) - if err != nil { - return nil, errkit.Wrap(err, "Failed to get snapshot content", "volumeSnapshot", snapshotName, "volumeSnapshotContent", *snap.Status.BoundVolumeSnapshotContentName) - } - - src := &Source{ - Handle: *cont.Status.SnapshotHandle, - Driver: cont.Spec.Driver, - RestoreSize: cont.Status.RestoreSize, - VolumeSnapshotClassName: *cont.Spec.VolumeSnapshotClassName, - } - return src, nil -} - -// CreateFromSource will create a 'Volumesnapshot' and 'VolumesnaphotContent' pair for the underlying snapshot source. -func (sna *SnapshotBeta) CreateFromSource(ctx context.Context, source *Source, waitForReady bool, snapshotMeta, snapshotContentMeta ObjectMeta) error { - deletionPolicy, err := getDeletionPolicyFromClass(sna.dynCli, v1beta1.VolSnapClassGVR, source.VolumeSnapshotClassName) - if err != nil { - return errkit.Wrap(err, "Failed to get DeletionPolicy from VolumeSnapshotClass") - } - snapshotContentMeta.Name = snapshotMeta.Name + "-content-" + string(uuid.NewUUID()) - snapshotMeta.Labels = blockstorage.SanitizeTags(snapshotMeta.Labels) - snap := UnstructuredVolumeSnapshot(v1beta1.VolSnapGVR, "", source.VolumeSnapshotClassName, snapshotMeta, snapshotContentMeta) - if err := sna.CreateContentFromSource(ctx, source, snapshotMeta.Name, snapshotMeta.Namespace, deletionPolicy, snapshotContentMeta); err != nil { - return err - } - if _, err := sna.dynCli.Resource(v1beta1.VolSnapGVR).Namespace(snapshotMeta.Namespace).Create(ctx, snap, metav1.CreateOptions{}); err != nil { - return errkit.Wrap(err, "Failed to create content", "volumeSnapshot", snap.GetName()) - } - if !waitForReady { - return nil - } - err = sna.WaitOnReadyToUse(ctx, snapshotMeta.Name, snapshotMeta.Namespace) - return err -} - -// UpdateVolumeSnapshotStatusBeta sets the readyToUse valuse of a VolumeSnapshot. -func (sna *SnapshotBeta) UpdateVolumeSnapshotStatusBeta(ctx context.Context, namespace string, snapshotName string, readyToUse bool) error { - return updateVolumeSnapshotStatus(ctx, sna.dynCli, v1beta1.VolSnapGVR, namespace, snapshotName, readyToUse) -} - -func (sna *SnapshotBeta) GroupVersion(ctx context.Context) schema.GroupVersion { - return schema.GroupVersion{ - Group: v1beta1.GroupName, - Version: v1beta1.Version, - } -} - -func updateVolumeSnapshotStatus(ctx context.Context, dynCli dynamic.Interface, snapGVR schema.GroupVersionResource, namespace string, snapshotName string, readyToUse bool) error { - us, err := dynCli.Resource(snapGVR).Namespace(namespace).Get(ctx, snapshotName, metav1.GetOptions{}) - if err != nil { - return err - } - status, ok := us.Object["status"].(map[string]interface{}) - if !ok { - return errkit.New("Failed to convert status to map", "volumeSnapshot", snapshotName, "status", status) - } - status["readyToUse"] = readyToUse - us.Object["status"] = status - if _, err := dynCli.Resource(snapGVR).Namespace(namespace).UpdateStatus(ctx, us, metav1.UpdateOptions{}); err != nil { - return errkit.Wrap(err, "Failed to update status", "volumeSnapshot", snapshotName) - } - return nil -} - -// CreateContentFromSource will create a 'VolumesnaphotContent' for the underlying snapshot source. -func (sna *SnapshotBeta) CreateContentFromSource(ctx context.Context, source *Source, snapshotName, namespace, deletionPolicy string, snapshotContentMeta ObjectMeta) error { - content := UnstructuredVolumeSnapshotContent(v1beta1.VolSnapContentGVR, snapshotName, namespace, deletionPolicy, source.Driver, source.Handle, source.VolumeSnapshotClassName, snapshotContentMeta) - if _, err := sna.dynCli.Resource(v1beta1.VolSnapContentGVR).Create(ctx, content, metav1.CreateOptions{}); err != nil { - return errkit.Wrap(err, "Failed to create content", "volumeSnapshotContent", content.GetName()) - } - return nil -} - -// WaitOnReadyToUse will block until the Volumesnapshot in 'namespace' with name 'snapshotName' -// has status 'ReadyToUse' or 'ctx.Done()' is signalled. -func (sna *SnapshotBeta) WaitOnReadyToUse(ctx context.Context, snapshotName, namespace string) error { - return waitOnReadyToUse(ctx, sna.dynCli, v1beta1.VolSnapGVR, snapshotName, namespace, isReadyToUseBeta) -} - -func isReadyToUseBeta(us *unstructured.Unstructured) (bool, error) { - vs := v1beta1.VolumeSnapshot{} - if err := TransformUnstructured(us, &vs); err != nil { - return false, err - } - if vs.Status == nil { - return false, nil - } - // Error can be set while waiting for creation - if vs.Status.Error != nil { - return false, errkit.New(*vs.Status.Error.Message) - } - return (vs.Status.ReadyToUse != nil && *vs.Status.ReadyToUse && vs.Status.CreationTime != nil), nil -} - -func getSnapshotContent(ctx context.Context, dynCli dynamic.Interface, snapContentGVR schema.GroupVersionResource, contentName string) (*v1.VolumeSnapshotContent, error) { - us, err := dynCli.Resource(snapContentGVR).Get(ctx, contentName, metav1.GetOptions{}) - if err != nil { - return nil, err - } - vsc := v1.VolumeSnapshotContent{} - if err := TransformUnstructured(us, &vsc); err != nil { - return nil, err - } - return &vsc, nil -} - -func getDeletionPolicyFromClass(dynCli dynamic.Interface, snapClassGVR schema.GroupVersionResource, snapClassName string) (string, error) { - us, err := dynCli.Resource(snapClassGVR).Get(context.TODO(), snapClassName, metav1.GetOptions{}) - if err != nil { - return "", errkit.Wrap(err, "Failed to find VolumeSnapshotClass", "volumeSnapshotClass", snapClassName) - } - vsc := v1beta1.VolumeSnapshotClass{} - if err := TransformUnstructured(us, &vsc); err != nil { - return "", err - } - return vsc.DeletionPolicy, nil -} - -// UnstructuredVolumeSnapshot returns Unstructured object for the VolumeSnapshot resource. -// If snapshotContentMeta has name value set, UnstructuredVolumeSnapshot will return VolumeSnapshot object with VolumeSnapshotContent information. -func UnstructuredVolumeSnapshot(gvr schema.GroupVersionResource, pvcName, snapClassName string, snapshotMeta, snapshotContentMeta ObjectMeta) *unstructured.Unstructured { - snap := &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": fmt.Sprintf("%s/%s", gvr.Group, gvr.Version), - "kind": VolSnapKind, - "metadata": map[string]interface{}{ - "name": snapshotMeta.Name, - "namespace": snapshotMeta.Namespace, - }, - }, - } - if pvcName != "" { - snap.Object["spec"] = map[string]interface{}{ - "source": map[string]interface{}{ - "persistentVolumeClaimName": pvcName, - }, - "volumeSnapshotClassName": snapClassName, - } - } - if snapshotContentMeta.Name != "" { - snap.Object["spec"] = map[string]interface{}{ - "source": map[string]interface{}{ - "volumeSnapshotContentName": snapshotContentMeta.Name, - }, - "volumeSnapshotClassName": snapClassName, - } - } - if snapshotMeta.Labels != nil { - snap.SetLabels(snapshotMeta.Labels) - } - if snapshotMeta.Annotations != nil { - snap.SetAnnotations(snapshotMeta.Annotations) - } - return snap -} - -// UnstructuredVolumeSnapshotContent returns Unstructured object for the VolumeSnapshotContent resource. -func UnstructuredVolumeSnapshotContent(gvr schema.GroupVersionResource, snapshotName, snapshotNS, deletionPolicy, driver, handle, snapClassName string, snapshotContentMeta ObjectMeta) *unstructured.Unstructured { - snapshotContent := unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": fmt.Sprintf("%s/%s", gvr.Group, gvr.Version), - "kind": VolSnapContentKind, - "metadata": map[string]interface{}{ - "name": snapshotContentMeta.Name, - }, - "spec": map[string]interface{}{ - "volumeSnapshotRef": map[string]interface{}{ - "kind": VolSnapKind, - "name": snapshotName, - "namespace": snapshotNS, - }, - "deletionPolicy": deletionPolicy, - "driver": driver, - "source": map[string]interface{}{ - "snapshotHandle": handle, - }, - "volumeSnapshotClassName": snapClassName, - }, - }, - } - if snapshotContentMeta.Labels != nil { - snapshotContent.SetLabels(snapshotContentMeta.Labels) - } - if snapshotContentMeta.Annotations != nil { - snapshotContent.SetAnnotations(snapshotContentMeta.Annotations) - } - return &snapshotContent -} - -func UnstructuredVolumeSnapshotClass(gvr schema.GroupVersionResource, name, driver, deletionPolicy string, params map[string]string) *unstructured.Unstructured { - obj := map[string]interface{}{ - "apiVersion": fmt.Sprintf("%s/%s", gvr.Group, gvr.Version), - "kind": VolSnapClassKind, - "metadata": map[string]interface{}{ - "name": name, - }, - VolSnapClassBetaDriverKey: driver, - "deletionPolicy": deletionPolicy, - } - if params != nil { - obj["parameters"] = Mss2msi(params) - } - - return &unstructured.Unstructured{ - Object: obj, - } -} - -// Mss2msi takes a map of string:string and returns a string:inteface map. -// This is useful since the unstructured type take map[string]interface{} as values. -func Mss2msi(in map[string]string) map[string]interface{} { - if in == nil { - return nil - } - paramsMap := map[string]interface{}{} - for k, v := range in { - paramsMap[k] = v - } - return paramsMap -} diff --git a/pkg/kube/snapshot/snapshot_stable.go b/pkg/kube/snapshot/snapshot_stable.go index e9cda64108..3c0688a9ff 100644 --- a/pkg/kube/snapshot/snapshot_stable.go +++ b/pkg/kube/snapshot/snapshot_stable.go @@ -16,11 +16,15 @@ package snapshot import ( "context" + "encoding/json" + "fmt" "github.com/kanisterio/errkit" v1 "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + pkglabels "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/uuid" "k8s.io/client-go/dynamic" @@ -30,6 +34,10 @@ import ( ) const ( + GroupName = "snapshot.storage.k8s.io" + Version = "v1" + PVCKind = "PersistentVolumeClaim" + // VolumeSnapshotContentResourcePlural is "volumesnapshotcontents" VolumeSnapshotContentResourcePlural = "volumesnapshotcontents" // VolumeSnapshotResourcePlural is "volumesnapshots" @@ -37,8 +45,15 @@ const ( // VolumeSnapshotClassResourcePlural is "volumesnapshotclasses" VolumeSnapshotClassResourcePlural = "volumesnapshotclasses" - GroupName = "snapshot.storage.k8s.io" - Version = "v1" + // Snapshot resource Kinds + VolSnapClassKind = "VolumeSnapshotClass" + VolSnapKind = "VolumeSnapshot" + VolSnapContentKind = "VolumeSnapshotContent" + + VolSnapClassDriverKey = "driver" + DeletionPolicyDelete = "Delete" + DeletionPolicyRetain = "Retain" + CloneVolumeSnapshotClassLabelName = "kanister-cloned-from" ) var ( @@ -50,46 +65,47 @@ var ( VolSnapContentGVR = schema.GroupVersionResource{Group: GroupName, Version: Version, Resource: VolumeSnapshotContentResourcePlural} ) -type SnapshotStable struct { +type Snapshot struct { kubeCli kubernetes.Interface dynCli dynamic.Interface } -func NewSnapshotStable(kubeCli kubernetes.Interface, dynCli dynamic.Interface) Snapshotter { - return &SnapshotStable{kubeCli: kubeCli, dynCli: dynCli} +// NewSnapshotter creates and return new Snapshotter object +func NewSnapshotter(kubeCli kubernetes.Interface, dynCli dynamic.Interface) Snapshotter { + return &Snapshot{kubeCli: kubeCli, dynCli: dynCli} } // CloneVolumeSnapshotClass creates a copy of the source volume snapshot class -func (sna *SnapshotStable) CloneVolumeSnapshotClass(ctx context.Context, sourceClassName, targetClassName, newDeletionPolicy string, excludeAnnotations []string) error { +func (sna *Snapshot) CloneVolumeSnapshotClass(ctx context.Context, sourceClassName, targetClassName, newDeletionPolicy string, excludeAnnotations []string) error { return cloneSnapshotClass(ctx, sna.dynCli, VolSnapClassGVR, sourceClassName, targetClassName, newDeletionPolicy, excludeAnnotations) } // GetVolumeSnapshotClass returns VolumeSnapshotClass name which is annotated with given key. -func (sna *SnapshotStable) GetVolumeSnapshotClass(ctx context.Context, annotationKey, annotationValue, storageClassName string) (string, error) { +func (sna *Snapshot) GetVolumeSnapshotClass(ctx context.Context, annotationKey, annotationValue, storageClassName string) (string, error) { return GetSnapshotClassbyAnnotation(ctx, sna.dynCli, sna.kubeCli, VolSnapClassGVR, annotationKey, annotationValue, storageClassName) } // Create creates a VolumeSnapshot and returns it or any error happened meanwhile. -func (sna *SnapshotStable) Create(ctx context.Context, volumeName string, snapshotClass *string, waitForReady bool, snapshotMeta ObjectMeta) error { +func (sna *Snapshot) Create(ctx context.Context, volumeName string, snapshotClass *string, waitForReady bool, snapshotMeta ObjectMeta) error { return createSnapshot(ctx, sna.dynCli, sna.kubeCli, VolSnapGVR, volumeName, snapshotClass, waitForReady, snapshotMeta) } // Get will return the VolumeSnapshot in the 'namespace' with given 'name'. -func (sna *SnapshotStable) Get(ctx context.Context, name, namespace string) (*v1.VolumeSnapshot, error) { +func (sna *Snapshot) Get(ctx context.Context, name, namespace string) (*v1.VolumeSnapshot, error) { return getSnapshot(ctx, sna.dynCli, VolSnapGVR, name, namespace) } -func (sna *SnapshotStable) List(ctx context.Context, namespace string, labels map[string]string) (*v1.VolumeSnapshotList, error) { +func (sna *Snapshot) List(ctx context.Context, namespace string, labels map[string]string) (*v1.VolumeSnapshotList, error) { return listSnapshots(ctx, sna.dynCli, VolSnapGVR, namespace, labels) } // Delete will delete the VolumeSnapshot and returns any error as a result. -func (sna *SnapshotStable) Delete(ctx context.Context, name, namespace string) (*v1.VolumeSnapshot, error) { +func (sna *Snapshot) Delete(ctx context.Context, name, namespace string) (*v1.VolumeSnapshot, error) { return deleteSnapshot(ctx, sna.dynCli, VolSnapGVR, name, namespace) } // DeleteContent will delete the specified VolumeSnapshotContent -func (sna *SnapshotStable) DeleteContent(ctx context.Context, name string) error { +func (sna *Snapshot) DeleteContent(ctx context.Context, name string) error { if err := sna.dynCli.Resource(VolSnapContentGVR).Delete(ctx, name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) { return errkit.Wrap(err, "Failed to delete", "volumeSnapshotContent", name) } @@ -99,7 +115,7 @@ func (sna *SnapshotStable) DeleteContent(ctx context.Context, name string) error // Clone will clone the VolumeSnapshot to namespace 'cloneNamespace'. // Underlying VolumeSnapshotContent will be cloned with a different name. -func (sna *SnapshotStable) Clone(ctx context.Context, name, namespace string, waitForReady bool, snapshotMeta, snapshotContentMeta ObjectMeta) error { +func (sna *Snapshot) Clone(ctx context.Context, name, namespace string, waitForReady bool, snapshotMeta, snapshotContentMeta ObjectMeta) error { _, err := sna.Get(ctx, snapshotMeta.Name, snapshotMeta.Namespace) if err == nil { return errkit.New("Target snapshot already exists in target namespace", "volumeSnapshot", snapshotMeta.Name, "namespace", snapshotMeta.Namespace) @@ -116,12 +132,12 @@ func (sna *SnapshotStable) Clone(ctx context.Context, name, namespace string, wa } // GetSource will return the CSI source that backs the volume snapshot. -func (sna *SnapshotStable) GetSource(ctx context.Context, snapshotName, namespace string) (*Source, error) { +func (sna *Snapshot) GetSource(ctx context.Context, snapshotName, namespace string) (*Source, error) { return getSnapshotSource(ctx, sna.dynCli, VolSnapGVR, VolSnapContentGVR, snapshotName, namespace) } // CreateFromSource will create a 'Volumesnapshot' and 'VolumesnaphotContent' pair for the underlying snapshot source. -func (sna *SnapshotStable) CreateFromSource(ctx context.Context, source *Source, waitForReady bool, snapshotMeta, snapshotContentMeta ObjectMeta) error { +func (sna *Snapshot) CreateFromSource(ctx context.Context, source *Source, waitForReady bool, snapshotMeta, snapshotContentMeta ObjectMeta) error { deletionPolicy, err := getDeletionPolicyFromClass(sna.dynCli, VolSnapClassGVR, source.VolumeSnapshotClassName) if err != nil { return errkit.Wrap(err, "Failed to get DeletionPolicy from VolumeSnapshotClass") @@ -149,13 +165,13 @@ func (sna *SnapshotStable) CreateFromSource(ctx context.Context, source *Source, return err } -// UpdateVolumeSnapshotStatusStable sets the readyToUse valuse of a VolumeSnapshot. -func (sna *SnapshotStable) UpdateVolumeSnapshotStatusStable(ctx context.Context, namespace string, snapshotName string, readyToUse bool) error { +// UpdateVolumeSnapshotStatus sets the readyToUse valuse of a VolumeSnapshot. +func (sna *Snapshot) UpdateVolumeSnapshotStatus(ctx context.Context, namespace string, snapshotName string, readyToUse bool) error { return updateVolumeSnapshotStatus(ctx, sna.dynCli, VolSnapGVR, namespace, snapshotName, readyToUse) } // CreateContentFromSource will create a 'VolumesnaphotContent' for the underlying snapshot source. -func (sna *SnapshotStable) CreateContentFromSource(ctx context.Context, source *Source, snapshotName, snapshotNS, deletionPolicy string, snapshotContentMeta ObjectMeta) error { +func (sna *Snapshot) CreateContentFromSource(ctx context.Context, source *Source, snapshotName, snapshotNS, deletionPolicy string, snapshotContentMeta ObjectMeta) error { content := UnstructuredVolumeSnapshotContent(VolSnapContentGVR, snapshotName, snapshotNS, deletionPolicy, source.Driver, source.Handle, source.VolumeSnapshotClassName, snapshotContentMeta) if _, err := sna.dynCli.Resource(VolSnapContentGVR).Create(ctx, content, metav1.CreateOptions{}); err != nil { return errkit.Wrap(err, "Failed to create content", "volumeSnapshotContent", content.GetName()) @@ -165,13 +181,374 @@ func (sna *SnapshotStable) CreateContentFromSource(ctx context.Context, source * // WaitOnReadyToUse will block until the Volumesnapshot in 'namespace' with name 'snapshotName' // has status 'ReadyToUse' or 'ctx.Done()' is signalled. -func (sna *SnapshotStable) WaitOnReadyToUse(ctx context.Context, snapshotName, namespace string) error { - return waitOnReadyToUse(ctx, sna.dynCli, VolSnapGVR, snapshotName, namespace, isReadyToUseBeta) +func (sna *Snapshot) WaitOnReadyToUse(ctx context.Context, snapshotName, namespace string) error { + return waitOnReadyToUse(ctx, sna.dynCli, VolSnapGVR, snapshotName, namespace, isReadyToUse) } -func (sna *SnapshotStable) GroupVersion(ctx context.Context) schema.GroupVersion { +func (sna *Snapshot) GroupVersion(ctx context.Context) schema.GroupVersion { return schema.GroupVersion{ Group: GroupName, Version: Version, } } + +func cloneSnapshotClass(ctx context.Context, dynCli dynamic.Interface, snapClassGVR schema.GroupVersionResource, sourceClassName, targetClassName, newDeletionPolicy string, excludeAnnotations []string) error { + usSourceSnapClass, err := dynCli.Resource(snapClassGVR).Get(ctx, sourceClassName, metav1.GetOptions{}) + if err != nil { + return errkit.Wrap(err, "Failed to find source VolumeSnapshotClass", "volumeSnapshotClass", sourceClassName) + } + + sourceSnapClass := v1.VolumeSnapshotClass{} + if err := TransformUnstructured(usSourceSnapClass, &sourceSnapClass); err != nil { + return err + } + existingAnnotations := sourceSnapClass.GetAnnotations() + for _, key := range excludeAnnotations { + delete(existingAnnotations, key) + } + usNew := UnstructuredVolumeSnapshotClass(snapClassGVR, targetClassName, sourceSnapClass.Driver, newDeletionPolicy, sourceSnapClass.Parameters) + // Set Annotations/Labels + usNew.SetAnnotations(existingAnnotations) + usNew.SetLabels(map[string]string{CloneVolumeSnapshotClassLabelName: sourceClassName}) + if _, err = dynCli.Resource(snapClassGVR).Create(ctx, usNew, metav1.CreateOptions{}); err != nil && !apierrors.IsAlreadyExists(err) { + return errkit.Wrap(err, "Failed to create VolumeSnapshotClass", "volumeSnapshotClass", targetClassName) + } + return nil +} + +func createSnapshot( + ctx context.Context, + dynCli dynamic.Interface, + kubeCli kubernetes.Interface, + snapGVR schema.GroupVersionResource, + volumeName string, + snapshotClass *string, + waitForReady bool, + snapshotMeta ObjectMeta, +) error { + if _, err := kubeCli.CoreV1().PersistentVolumeClaims(snapshotMeta.Namespace).Get(ctx, volumeName, metav1.GetOptions{}); err != nil { + if apierrors.IsNotFound(err) { + return errkit.New("Failed to find PVC", "pvc", volumeName, "namespace", snapshotMeta.Namespace) + } + return errkit.Wrap(err, "Failed to query PVC", "pvc", volumeName, "namespace", snapshotMeta.Namespace) + } + snapshotMeta.Labels = blockstorage.SanitizeTags(snapshotMeta.Labels) + snapshotContentMeta := ObjectMeta{} + snap := UnstructuredVolumeSnapshot(snapGVR, volumeName, *snapshotClass, snapshotMeta, snapshotContentMeta) + if _, err := dynCli.Resource(snapGVR).Namespace(snapshotMeta.Namespace).Create(ctx, snap, metav1.CreateOptions{}); err != nil { + return errkit.Wrap(err, "Failed to create snapshot resource", "name", snapshotMeta.Name, "namespace", snapshotMeta.Namespace) + } + + if !waitForReady { + return nil + } + + if err := waitOnReadyToUse(ctx, dynCli, snapGVR, snapshotMeta.Name, snapshotMeta.Namespace, isReadyToUse); err != nil { + return err + } + + _, err := getSnapshot(ctx, dynCli, snapGVR, snapshotMeta.Name, snapshotMeta.Namespace) + return err +} + +func getSnapshot(ctx context.Context, dynCli dynamic.Interface, snapGVR schema.GroupVersionResource, name, namespace string) (*v1.VolumeSnapshot, error) { + us, err := dynCli.Resource(snapGVR).Namespace(namespace).Get(ctx, name, metav1.GetOptions{}) + if err != nil { + return nil, err + } + + vs := &v1.VolumeSnapshot{} + if err := TransformUnstructured(us, vs); err != nil { + return nil, err + } + return vs, nil +} + +func listSnapshots(ctx context.Context, dynCli dynamic.Interface, snapGVR schema.GroupVersionResource, namespace string, labels map[string]string) (*v1.VolumeSnapshotList, error) { + listOptions := metav1.ListOptions{} + if labels != nil { + labelSelector := metav1.LabelSelector{MatchLabels: blockstorage.SanitizeTags(labels)} + listOptions.LabelSelector = pkglabels.Set(labelSelector.MatchLabels).String() + } + usList, err := dynCli.Resource(snapGVR).Namespace(namespace).List(ctx, listOptions) + if err != nil { + return nil, err + } + vsList := &v1.VolumeSnapshotList{} + for _, us := range usList.Items { + vs := &v1.VolumeSnapshot{} + if err := TransformUnstructured(&us, vs); err != nil { + return nil, err + } + vsList.Items = append(vsList.Items, *vs) + } + return vsList, nil +} + +func deleteSnapshot(ctx context.Context, dynCli dynamic.Interface, snapGVR schema.GroupVersionResource, name, namespace string) (*v1.VolumeSnapshot, error) { + snap, err := getSnapshot(ctx, dynCli, snapGVR, name, namespace) + if apierrors.IsNotFound(err) { + return nil, nil + } + if err != nil { + return nil, errkit.Wrap(err, "Failed to find VolumeSnapshot", "namespace", namespace, "name", name) + } + if err := dynCli.Resource(snapGVR).Namespace(namespace).Delete(ctx, name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) { + return nil, errkit.Wrap(err, "Failed to delete VolumeSnapshot", "namespace", namespace, "name", name) + } + // If the Snapshot does not exist, that's an acceptable error and we ignore it + return snap, nil +} + +func getSnapshotSource(ctx context.Context, dynCli dynamic.Interface, snapGVR, snapContentGVR schema.GroupVersionResource, snapshotName, namespace string) (*Source, error) { + snap, err := getSnapshot(ctx, dynCli, snapGVR, snapshotName, namespace) + if err != nil { + return nil, errkit.Wrap(err, "Failed to get snapshot", "volumeSnapshot", snapshotName) + } + if snap.Status.ReadyToUse == nil || !*snap.Status.ReadyToUse { + return nil, errkit.New("Snapshot is not ready", "volumeSnapshot", snapshotName, "namespace", namespace) + } + if snap.Status.BoundVolumeSnapshotContentName == nil { + return nil, errkit.New("Snapshot does not have content", "volumeSnapshot", snapshotName, "namespace", namespace) + } + + cont, err := getSnapshotContent(ctx, dynCli, snapContentGVR, *snap.Status.BoundVolumeSnapshotContentName) + if err != nil { + return nil, errkit.Wrap(err, "Failed to get snapshot content", "volumeSnapshot", snapshotName, "volumeSnapshotContent", *snap.Status.BoundVolumeSnapshotContentName) + } + + src := &Source{ + Handle: *cont.Status.SnapshotHandle, + Driver: cont.Spec.Driver, + RestoreSize: cont.Status.RestoreSize, + VolumeSnapshotClassName: *cont.Spec.VolumeSnapshotClassName, + } + return src, nil +} + +func updateVolumeSnapshotStatus(ctx context.Context, dynCli dynamic.Interface, snapGVR schema.GroupVersionResource, namespace string, snapshotName string, readyToUse bool) error { + us, err := dynCli.Resource(snapGVR).Namespace(namespace).Get(ctx, snapshotName, metav1.GetOptions{}) + if err != nil { + return err + } + status, ok := us.Object["status"].(map[string]interface{}) + if !ok { + return errkit.New("Failed to convert status to map", "volumeSnapshot", snapshotName, "status", status) + } + status["readyToUse"] = readyToUse + us.Object["status"] = status + if _, err := dynCli.Resource(snapGVR).Namespace(namespace).UpdateStatus(ctx, us, metav1.UpdateOptions{}); err != nil { + return errkit.Wrap(err, "Failed to update status", "volumeSnapshot", snapshotName) + } + return nil +} + +func isReadyToUse(us *unstructured.Unstructured) (bool, error) { + vs := v1.VolumeSnapshot{} + if err := TransformUnstructured(us, &vs); err != nil { + return false, err + } + if vs.Status == nil { + return false, nil + } + // Error can be set while waiting for creation + if vs.Status.Error != nil { + return false, errkit.New(*vs.Status.Error.Message) + } + return (vs.Status.ReadyToUse != nil && *vs.Status.ReadyToUse && vs.Status.CreationTime != nil), nil +} + +func getSnapshotContent(ctx context.Context, dynCli dynamic.Interface, snapContentGVR schema.GroupVersionResource, contentName string) (*v1.VolumeSnapshotContent, error) { + us, err := dynCli.Resource(snapContentGVR).Get(ctx, contentName, metav1.GetOptions{}) + if err != nil { + return nil, err + } + vsc := v1.VolumeSnapshotContent{} + if err := TransformUnstructured(us, &vsc); err != nil { + return nil, err + } + return &vsc, nil +} + +func getDeletionPolicyFromClass(dynCli dynamic.Interface, snapClassGVR schema.GroupVersionResource, snapClassName string) (string, error) { + us, err := dynCli.Resource(snapClassGVR).Get(context.TODO(), snapClassName, metav1.GetOptions{}) + if err != nil { + return "", errkit.Wrap(err, "Failed to find VolumeSnapshotClass", "volumeSnapshotClass", snapClassName) + } + vsc := v1.VolumeSnapshotClass{} + if err := TransformUnstructured(us, &vsc); err != nil { + return "", err + } + return string(vsc.DeletionPolicy), nil +} + +// UnstructuredVolumeSnapshot returns Unstructured object for the VolumeSnapshot resource. +// If snapshotContentMeta has name value set, UnstructuredVolumeSnapshot will return VolumeSnapshot object with VolumeSnapshotContent information. +func UnstructuredVolumeSnapshot(gvr schema.GroupVersionResource, pvcName, snapClassName string, snapshotMeta, snapshotContentMeta ObjectMeta) *unstructured.Unstructured { + snap := &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": fmt.Sprintf("%s/%s", gvr.Group, gvr.Version), + "kind": VolSnapKind, + "metadata": map[string]interface{}{ + "name": snapshotMeta.Name, + "namespace": snapshotMeta.Namespace, + }, + }, + } + if pvcName != "" { + snap.Object["spec"] = map[string]interface{}{ + "source": map[string]interface{}{ + "persistentVolumeClaimName": pvcName, + }, + "volumeSnapshotClassName": snapClassName, + } + } + if snapshotContentMeta.Name != "" { + snap.Object["spec"] = map[string]interface{}{ + "source": map[string]interface{}{ + "volumeSnapshotContentName": snapshotContentMeta.Name, + }, + "volumeSnapshotClassName": snapClassName, + } + } + if snapshotMeta.Labels != nil { + snap.SetLabels(snapshotMeta.Labels) + } + if snapshotMeta.Annotations != nil { + snap.SetAnnotations(snapshotMeta.Annotations) + } + return snap +} + +// UnstructuredVolumeSnapshotContent returns Unstructured object for the VolumeSnapshotContent resource. +func UnstructuredVolumeSnapshotContent(gvr schema.GroupVersionResource, snapshotName, snapshotNS, deletionPolicy, driver, handle, snapClassName string, snapshotContentMeta ObjectMeta) *unstructured.Unstructured { + snapshotContent := unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": fmt.Sprintf("%s/%s", gvr.Group, gvr.Version), + "kind": VolSnapContentKind, + "metadata": map[string]interface{}{ + "name": snapshotContentMeta.Name, + }, + "spec": map[string]interface{}{ + "volumeSnapshotRef": map[string]interface{}{ + "kind": VolSnapKind, + "name": snapshotName, + "namespace": snapshotNS, + }, + "deletionPolicy": deletionPolicy, + "driver": driver, + "source": map[string]interface{}{ + "snapshotHandle": handle, + }, + "volumeSnapshotClassName": snapClassName, + }, + }, + } + if snapshotContentMeta.Labels != nil { + snapshotContent.SetLabels(snapshotContentMeta.Labels) + } + if snapshotContentMeta.Annotations != nil { + snapshotContent.SetAnnotations(snapshotContentMeta.Annotations) + } + return &snapshotContent +} + +func UnstructuredVolumeSnapshotClass(gvr schema.GroupVersionResource, name, driver, deletionPolicy string, params map[string]string) *unstructured.Unstructured { + obj := map[string]interface{}{ + "apiVersion": fmt.Sprintf("%s/%s", gvr.Group, gvr.Version), + "kind": VolSnapClassKind, + "metadata": map[string]interface{}{ + "name": name, + }, + VolSnapClassDriverKey: driver, + "deletionPolicy": deletionPolicy, + } + if params != nil { + obj["parameters"] = Mss2msi(params) + } + + return &unstructured.Unstructured{ + Object: obj, + } +} + +// Mss2msi takes a map of string:string and returns a string:inteface map. +// This is useful since the unstructured type take map[string]interface{} as values. +func Mss2msi(in map[string]string) map[string]interface{} { + if in == nil { + return nil + } + paramsMap := map[string]interface{}{} + for k, v := range in { + paramsMap[k] = v + } + return paramsMap +} + +// TransformUnstructured maps Unstructured object to object pointed by obj +func TransformUnstructured(u *unstructured.Unstructured, obj metav1.Object) error { + if u == nil { + return errkit.New("Cannot deserialize nil unstructured") + } + b, err := json.Marshal(u.Object) + if err != nil { + gvk := u.GetObjectKind().GroupVersionKind() + return errkit.Wrap(err, "Failed to Marshal unstructured object GroupVersionKind", "unstructured", gvk) + } + err = json.Unmarshal(b, obj) + if err != nil { + return errkit.Wrap(err, "Failed to Unmarshal unstructured object") + } + + return nil +} + +// GetSnapshotClassbyAnnotation checks if the provided annotation is present in either the storageclass +// or volumesnapshotclass and returns the volumesnapshotclass. +func GetSnapshotClassbyAnnotation(ctx context.Context, dynCli dynamic.Interface, kubeCli kubernetes.Interface, gvr schema.GroupVersionResource, annotationKey, annotationValue, storageClass string) (string, error) { + // fetch storageClass + sc, err := kubeCli.StorageV1().StorageClasses().Get(ctx, storageClass, metav1.GetOptions{}) + if err != nil { + return "", errkit.Wrap(err, "Failed to find StorageClass in the cluster", "class", storageClass) + } + // Check if storageclass annotation override is present. + if val, ok := sc.Annotations[annotationKey]; ok { + vsc, err := dynCli.Resource(gvr).Get(ctx, val, metav1.GetOptions{}) + if err != nil { + return "", errkit.Wrap(err, "Failed to get VolumeSnapshotClass specified in Storageclass annotations", "snapshotClass", val, "storageClass", sc.Name) + } + return vsc.GetName(), nil + } + us, err := dynCli.Resource(gvr).List(ctx, metav1.ListOptions{}) + if err != nil { + return "", errkit.Wrap(err, "Failed to get VolumeSnapshotClasses in the cluster") + } + if us == nil || len(us.Items) == 0 { + return "", errkit.New("Failed to find any VolumeSnapshotClass in the cluster") + } + for _, vsc := range us.Items { + ans := vsc.GetAnnotations() + driver, err := getDriverFromUnstruturedVSC(vsc) + if err != nil { + return "", errkit.Wrap(err, "Failed to get driver for VolumeSnapshotClass", "className", vsc.GetName()) + } + if val, ok := ans[annotationKey]; ok && val == annotationValue && driver == sc.Provisioner { + return vsc.GetName(), nil + } + } + return "", errkit.New("Failed to find VolumeSnapshotClass with annotation in the cluster", "annotationKey", annotationKey, "annotationValue", annotationValue) +} + +func getDriverFromUnstruturedVSC(uVSC unstructured.Unstructured) (string, error) { + if uVSC.GetKind() != VolSnapClassKind { + return "", errkit.New("Cannot get diver for kind", "kind", uVSC.GetKind()) + } + driver, ok := uVSC.Object[VolSnapClassDriverKey] + if !ok { + return "", errkit.New("VolumeSnapshotClass missing driver/snapshotter field", "volumeSnapshotClass", uVSC.GetName()) + } + if driverString, ok := driver.(string); ok { + return driverString, nil + } + return "", errkit.New("Failed to convert driver to string") +} diff --git a/pkg/kube/snapshot/snapshot_test.go b/pkg/kube/snapshot/snapshot_test.go index 76130c83f0..cd09ea1f95 100644 --- a/pkg/kube/snapshot/snapshot_test.go +++ b/pkg/kube/snapshot/snapshot_test.go @@ -17,9 +17,7 @@ package snapshot_test import ( "context" "fmt" - "reflect" "strconv" - "strings" "testing" "time" @@ -40,8 +38,6 @@ import ( "github.com/kanisterio/kanister/pkg/kube" "github.com/kanisterio/kanister/pkg/kube/snapshot" - "github.com/kanisterio/kanister/pkg/kube/snapshot/apis/v1alpha1" - "github.com/kanisterio/kanister/pkg/kube/snapshot/apis/v1beta1" "github.com/kanisterio/kanister/pkg/kube/volume" "github.com/kanisterio/kanister/pkg/poll" ) @@ -49,19 +45,13 @@ import ( func Test(t *testing.T) { check.TestingT(t) } type SnapshotTestSuite struct { - sourceNamespace string - targetNamespace string - snapshotterAlpha snapshot.Snapshotter - snapshotterBeta snapshot.Snapshotter - snapshotterStable snapshot.Snapshotter - cli kubernetes.Interface - dynCli dynamic.Interface - snapshotClassAlpha *string - snapshotClassBeta *string - snapshotClassStable *string - storageClassCSIAlpha string - storageClassCSIBeta string - storageClassCSIStable string + sourceNamespace string + targetNamespace string + snapshotter snapshot.Snapshotter + cli kubernetes.Interface + dynCli dynamic.Interface + snapshotClass *string + storageClassCSI string } var _ = check.Suite(&SnapshotTestSuite{}) @@ -92,45 +82,18 @@ func (s *SnapshotTestSuite) SetUpSuite(c *check.C) { c.Assert(err, check.IsNil) s.dynCli = dynCli - s.snapshotterAlpha = snapshot.NewSnapshotAlpha(cli, dynCli) - s.snapshotterBeta = snapshot.NewSnapshotBeta(cli, dynCli) - s.snapshotterStable = snapshot.NewSnapshotStable(cli, dynCli) + s.snapshotter = snapshot.NewSnapshotter(cli, dynCli) - // Pick latest version available as in snapshot.NewSnapshotter - var ( - snapClassStable, driverStable string - snapClassBeta, driverBeta string - snapClassAlpha, driverAlpha string - ) - for { - snapClassStable, driverStable = findSnapshotClassName(c, ctx, s.dynCli, snapshot.VolSnapClassGVR, snapv1.VolumeSnapshotClass{}) - if snapClassStable != "" { - s.snapshotClassStable = &snapClassStable - break - } - snapClassBeta, driverBeta = findSnapshotClassName(c, ctx, s.dynCli, v1beta1.VolSnapClassGVR, v1beta1.VolumeSnapshotClass{}) - if snapClassBeta != "" { - s.snapshotClassBeta = &snapClassBeta - break - } - snapClassAlpha, driverAlpha = findSnapshotClassName(c, ctx, s.dynCli, v1alpha1.VolSnapClassGVR, v1alpha1.VolumeSnapshotClass{}) - if snapClassAlpha != "" { - s.snapshotClassAlpha = &snapClassAlpha - break - } + snapClass, driver := findSnapshotClassName(c, ctx, s.dynCli, snapshot.VolSnapClassGVR, snapv1.VolumeSnapshotClass{}) + if snapClass != "" { + s.snapshotClass = &snapClass } storageClasses, err := cli.StorageV1().StorageClasses().List(ctx, metav1.ListOptions{}) c.Assert(err, check.IsNil) for _, class := range storageClasses.Items { - if class.Provisioner == driverAlpha && *class.VolumeBindingMode == scv1.VolumeBindingImmediate { - s.storageClassCSIAlpha = class.Name - } - if class.Provisioner == driverBeta && *class.VolumeBindingMode == scv1.VolumeBindingImmediate { - s.storageClassCSIBeta = class.Name - } - if class.Provisioner == driverStable && *class.VolumeBindingMode == scv1.VolumeBindingImmediate { - s.storageClassCSIStable = class.Name + if class.Provisioner == driver && *class.VolumeBindingMode == scv1.VolumeBindingImmediate { + s.storageClassCSI = class.Name } } @@ -150,8 +113,6 @@ func (s *SnapshotTestSuite) TestVolumeSnapshotFake(c *check.C) { snapshotName := "snap-1-fake" volName := "pvc-1-fake" scheme := runtime.NewScheme() - scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "snapshot.storage.k8s.io", Version: "v1alpha1", Kind: "VolumeSnapshotClassList"}, &unstructured.UnstructuredList{}) - scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "snapshot.storage.k8s.io", Version: "v1beta1", Kind: "VolumeSnapshotClassList"}, &unstructured.UnstructuredList{}) scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "snapshot.storage.k8s.io", Version: "v1", Kind: "VolumeSnapshotClassList"}, &unstructured.UnstructuredList{}) fakeCli := fake.NewSimpleClientset() @@ -173,36 +134,29 @@ func (s *SnapshotTestSuite) TestVolumeSnapshotFake(c *check.C) { _, err = fakeCli.CoreV1().PersistentVolumeClaims(defaultNamespace).Create(context.TODO(), pvc, metav1.CreateOptions{}) c.Assert(err, check.IsNil) - for _, fakeSs := range []snapshot.Snapshotter{ - snapshot.NewSnapshotAlpha(fakeCli, dynfake.NewSimpleDynamicClient(scheme)), - snapshot.NewSnapshotBeta(fakeCli, dynfake.NewSimpleDynamicClient(scheme)), - snapshot.NewSnapshotStable(fakeCli, dynfake.NewSimpleDynamicClient(scheme)), - } { - snapshotMeta := snapshot.ObjectMeta{ - Name: snapshotName, - Namespace: defaultNamespace, - } - err = fakeSs.Create(context.Background(), volName, &fakeClass, false, snapshotMeta) - c.Assert(err, check.IsNil) - snap, err := fakeSs.Get(context.Background(), snapshotName, defaultNamespace) - c.Assert(err, check.IsNil) - c.Assert(snap.Name, check.Equals, snapshotName) - - err = fakeSs.Create(context.Background(), volName, &fakeClass, false, snapshotMeta) - c.Assert(err, check.NotNil) - deletedSnap, err := fakeSs.Delete(context.Background(), snap.Name, snap.Namespace) - c.Assert(err, check.IsNil) - c.Assert(deletedSnap.Name, check.Equals, snap.Name) - _, err = fakeSs.Delete(context.Background(), snap.Name, snap.Namespace) - c.Assert(err, check.IsNil) + fakeSs := snapshot.NewSnapshotter(fakeCli, dynfake.NewSimpleDynamicClient(scheme)) + snapshotMeta := snapshot.ObjectMeta{ + Name: snapshotName, + Namespace: defaultNamespace, } + err = fakeSs.Create(context.Background(), volName, &fakeClass, false, snapshotMeta) + c.Assert(err, check.IsNil) + snap, err := fakeSs.Get(context.Background(), snapshotName, defaultNamespace) + c.Assert(err, check.IsNil) + c.Assert(snap.Name, check.Equals, snapshotName) + + err = fakeSs.Create(context.Background(), volName, &fakeClass, false, snapshotMeta) + c.Assert(err, check.NotNil) + deletedSnap, err := fakeSs.Delete(context.Background(), snap.Name, snap.Namespace) + c.Assert(err, check.IsNil) + c.Assert(deletedSnap.Name, check.Equals, snap.Name) + _, err = fakeSs.Delete(context.Background(), snap.Name, snap.Namespace) + c.Assert(err, check.IsNil) } func (s *SnapshotTestSuite) TestVolumeSnapshotClassCloneFake(c *check.C) { ctx := context.Background() scheme := runtime.NewScheme() - scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "snapshot.storage.k8s.io", Version: "v1alpha1", Kind: "VolumeSnapshotClassList"}, &unstructured.UnstructuredList{}) - scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "snapshot.storage.k8s.io", Version: "v1beta1", Kind: "VolumeSnapshotClassList"}, &unstructured.UnstructuredList{}) scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "snapshot.storage.k8s.io", Version: "v1", Kind: "VolumeSnapshotClassList"}, &unstructured.UnstructuredList{}) fakeCli := fake.NewSimpleClientset( @@ -225,20 +179,10 @@ func (s *SnapshotTestSuite) TestVolumeSnapshotClassCloneFake(c *check.C) { snapClassGVR schema.GroupVersionResource snapshotter snapshot.Snapshotter }{ - { - sourceSnapClassSpec: snapshot.UnstructuredVolumeSnapshotClassAlpha(fakeClass, fakeDriver, snapshot.DeletionPolicyDelete, fakeParams), - snapClassGVR: v1alpha1.VolSnapClassGVR, - snapshotter: snapshot.NewSnapshotAlpha(fakeCli, dynCli), - }, - { - sourceSnapClassSpec: snapshot.UnstructuredVolumeSnapshotClass(v1beta1.VolSnapClassGVR, fakeClass, fakeDriver, snapshot.DeletionPolicyDelete, fakeParams), - snapClassGVR: v1beta1.VolSnapClassGVR, - snapshotter: snapshot.NewSnapshotBeta(fakeCli, dynCli), - }, { sourceSnapClassSpec: snapshot.UnstructuredVolumeSnapshotClass(snapshot.VolSnapClassGVR, fakeClass, fakeDriver, snapshot.DeletionPolicyDelete, fakeParams), snapClassGVR: snapshot.VolSnapClassGVR, - snapshotter: snapshot.NewSnapshotStable(fakeCli, dynCli), + snapshotter: snapshot.NewSnapshotter(fakeCli, dynCli), }, } { annotationKeyToKeep := "keepme" @@ -302,45 +246,6 @@ func (s *SnapshotTestSuite) TestVolumeSnapshotCloneFake(c *check.C) { snapContentObject metav1.Object fakeSs snapshot.Snapshotter }{ - { - snapClassSpec: snapshot.UnstructuredVolumeSnapshotClassAlpha(fakeClass, fakeDriver, deletionPolicy, nil), - snapClassGVR: v1alpha1.VolSnapClassGVR, - contentSpec: snapshot.UnstructuredVolumeSnapshotContentAlpha(fakeSnapshotName, defaultNamespace, deletionPolicy, fakeDriver, fakeSnapshotHandle, fakeClass, snapshot.ObjectMeta{ - Name: fakeContentName, - Annotations: fakeContentAnnotation, - }), - contentGVR: v1alpha1.VolSnapContentGVR, - snapSpec: snapshot.UnstructuredVolumeSnapshotAlpha("", fakeClass, snapshot.ObjectMeta{ - Name: fakeSnapshotName, - Namespace: defaultNamespace, - Annotations: fakeSnapshotAnnotation, - }, snapshot.ObjectMeta{ - Name: fakeContentName, - }), - snapGVR: v1alpha1.VolSnapGVR, - snapContentObject: &v1alpha1.VolumeSnapshotContent{}, - fakeSs: snapshot.NewSnapshotAlpha(nil, dynCli), - }, - { - snapClassSpec: snapshot.UnstructuredVolumeSnapshotClass(v1beta1.VolSnapClassGVR, fakeClass, fakeDriver, deletionPolicy, nil), - snapClassGVR: v1beta1.VolSnapClassGVR, - contentSpec: snapshot.UnstructuredVolumeSnapshotContent(v1beta1.VolSnapContentGVR, fakeSnapshotName, defaultNamespace, deletionPolicy, fakeDriver, fakeSnapshotHandle, fakeClass, snapshot.ObjectMeta{ - Name: fakeContentName, - Annotations: fakeContentAnnotation, - }), - contentGVR: v1beta1.VolSnapContentGVR, - - snapSpec: snapshot.UnstructuredVolumeSnapshot(v1beta1.VolSnapGVR, "", fakeClass, snapshot.ObjectMeta{ - Name: fakeSnapshotName, - Namespace: defaultNamespace, - Annotations: fakeSnapshotAnnotation, - }, snapshot.ObjectMeta{ - Name: fakeContentName, - }), - snapGVR: v1beta1.VolSnapGVR, - snapContentObject: &v1beta1.VolumeSnapshotContent{}, - fakeSs: snapshot.NewSnapshotBeta(nil, dynCli), - }, { snapClassSpec: snapshot.UnstructuredVolumeSnapshotClass(snapshot.VolSnapClassGVR, fakeClass, fakeDriver, deletionPolicy, nil), snapClassGVR: snapshot.VolSnapClassGVR, @@ -359,7 +264,7 @@ func (s *SnapshotTestSuite) TestVolumeSnapshotCloneFake(c *check.C) { }), snapGVR: snapshot.VolSnapGVR, snapContentObject: &snapv1.VolumeSnapshotContent{}, - fakeSs: snapshot.NewSnapshotStable(nil, dynCli), + fakeSs: snapshot.NewSnapshotter(nil, dynCli), }, } { tc.contentSpec.Object["status"] = map[string]interface{}{ @@ -398,14 +303,6 @@ func (s *SnapshotTestSuite) TestVolumeSnapshotCloneFake(c *check.C) { c.Assert(err, check.IsNil) err = snapshot.TransformUnstructured(us, tc.snapContentObject) c.Assert(err, check.IsNil) - if cloneContent, ok := tc.snapContentObject.(*v1alpha1.VolumeSnapshotContent); ok { - c.Assert(strings.HasPrefix(cloneContent.Name, fakeClone), check.Equals, true) - c.Assert(cloneContent.Spec.DeletionPolicy, check.Equals, tc.snapClassSpec.Object["deletionPolicy"]) - } - if cloneContent, ok := tc.snapContentObject.(*v1beta1.VolumeSnapshotContent); ok { - c.Assert(strings.HasPrefix(cloneContent.Name, fakeClone), check.Equals, true) - c.Assert(cloneContent.Spec.DeletionPolicy, check.Equals, tc.snapClassSpec.Object["deletionPolicy"]) - } } } @@ -413,8 +310,6 @@ func (s *SnapshotTestSuite) TestWaitOnReadyToUse(c *check.C) { snapshotNameBase := "snap-1-fake" volName := "pvc-1-fake" scheme := runtime.NewScheme() - scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "snapshot.storage.k8s.io", Version: "v1alpha1", Kind: "VolumeSnapshotClassList"}, &unstructured.UnstructuredList{}) - scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "snapshot.storage.k8s.io", Version: "v1beta1", Kind: "VolumeSnapshotClassList"}, &unstructured.UnstructuredList{}) scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "snapshot.storage.k8s.io", Version: "v1", Kind: "VolumeSnapshotClassList"}, &unstructured.UnstructuredList{}) fakeCli := fake.NewSimpleClientset() @@ -439,78 +334,64 @@ func (s *SnapshotTestSuite) TestWaitOnReadyToUse(c *check.C) { dynCli := dynfake.NewSimpleDynamicClient(scheme) - for _, fakeSs := range []snapshot.Snapshotter{ - snapshot.NewSnapshotAlpha(fakeCli, dynCli), - snapshot.NewSnapshotBeta(fakeCli, dynCli), - snapshot.NewSnapshotStable(fakeCli, dynCli), - } { - ctx := context.Background() - - var volumeSnapshotGVR schema.GroupVersionResource - var snapshotName string - switch fakeSs.(type) { - case *snapshot.SnapshotAlpha: - volumeSnapshotGVR = v1alpha1.VolSnapGVR - snapshotName = snapshotNameBase + "-alpha" - case *snapshot.SnapshotBeta: - volumeSnapshotGVR = v1beta1.VolSnapGVR - snapshotName = snapshotNameBase + "-beta" - case *snapshot.SnapshotStable: - volumeSnapshotGVR = snapshot.VolSnapGVR - snapshotName = snapshotNameBase + "-stable" - } - snapshotMeta := snapshot.ObjectMeta{ - Name: snapshotName, - Namespace: defaultNamespace, - } - err = fakeSs.Create(ctx, volName, &fakeClass, false, snapshotMeta) + fakeSs := snapshot.NewSnapshotter(fakeCli, dynCli) + ctx := context.Background() + + var volumeSnapshotGVR schema.GroupVersionResource + var snapshotName string + volumeSnapshotGVR = snapshot.VolSnapGVR + snapshotName = snapshotNameBase + "-snap" + snapshotMeta := snapshot.ObjectMeta{ + Name: snapshotName, + Namespace: defaultNamespace, + } + err = fakeSs.Create(ctx, volName, &fakeClass, false, snapshotMeta) + c.Assert(err, check.IsNil) + + // This function should timeout + timeout := 500 * time.Millisecond + bgTimeout := 5 * time.Second + // We don't have readyToUse and no error, waiting indefinitely + err = waitOnReadyToUseWithTimeout(ctx, fakeSs, snapshotName, timeout) + c.Assert(err, check.NotNil) + c.Assert(err.Error(), check.Matches, ".*context deadline exceeded*") + + reply := waitOnReadyToUseInBackground(ctx, fakeSs, snapshotName, bgTimeout) + setReadyStatus(c, dynCli, volumeSnapshotGVR, snapshotName, defaultNamespace) + select { + case err = <-reply: c.Assert(err, check.IsNil) + case <-time.After(2 * time.Second): + c.Error("timeout waiting on ready to use") + } - // This function should timeout - timeout := 500 * time.Millisecond - bgTimeout := 5 * time.Second - // We don't have readyToUse and no error, waiting indefinitely - err = waitOnReadyToUseWithTimeout(ctx, fakeSs, snapshotName, timeout) - c.Assert(err, check.NotNil) - c.Assert(err.Error(), check.Matches, ".*context deadline exceeded*") - - reply := waitOnReadyToUseInBackground(ctx, fakeSs, snapshotName, bgTimeout) - setReadyStatus(c, dynCli, volumeSnapshotGVR, snapshotName, defaultNamespace) - select { - case err = <-reply: - c.Assert(err, check.IsNil) - case <-time.After(2 * time.Second): - c.Error("timeout waiting on ready to use") - } + setVolumeSnapshotStatus(c, dynCli, volumeSnapshotGVR, snapshotName, defaultNamespace, nil) - setVolumeSnapshotStatus(c, dynCli, volumeSnapshotGVR, snapshotName, defaultNamespace, nil) - - // Set non-transient error - message := "some error" - setErrorStatus(c, dynCli, volumeSnapshotGVR, snapshotName, defaultNamespace, message) - - // If there is non-transient error, exit right away - err = waitOnReadyToUseWithTimeout(ctx, fakeSs, snapshotName, timeout) - c.Assert(err, check.NotNil) - c.Assert(err.Error(), check.Matches, ".*some error.*") - - // Set transient error - message = "the object has been modified; please apply your changes to the latest version and try again" - setErrorStatus(c, dynCli, volumeSnapshotGVR, snapshotName, defaultNamespace, message) - - // If there is a transient error, wait with exp backoff which is long - err = waitOnReadyToUseWithTimeout(ctx, fakeSs, snapshotName, timeout) - c.Assert(err, check.NotNil) - c.Assert(err.Error(), check.Matches, ".*context deadline exceeded*") - - reply = waitOnReadyToUseInBackground(ctx, fakeSs, snapshotName, bgTimeout) - setReadyStatus(c, dynCli, volumeSnapshotGVR, snapshotName, defaultNamespace) - select { - case err = <-reply: - c.Assert(err, check.IsNil) - case <-time.After(2 * time.Second): - c.Error("timeout waiting on ready to use") - } + // Set non-transient error + message := "some error" + setErrorStatus(c, dynCli, volumeSnapshotGVR, snapshotName, defaultNamespace, message) + + // If there is non-transient error, exit right away + err = waitOnReadyToUseWithTimeout(ctx, fakeSs, snapshotName, timeout) + c.Assert(err, check.NotNil) + c.Assert(err.Error(), check.Matches, ".*some error.*") + + // Set transient error + message = "the object has been modified; please apply your changes to the latest version and try again" + setErrorStatus(c, dynCli, volumeSnapshotGVR, snapshotName, defaultNamespace, message) + + // If there is a transient error, wait with exp backoff which is long + err = waitOnReadyToUseWithTimeout(ctx, fakeSs, snapshotName, timeout) + c.Assert(err, check.NotNil) + c.Assert(err.Error(), check.Matches, ".*context deadline exceeded*") + + reply = waitOnReadyToUseInBackground(ctx, fakeSs, snapshotName, bgTimeout) + setReadyStatus(c, dynCli, volumeSnapshotGVR, snapshotName, defaultNamespace) + select { + case err = <-reply: + c.Assert(err, check.IsNil) + case <-time.After(2 * time.Second): + c.Error("timeout waiting on ready to use") } } @@ -594,40 +475,16 @@ func setVolumeSnapshotStatus( // ---------------------------------------------------------------------------- -func (s *SnapshotTestSuite) TestVolumeSnapshotAlpha(c *check.C) { - if s.snapshotClassAlpha == nil { - c.Skip("No v1alpha1 Volumesnapshotclass in the cluster") - } - if s.storageClassCSIAlpha == "" { - c.Skip("No Storageclass with CSI provisioner, install CSI and create a storageclass for it") - } - c.Logf("snapshotclass: %s, storageclass %s", *s.snapshotClassAlpha, s.storageClassCSIAlpha) - c.Logf("VolumeSnapshot test - source namespace: %s - target namespace: %s", s.sourceNamespace, s.targetNamespace) - s.testVolumeSnapshot(c, s.snapshotterAlpha, s.storageClassCSIAlpha, s.snapshotClassAlpha) -} - -func (s *SnapshotTestSuite) TestVolumeSnapshotBeta(c *check.C) { - if s.snapshotClassBeta == nil { - c.Skip("No v1beta1 Volumesnapshotclass in the cluster") - } - if s.storageClassCSIBeta == "" { - c.Skip("No Storageclass with CSI provisioner, install CSI and create a storageclass for it") - } - c.Logf("snapshotclass: %s, storageclass %s", *s.snapshotClassBeta, s.storageClassCSIBeta) - c.Logf("VolumeSnapshot test - source namespace: %s - target namespace: %s", s.sourceNamespace, s.targetNamespace) - s.testVolumeSnapshot(c, s.snapshotterBeta, s.storageClassCSIBeta, s.snapshotClassBeta) -} - -func (s *SnapshotTestSuite) TestVolumeSnapshotStable(c *check.C) { - if s.snapshotClassStable == nil { +func (s *SnapshotTestSuite) TestVolumeSnapshot(c *check.C) { + if s.snapshotClass == nil { c.Skip("No v1 Volumesnapshotclass in the cluster") } - if s.storageClassCSIStable == "" { + if s.storageClassCSI == "" { c.Skip("No Storageclass with CSI provisioner, install CSI and create a storageclass for it") } - c.Logf("snapshotclass: %s, storageclass %s", *s.snapshotClassStable, s.storageClassCSIStable) + c.Logf("snapshotclass: %s, storageclass %s", *s.snapshotClass, s.storageClassCSI) c.Logf("VolumeSnapshot test - source namespace: %s - target namespace: %s", s.sourceNamespace, s.targetNamespace) - s.testVolumeSnapshot(c, s.snapshotterStable, s.storageClassCSIStable, s.snapshotClassStable) + s.testVolumeSnapshot(c, s.snapshotter, s.storageClassCSI, s.snapshotClass) } func (s *SnapshotTestSuite) testVolumeSnapshot(c *check.C, snapshotter snapshot.Snapshotter, storageClass string, snapshotClass *string) { @@ -794,12 +651,12 @@ func (s *SnapshotTestSuite) cleanupNamespace(c *check.C, ns string) { } } - vss, errb := s.dynCli.Resource(v1alpha1.VolSnapGVR).Namespace(ns).List(ctx, metav1.ListOptions{}) + vss, errb := s.dynCli.Resource(snapshot.VolSnapGVR).Namespace(ns).List(ctx, metav1.ListOptions{}) if errb != nil { c.Logf("Failed to list snapshots, Namespace: %s, Error: %v", ns, errb) } else { for _, vs := range vss.Items { - if err := s.dynCli.Resource(v1alpha1.VolSnapGVR).Namespace(ns).Delete(context.TODO(), vs.GetName(), metav1.DeleteOptions{}); err != nil { + if err := s.dynCli.Resource(snapshot.VolSnapGVR).Namespace(ns).Delete(context.TODO(), vs.GetName(), metav1.DeleteOptions{}); err != nil { errb = err c.Logf("Failed to delete snapshot, Volumesnapshot: %s, Namespace %s, Error: %v", vs.GetName(), vs.GetNamespace(), err) } @@ -816,65 +673,12 @@ func (s *SnapshotTestSuite) cleanupNamespace(c *check.C, ns string) { } } -func (s *SnapshotTestSuite) TestNewSnapshotter(c *check.C) { - fakeCli := fake.NewSimpleClientset() - _, err := snapshot.NewSnapshotter(fakeCli, nil) - c.Assert(err, check.NotNil) - for _, tc := range []struct { - apiResources metav1.APIResourceList - expected string - check check.Checker - }{ - { - apiResources: metav1.APIResourceList{ - TypeMeta: metav1.TypeMeta{ - Kind: "VolumeSnapshot", - APIVersion: "v1alpha1", - }, - GroupVersion: "snapshot.storage.k8s.io/v1alpha1", - }, - expected: "*snapshot.SnapshotAlpha", - check: check.IsNil, - }, - { - apiResources: metav1.APIResourceList{ - TypeMeta: metav1.TypeMeta{ - Kind: "VolumeSnapshot", - APIVersion: "v1beta1", - }, - GroupVersion: "snapshot.storage.k8s.io/v1beta1", - }, - expected: "*snapshot.SnapshotBeta", - check: check.IsNil, - }, - { - apiResources: metav1.APIResourceList{ - TypeMeta: metav1.TypeMeta{ - Kind: "VolumeSnapshot", - APIVersion: "v1", - }, - GroupVersion: "snapshot.storage.k8s.io/v1", - }, - expected: "*snapshot.SnapshotStable", - check: check.IsNil, - }, - } { - apiRes := tc.apiResources - fakeCli.Resources = []*metav1.APIResourceList{&apiRes} - ss, err := snapshot.NewSnapshotter(fakeCli, nil) - c.Assert(err, tc.check) - c.Assert(reflect.TypeOf(ss).String(), check.Equals, tc.expected) - } -} - type snapshotClassTC struct { name string annotationKey string annotationValue string storageClassName string - snapClassAlpha *unstructured.Unstructured - snapClassBeta *unstructured.Unstructured - snapClassStable *unstructured.Unstructured + snapClass *unstructured.Unstructured testKey string testValue string check check.Checker @@ -883,8 +687,6 @@ type snapshotClassTC struct { func (s *SnapshotTestSuite) TestGetVolumeSnapshotClassFake(c *check.C) { ctx := context.Background() scheme := runtime.NewScheme() - scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "snapshot.storage.k8s.io", Version: "v1alpha1", Kind: "VolumeSnapshotClassList"}, &unstructured.UnstructuredList{}) - scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "snapshot.storage.k8s.io", Version: "v1beta1", Kind: "VolumeSnapshotClassList"}, &unstructured.UnstructuredList{}) scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "snapshot.storage.k8s.io", Version: "v1", Kind: "VolumeSnapshotClassList"}, &unstructured.UnstructuredList{}) dynCli := dynfake.NewSimpleDynamicClient(scheme) @@ -902,14 +704,8 @@ func (s *SnapshotTestSuite) TestGetVolumeSnapshotClassFake(c *check.C) { Provisioner: "wrongDriver", }, ) - fakeSsAlpha := snapshot.NewSnapshotAlpha(kubeCli, dynCli) - fakeSsBeta := snapshot.NewSnapshotBeta(kubeCli, dynCli) - fakeSsStable := snapshot.NewSnapshotStable(kubeCli, dynCli) - _, err := fakeSsAlpha.GetVolumeSnapshotClass(ctx, "test-annotation", "value", fakeSC) - c.Assert(err, check.NotNil) - _, err = fakeSsBeta.GetVolumeSnapshotClass(ctx, "test-annotation", "value", fakeSC) - c.Assert(err, check.NotNil) - _, err = fakeSsStable.GetVolumeSnapshotClass(ctx, "test-annotation", "value", fakeSC) + fakeSs := snapshot.NewSnapshotter(kubeCli, dynCli) + _, err := fakeSs.GetVolumeSnapshotClass(ctx, "test-annotation", "value", fakeSC) c.Assert(err, check.NotNil) for _, tc := range []snapshotClassTC{ @@ -918,9 +714,7 @@ func (s *SnapshotTestSuite) TestGetVolumeSnapshotClassFake(c *check.C) { annotationKey: "test-1", annotationValue: "true", storageClassName: fakeSC, - snapClassAlpha: snapshot.UnstructuredVolumeSnapshotClassAlpha("test-1", fakeDriver, "Delete", nil), - snapClassBeta: snapshot.UnstructuredVolumeSnapshotClass(v1beta1.VolSnapClassGVR, "test-1", fakeDriver, "Delete", nil), - snapClassStable: snapshot.UnstructuredVolumeSnapshotClass(snapshot.VolSnapClassGVR, "test-1", fakeDriver, "Delete", nil), + snapClass: snapshot.UnstructuredVolumeSnapshotClass(snapshot.VolSnapClassGVR, "test-1", fakeDriver, "Delete", nil), testKey: "test-1", testValue: "true", check: check.IsNil, @@ -930,9 +724,7 @@ func (s *SnapshotTestSuite) TestGetVolumeSnapshotClassFake(c *check.C) { annotationKey: "", annotationValue: "", storageClassName: fakeSC, - snapClassAlpha: snapshot.UnstructuredVolumeSnapshotClassAlpha("test-2", fakeDriver, "Delete", nil), - snapClassBeta: snapshot.UnstructuredVolumeSnapshotClass(v1beta1.VolSnapClassGVR, "test-2", fakeDriver, "Delete", nil), - snapClassStable: snapshot.UnstructuredVolumeSnapshotClass(snapshot.VolSnapClassGVR, "test-2", fakeDriver, "Delete", nil), + snapClass: snapshot.UnstructuredVolumeSnapshotClass(snapshot.VolSnapClassGVR, "test-2", fakeDriver, "Delete", nil), testKey: "", testValue: "", check: check.IsNil, @@ -942,9 +734,7 @@ func (s *SnapshotTestSuite) TestGetVolumeSnapshotClassFake(c *check.C) { annotationKey: "test-3", annotationValue: "false", storageClassName: fakeSC, - snapClassAlpha: snapshot.UnstructuredVolumeSnapshotClassAlpha("test-2", fakeDriver, "Delete", nil), - snapClassBeta: snapshot.UnstructuredVolumeSnapshotClass(v1beta1.VolSnapClassGVR, "test-2", fakeDriver, "Delete", nil), - snapClassStable: snapshot.UnstructuredVolumeSnapshotClass(snapshot.VolSnapClassGVR, "test-2", fakeDriver, "Delete", nil), + snapClass: snapshot.UnstructuredVolumeSnapshotClass(snapshot.VolSnapClassGVR, "test-2", fakeDriver, "Delete", nil), testKey: "invalid", testValue: "false", check: check.NotNil, @@ -954,9 +744,7 @@ func (s *SnapshotTestSuite) TestGetVolumeSnapshotClassFake(c *check.C) { annotationKey: "test-4", annotationValue: "false", storageClassName: fakeSC, - snapClassAlpha: snapshot.UnstructuredVolumeSnapshotClassAlpha("test-4", fakeDriver, "Delete", nil), - snapClassBeta: snapshot.UnstructuredVolumeSnapshotClass(v1beta1.VolSnapClassGVR, "test-4", fakeDriver, "Delete", nil), - snapClassStable: snapshot.UnstructuredVolumeSnapshotClass(snapshot.VolSnapClassGVR, "test-4", fakeDriver, "Delete", nil), + snapClass: snapshot.UnstructuredVolumeSnapshotClass(snapshot.VolSnapClassGVR, "test-4", fakeDriver, "Delete", nil), testKey: "test-4", testValue: "true", check: check.NotNil, @@ -966,9 +754,7 @@ func (s *SnapshotTestSuite) TestGetVolumeSnapshotClassFake(c *check.C) { annotationKey: "test-5", annotationValue: "true", storageClassName: "badStorageClass", - snapClassAlpha: snapshot.UnstructuredVolumeSnapshotClassAlpha("test-5", fakeDriver, "Delete", nil), - snapClassBeta: snapshot.UnstructuredVolumeSnapshotClass(v1beta1.VolSnapClassGVR, "test-5", fakeDriver, "Delete", nil), - snapClassStable: snapshot.UnstructuredVolumeSnapshotClass(snapshot.VolSnapClassGVR, "test-5", fakeDriver, "Delete", nil), + snapClass: snapshot.UnstructuredVolumeSnapshotClass(snapshot.VolSnapClassGVR, "test-5", fakeDriver, "Delete", nil), testKey: "test-5", testValue: "true", check: check.NotNil, @@ -978,9 +764,7 @@ func (s *SnapshotTestSuite) TestGetVolumeSnapshotClassFake(c *check.C) { annotationKey: "test-6", annotationValue: "true", storageClassName: fakeSC, - snapClassAlpha: snapshot.UnstructuredVolumeSnapshotClassAlpha("test-6", "driverMismatch", "Delete", nil), - snapClassBeta: snapshot.UnstructuredVolumeSnapshotClass(v1beta1.VolSnapClassGVR, "test-6", "driverMismatch", "Delete", nil), - snapClassStable: snapshot.UnstructuredVolumeSnapshotClass(snapshot.VolSnapClassGVR, "test-6", "driverMismatch", "Delete", nil), + snapClass: snapshot.UnstructuredVolumeSnapshotClass(snapshot.VolSnapClassGVR, "test-6", "driverMismatch", "Delete", nil), testKey: "test-6", testValue: "true", check: check.NotNil, @@ -990,29 +774,9 @@ func (s *SnapshotTestSuite) TestGetVolumeSnapshotClassFake(c *check.C) { annotationKey: "test-7", annotationValue: "true", storageClassName: fakeSC, - snapClassAlpha: &unstructured.Unstructured{ + snapClass: &unstructured.Unstructured{ Object: map[string]interface{}{ - "apiVersion": fmt.Sprintf("%s/%s", v1alpha1.GroupName, v1alpha1.Version), - "kind": snapshot.VolSnapClassKind, - "metadata": map[string]interface{}{ - "name": "test-7", - }, - "deletionPolicy": "Delete", - }, - }, - snapClassBeta: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": fmt.Sprintf("%s/%s", v1alpha1.GroupName, v1alpha1.Version), - "kind": snapshot.VolSnapClassKind, - "metadata": map[string]interface{}{ - "name": "test-7", - }, - "deletionPolicy": "Delete", - }, - }, - snapClassStable: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": fmt.Sprintf("%s/%s", v1alpha1.GroupName, v1alpha1.Version), + "apiVersion": fmt.Sprintf("%s/%s", snapshot.GroupName, snapshot.Version), "kind": snapshot.VolSnapClassKind, "metadata": map[string]interface{}{ "name": "test-7", @@ -1029,29 +793,9 @@ func (s *SnapshotTestSuite) TestGetVolumeSnapshotClassFake(c *check.C) { annotationKey: "test-8", annotationValue: "true", storageClassName: fakeSC, - snapClassAlpha: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": fmt.Sprintf("%s/%s", v1alpha1.GroupName, v1alpha1.Version), - "kind": "bad kind", - "metadata": map[string]interface{}{ - "name": "test-8", - }, - "deletionPolicy": "Delete", - }, - }, - snapClassBeta: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": fmt.Sprintf("%s/%s", v1alpha1.GroupName, v1alpha1.Version), - "kind": "bad kind", - "metadata": map[string]interface{}{ - "name": "test-8", - }, - "deletionPolicy": "Delete", - }, - }, - snapClassStable: &unstructured.Unstructured{ + snapClass: &unstructured.Unstructured{ Object: map[string]interface{}{ - "apiVersion": fmt.Sprintf("%s/%s", v1alpha1.GroupName, v1alpha1.Version), + "apiVersion": fmt.Sprintf("%s/%s", snapshot.GroupName, snapshot.Version), "kind": "bad kind", "metadata": map[string]interface{}{ "name": "test-8", @@ -1068,35 +812,9 @@ func (s *SnapshotTestSuite) TestGetVolumeSnapshotClassFake(c *check.C) { annotationKey: "test-9", annotationValue: "true", storageClassName: fakeSC, - snapClassAlpha: &unstructured.Unstructured{ + snapClass: &unstructured.Unstructured{ Object: map[string]interface{}{ - "apiVersion": fmt.Sprintf("%s/%s", v1alpha1.GroupName, v1alpha1.Version), - "kind": snapshot.VolSnapClassKind, - "metadata": map[string]interface{}{ - "name": "test-9", - }, - "deletionPolicy": "Delete", - "snapshotter": map[string]interface{}{ - "not": "string", - }, - }, - }, - snapClassBeta: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": fmt.Sprintf("%s/%s", v1alpha1.GroupName, v1alpha1.Version), - "kind": snapshot.VolSnapClassKind, - "metadata": map[string]interface{}{ - "name": "test-9", - }, - "deletionPolicy": "Delete", - "driver": map[string]interface{}{ - "not": "string", - }, - }, - }, - snapClassStable: &unstructured.Unstructured{ - Object: map[string]interface{}{ - "apiVersion": fmt.Sprintf("%s/%s", v1alpha1.GroupName, v1alpha1.Version), + "apiVersion": fmt.Sprintf("%s/%s", snapshot.GroupName, snapshot.Version), "kind": snapshot.VolSnapClassKind, "metadata": map[string]interface{}{ "name": "test-9", @@ -1112,9 +830,7 @@ func (s *SnapshotTestSuite) TestGetVolumeSnapshotClassFake(c *check.C) { check: check.NotNil, }, } { - tc.testGetSnapshotClass(c, dynCli, fakeSsAlpha, tc.snapClassAlpha, v1alpha1.VolSnapClassGVR) - tc.testGetSnapshotClass(c, dynCli, fakeSsBeta, tc.snapClassBeta, v1beta1.VolSnapClassGVR) - tc.testGetSnapshotClass(c, dynCli, fakeSsStable, tc.snapClassStable, snapshot.VolSnapClassGVR) + tc.testGetSnapshotClass(c, dynCli, fakeSs, tc.snapClass, snapshot.VolSnapClassGVR) } } @@ -1155,22 +871,6 @@ func findSnapshotClassName(c *check.C, ctx context.Context, dynCli dynamic.Inter c.Fail() } snapshotClass = usClass.GetName() - if vsc, ok := object.(v1alpha1.VolumeSnapshotClass); ok { - err := snapshot.TransformUnstructured(usClass, &vsc) - if err != nil { - c.Logf("Failed to query VolumeSnapshotClass, skipping test. Error: %v", err) - c.Fail() - } - snapshotterName = vsc.Snapshotter - } - if vsc, ok := object.(v1beta1.VolumeSnapshotClass); ok { - err := snapshot.TransformUnstructured(usClass, &vsc) - if err != nil { - c.Logf("Failed to query VolumeSnapshotClass, skipping test. Error: %v", err) - c.Fail() - } - snapshotterName = vsc.Driver - } if vsc, ok := object.(snapv1.VolumeSnapshotClass); ok { err := snapshot.TransformUnstructured(usClass, &vsc) if err != nil { @@ -1183,127 +883,6 @@ func findSnapshotClassName(c *check.C, ctx context.Context, dynCli dynamic.Inter return snapshotClass, snapshotterName } -func (s *SnapshotTestSuite) TestCreateFromSourceAlpha(c *check.C) { - ctx := context.Background() - namespace := "namespace" - snapshotName := "snapname" - snapshotClass := "volSnapClass" - annotation := map[string]string{ - "test": "value", - } - snapshotMeta := snapshot.ObjectMeta{ - Name: snapshotName, - Namespace: namespace, - Annotations: annotation, - } - snapshotContentMeta := snapshot.ObjectMeta{ - Name: "content", - } - volSnap := snapshot.UnstructuredVolumeSnapshotAlpha("pvcName", snapshotClass, - snapshotMeta, snapshotContentMeta) - volSnap.Object["status"] = map[string]interface{}{ - "readyToUse": false, - } - scheme := runtime.NewScheme() - dynCli := dynfake.NewSimpleDynamicClient(scheme, volSnap) - kubeCli := fake.NewSimpleClientset() - - snapshotterAlpha, ok := snapshot.NewSnapshotAlpha(kubeCli, dynCli).(*snapshot.SnapshotAlpha) - c.Assert(ok, check.Equals, true) - - // set true - err := snapshotterAlpha.UpdateVolumeSnapshotStatusAlpha(ctx, namespace, snapshotName, true) - c.Assert(err, check.IsNil) - us, err := dynCli.Resource(v1alpha1.VolSnapGVR).Namespace(namespace).Get(ctx, snapshotName, metav1.GetOptions{}) - c.Assert(err, check.IsNil) - status, ok := us.Object["status"].(map[string]interface{}) - c.Assert(ok, check.Equals, true) - c.Assert(status["readyToUse"], check.Equals, true) - - // set false - err = snapshotterAlpha.UpdateVolumeSnapshotStatusAlpha(ctx, namespace, snapshotName, false) - c.Assert(err, check.IsNil) - us, err = dynCli.Resource(v1alpha1.VolSnapGVR).Namespace(namespace).Get(ctx, snapshotName, metav1.GetOptions{}) - c.Assert(err, check.IsNil) - status, ok = us.Object["status"].(map[string]interface{}) - c.Assert(ok, check.Equals, true) - c.Assert(status["readyToUse"], check.Equals, false) - - snapshotMeta = snapshot.ObjectMeta{ - Name: snapshotName, - Namespace: namespace, - } - snapshotContentMeta = snapshot.ObjectMeta{ - Name: "content", - } - // status not set - volSnap = snapshot.UnstructuredVolumeSnapshotAlpha("pvcName", snapshotClass, - snapshotMeta, snapshotContentMeta) - dynCli = dynfake.NewSimpleDynamicClient(scheme, volSnap) - snapshotterAlpha, ok = snapshot.NewSnapshotAlpha(kubeCli, dynCli).(*snapshot.SnapshotAlpha) - c.Assert(ok, check.Equals, true) - err = snapshotterAlpha.UpdateVolumeSnapshotStatusAlpha(ctx, namespace, snapshotName, false) - c.Assert(err, check.NotNil) -} - -func (s *SnapshotTestSuite) TestCreateFromSourceBeta(c *check.C) { - ctx := context.Background() - namespace := "namespace" - snapshotName := "snapname" - snapshotClass := "volSnapClass" - snapshotMeta := snapshot.ObjectMeta{ - Name: snapshotName, - Namespace: namespace, - } - snapshotContentMeta := snapshot.ObjectMeta{ - Name: "content", - } - volSnap := snapshot.UnstructuredVolumeSnapshot(v1beta1.VolSnapGVR, "pvcName", snapshotClass, - snapshotMeta, snapshotContentMeta) - volSnap.Object["status"] = map[string]interface{}{ - "readyToUse": false, - } - scheme := runtime.NewScheme() - dynCli := dynfake.NewSimpleDynamicClient(scheme, volSnap) - kubeCli := fake.NewSimpleClientset() - - snapshotterBeta, ok := snapshot.NewSnapshotBeta(kubeCli, dynCli).(*snapshot.SnapshotBeta) - c.Assert(ok, check.Equals, true) - - // set true - err := snapshotterBeta.UpdateVolumeSnapshotStatusBeta(ctx, namespace, snapshotName, true) - c.Assert(err, check.IsNil) - us, err := dynCli.Resource(v1beta1.VolSnapGVR).Namespace(namespace).Get(ctx, snapshotName, metav1.GetOptions{}) - c.Assert(err, check.IsNil) - status, ok := us.Object["status"].(map[string]interface{}) - c.Assert(ok, check.Equals, true) - c.Assert(status["readyToUse"], check.Equals, true) - - // set false - err = snapshotterBeta.UpdateVolumeSnapshotStatusBeta(ctx, namespace, snapshotName, false) - c.Assert(err, check.IsNil) - us, err = dynCli.Resource(v1beta1.VolSnapGVR).Namespace(namespace).Get(ctx, snapshotName, metav1.GetOptions{}) - c.Assert(err, check.IsNil) - status, ok = us.Object["status"].(map[string]interface{}) - c.Assert(ok, check.Equals, true) - c.Assert(status["readyToUse"], check.Equals, false) - - // status not set - snapshotMeta = snapshot.ObjectMeta{ - Name: snapshotName, - Namespace: namespace, - } - snapshotContentMeta = snapshot.ObjectMeta{ - Name: "content", - } - volSnap = snapshot.UnstructuredVolumeSnapshot(v1beta1.VolSnapGVR, "pvcName", snapshotClass, snapshotMeta, snapshotContentMeta) - dynCli = dynfake.NewSimpleDynamicClient(scheme, volSnap) - snapshotterBeta, ok = snapshot.NewSnapshotBeta(kubeCli, dynCli).(*snapshot.SnapshotBeta) - c.Assert(ok, check.Equals, true) - err = snapshotterBeta.UpdateVolumeSnapshotStatusBeta(ctx, namespace, snapshotName, false) - c.Assert(err, check.NotNil) -} - func (s *SnapshotTestSuite) TestCreateFromSource(c *check.C) { ctx := context.Background() namespace := "namespace" @@ -1327,62 +906,32 @@ func (s *SnapshotTestSuite) TestCreateFromSource(c *check.C) { snapshotContentMeta := snapshot.ObjectMeta{ Name: "content", } - volSnap := snapshot.UnstructuredVolumeSnapshot(v1alpha1.VolSnapGVR, "pvcName", snapshotClass, snapshotMeta, snapshotContentMeta) - volSnapClass := snapshot.UnstructuredVolumeSnapshotClass(v1alpha1.VolSnapClassGVR, snapshotClass, "driver", "DELETE", nil) - dynCli := dynfake.NewSimpleDynamicClient(scheme, volSnap, volSnapClass) - kubeCli := fake.NewSimpleClientset() - snapshoterAlpha := snapshot.NewSnapshotAlpha(kubeCli, dynCli) - snapshotMeta = snapshot.ObjectMeta{ - Name: existingSnapshotName, - Namespace: namespace, - Annotations: annotations, - } - snapshotContentMeta = snapshot.ObjectMeta{ - Name: "content", - } - volSnap = snapshot.UnstructuredVolumeSnapshot( - v1beta1.VolSnapGVR, - "pvcName", - snapshotClass, - snapshotMeta, snapshotContentMeta) - volSnapClass = snapshot.UnstructuredVolumeSnapshotClass(v1beta1.VolSnapClassGVR, snapshotClass, "driver", "DELETE", nil) - dynCli = dynfake.NewSimpleDynamicClient(scheme, volSnap, volSnapClass) - kubeCli = fake.NewSimpleClientset() - snapshoterBeta := snapshot.NewSnapshotBeta(kubeCli, dynCli) - snapshotMeta = snapshot.ObjectMeta{ - Name: existingSnapshotName, - Namespace: namespace, - Annotations: annotations, - } - snapshotContentMeta = snapshot.ObjectMeta{ - Name: "content", - } - volSnap = snapshot.UnstructuredVolumeSnapshot( + volSnap := snapshot.UnstructuredVolumeSnapshot( snapshot.VolSnapGVR, "pvcName", snapshotClass, snapshotMeta, snapshotContentMeta) - volSnapClass = snapshot.UnstructuredVolumeSnapshotClass(snapshot.VolSnapClassGVR, snapshotClass, "driver", "DELETE", nil) - dynCli = dynfake.NewSimpleDynamicClient(scheme, volSnap, volSnapClass) - kubeCli = fake.NewSimpleClientset() - snapshoterStable := snapshot.NewSnapshotStable(kubeCli, dynCli) - for _, snapshoter := range []snapshot.Snapshotter{snapshoterAlpha, snapshoterBeta, snapshoterStable} { + volSnapClass := snapshot.UnstructuredVolumeSnapshotClass(snapshot.VolSnapClassGVR, snapshotClass, "driver", "DELETE", nil) + dynCli := dynfake.NewSimpleDynamicClient(scheme, volSnap, volSnapClass) + kubeCli := fake.NewSimpleClientset() + snapshotter := snapshot.NewSnapshotter(kubeCli, dynCli) + for _, snapshotter := range []snapshot.Snapshotter{snapshotter} { snapshotMeta = snapshot.ObjectMeta{ Name: snapshotName, Namespace: namespace, Labels: labels, Annotations: annotations, } - err := snapshoter.CreateFromSource(ctx, source, false, snapshotMeta, snapshot.ObjectMeta{}) + err := snapshotter.CreateFromSource(ctx, source, false, snapshotMeta, snapshot.ObjectMeta{}) c.Assert(err, check.IsNil) - foundSns, err := snapshoter.List(ctx, namespace, labels) + foundSns, err := snapshotter.List(ctx, namespace, labels) c.Assert(err, check.IsNil) c.Assert(foundSns.Items, check.HasLen, 1) c.Assert(foundSns.Items[0].Name, check.Equals, snapshotName) } } -func (s *SnapshotTestSuite) TestCreateFromSourceStable(c *check.C) { +func (s *SnapshotTestSuite) TestUpdateVolumeSnapshotStatus(c *check.C) { ctx := context.Background() namespace := "namespace" snapshotName := "snapname" @@ -1405,11 +954,11 @@ func (s *SnapshotTestSuite) TestCreateFromSourceStable(c *check.C) { dynCli := dynfake.NewSimpleDynamicClient(scheme, volSnap) kubeCli := fake.NewSimpleClientset() - snapshotterStable, ok := snapshot.NewSnapshotStable(kubeCli, dynCli).(*snapshot.SnapshotStable) + snapshotter, ok := snapshot.NewSnapshotter(kubeCli, dynCli).(*snapshot.Snapshot) c.Assert(ok, check.Equals, true) // set true - err := snapshotterStable.UpdateVolumeSnapshotStatusStable(ctx, namespace, snapshotName, true) + err := snapshotter.UpdateVolumeSnapshotStatus(ctx, namespace, snapshotName, true) c.Assert(err, check.IsNil) us, err := dynCli.Resource(snapshot.VolSnapGVR).Namespace(namespace).Get(ctx, snapshotName, metav1.GetOptions{}) c.Assert(err, check.IsNil) @@ -1418,7 +967,7 @@ func (s *SnapshotTestSuite) TestCreateFromSourceStable(c *check.C) { c.Assert(status["readyToUse"], check.Equals, true) // set false - err = snapshotterStable.UpdateVolumeSnapshotStatusStable(ctx, namespace, snapshotName, false) + err = snapshotter.UpdateVolumeSnapshotStatus(ctx, namespace, snapshotName, false) c.Assert(err, check.IsNil) us, err = dynCli.Resource(snapshot.VolSnapGVR).Namespace(namespace).Get(ctx, snapshotName, metav1.GetOptions{}) c.Assert(err, check.IsNil) @@ -1438,19 +987,19 @@ func (s *SnapshotTestSuite) TestCreateFromSourceStable(c *check.C) { volSnap = snapshot.UnstructuredVolumeSnapshot(snapshot.VolSnapGVR, "pvcName", snapshotClass, snapshotMeta, snapshotContentMeta) dynCli = dynfake.NewSimpleDynamicClient(scheme, volSnap) - snapshotterStable, ok = snapshot.NewSnapshotStable(kubeCli, dynCli).(*snapshot.SnapshotStable) + snapshotter, ok = snapshot.NewSnapshotter(kubeCli, dynCli).(*snapshot.Snapshot) c.Assert(ok, check.Equals, true) - err = snapshotterStable.UpdateVolumeSnapshotStatusStable(ctx, namespace, snapshotName, false) + err = snapshotter.UpdateVolumeSnapshotStatus(ctx, namespace, snapshotName, false) c.Assert(err, check.NotNil) } func (s *SnapshotTestSuite) TestGetSnapshotClassbyAnnotation(c *check.C) { ctx := context.Background() - vsc1 := snapshot.UnstructuredVolumeSnapshotClass(v1beta1.VolSnapClassGVR, "vsc1", "driver", snapshot.DeletionPolicyDelete, nil) + vsc1 := snapshot.UnstructuredVolumeSnapshotClass(snapshot.VolSnapClassGVR, "vsc1", "driver", snapshot.DeletionPolicyDelete, nil) vsc1.SetAnnotations(map[string]string{ "key": "value", }) - vsc2 := snapshot.UnstructuredVolumeSnapshotClass(v1beta1.VolSnapClassGVR, "vsc2", "driver", snapshot.DeletionPolicyDelete, nil) + vsc2 := snapshot.UnstructuredVolumeSnapshotClass(snapshot.VolSnapClassGVR, "vsc2", "driver", snapshot.DeletionPolicyDelete, nil) sc1 := &scv1.StorageClass{ ObjectMeta: metav1.ObjectMeta{ Name: "sc1", @@ -1467,7 +1016,7 @@ func (s *SnapshotTestSuite) TestGetSnapshotClassbyAnnotation(c *check.C) { Provisioner: "driver", } scheme := runtime.NewScheme() - scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "snapshot.storage.k8s.io", Version: "v1beta1", Kind: "VolumeSnapshotClassList"}, &unstructured.UnstructuredList{}) + scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "snapshot.storage.k8s.io", Version: "v1", Kind: "VolumeSnapshotClassList"}, &unstructured.UnstructuredList{}) for _, tc := range []struct { dyncli dynamic.Interface kubecli kubernetes.Interface @@ -1481,7 +1030,7 @@ func (s *SnapshotTestSuite) TestGetSnapshotClassbyAnnotation(c *check.C) { { dyncli: dynfake.NewSimpleDynamicClient(scheme, vsc1), kubecli: fake.NewSimpleClientset(sc1), - gvr: v1beta1.VolSnapClassGVR, + gvr: snapshot.VolSnapClassGVR, key: "key", value: "value", sc: "sc1", @@ -1491,7 +1040,7 @@ func (s *SnapshotTestSuite) TestGetSnapshotClassbyAnnotation(c *check.C) { { // no vsc available dyncli: dynfake.NewSimpleDynamicClient(scheme), kubecli: fake.NewSimpleClientset(sc1), - gvr: v1beta1.VolSnapClassGVR, + gvr: snapshot.VolSnapClassGVR, key: "key", value: "value", sc: "sc1", @@ -1500,7 +1049,7 @@ func (s *SnapshotTestSuite) TestGetSnapshotClassbyAnnotation(c *check.C) { { // annotation on sc dyncli: dynfake.NewSimpleDynamicClient(scheme, vsc2), kubecli: fake.NewSimpleClientset(sc2), - gvr: v1beta1.VolSnapClassGVR, + gvr: snapshot.VolSnapClassGVR, key: "key", value: "value", sc: "sc2", @@ -1510,7 +1059,7 @@ func (s *SnapshotTestSuite) TestGetSnapshotClassbyAnnotation(c *check.C) { { // missing vsc dyncli: dynfake.NewSimpleDynamicClient(scheme), kubecli: fake.NewSimpleClientset(sc2), - gvr: v1beta1.VolSnapClassGVR, + gvr: snapshot.VolSnapClassGVR, key: "key", value: "value", sc: "sc2", @@ -1532,8 +1081,6 @@ var _ = check.Suite(&SnapshotLocalTestSuite{}) func (s *SnapshotLocalTestSuite) TestLabels(c *check.C) { ctx := context.Background() scheme := runtime.NewScheme() - scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "snapshot.storage.k8s.io", Version: "v1alpha1", Kind: "VolumeSnapshotList"}, &unstructured.UnstructuredList{}) - scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "snapshot.storage.k8s.io", Version: "v1beta1", Kind: "VolumeSnapshotList"}, &unstructured.UnstructuredList{}) scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "snapshot.storage.k8s.io", Version: "v1", Kind: "VolumeSnapshotList"}, &unstructured.UnstructuredList{}) ns := "namespace" volName := "vol1" @@ -1583,9 +1130,7 @@ func (s *SnapshotLocalTestSuite) TestLabels(c *check.C) { }, } { for _, fakeSs := range []snapshot.Snapshotter{ - snapshot.NewSnapshotAlpha(fakeCli, tc.dynCli), - snapshot.NewSnapshotBeta(fakeCli, tc.dynCli), - snapshot.NewSnapshotStable(fakeCli, tc.dynCli), + snapshot.NewSnapshotter(fakeCli, tc.dynCli), } { var err error var list *snapv1.VolumeSnapshotList @@ -1606,8 +1151,6 @@ func (s *SnapshotLocalTestSuite) TestLabels(c *check.C) { func (s *SnapshotLocalTestSuite) TestAnnotations(c *check.C) { ctx := context.Background() scheme := runtime.NewScheme() - scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "snapshot.storage.k8s.io", Version: "v1alpha1", Kind: "VolumeSnapshotList"}, &unstructured.UnstructuredList{}) - scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "snapshot.storage.k8s.io", Version: "v1beta1", Kind: "VolumeSnapshotList"}, &unstructured.UnstructuredList{}) scheme.AddKnownTypeWithName(schema.GroupVersionKind{Group: "snapshot.storage.k8s.io", Version: "v1", Kind: "VolumeSnapshotList"}, &unstructured.UnstructuredList{}) ns := "namespace" volName := "vol1" @@ -1645,9 +1188,7 @@ func (s *SnapshotLocalTestSuite) TestAnnotations(c *check.C) { }, } { for _, fakeSs := range []snapshot.Snapshotter{ - snapshot.NewSnapshotAlpha(fakeCli, tc.dynCli), - snapshot.NewSnapshotBeta(fakeCli, tc.dynCli), - snapshot.NewSnapshotStable(fakeCli, tc.dynCli), + snapshot.NewSnapshotter(fakeCli, tc.dynCli), } { var err error var vs *snapv1.VolumeSnapshot diff --git a/pkg/kube/volume/volume.go b/pkg/kube/volume/volume.go index 021f3cfceb..4eb1ff2531 100644 --- a/pkg/kube/volume/volume.go +++ b/pkg/kube/volume/volume.go @@ -204,10 +204,7 @@ func getPVCRestoreSize(ctx context.Context, args *CreatePVCFromSnapshotArgs) (*r quantities = append(quantities, &s) } - sns, err := snapshot.NewSnapshotter(args.KubeCli, args.DynCli) - if err != nil { - return nil, errkit.Wrap(err, "Failed to get snapshotter") - } + sns := snapshot.NewSnapshotter(args.KubeCli, args.DynCli) snap, err := sns.Get(ctx, args.SnapshotName, args.Namespace) if err != nil { return nil, errkit.Wrap(err, "Failed to get snapshot")