k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/e2e/storage/csimock/csi_snapshot.go (about)

     1  /*
     2  Copyright 2022 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package csimock
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"time"
    23  
    24  	"github.com/onsi/ginkgo/v2"
    25  	"github.com/onsi/gomega"
    26  
    27  	"google.golang.org/grpc/codes"
    28  	"google.golang.org/grpc/status"
    29  	v1 "k8s.io/api/core/v1"
    30  	storagev1 "k8s.io/api/storage/v1"
    31  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    32  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    34  	clientset "k8s.io/client-go/kubernetes"
    35  	"k8s.io/kubernetes/test/e2e/feature"
    36  	"k8s.io/kubernetes/test/e2e/framework"
    37  	e2emetrics "k8s.io/kubernetes/test/e2e/framework/metrics"
    38  	e2epv "k8s.io/kubernetes/test/e2e/framework/pv"
    39  	e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
    40  	"k8s.io/kubernetes/test/e2e/storage/drivers"
    41  	storageframework "k8s.io/kubernetes/test/e2e/storage/framework"
    42  	"k8s.io/kubernetes/test/e2e/storage/utils"
    43  	admissionapi "k8s.io/pod-security-admission/api"
    44  )
    45  
    46  var _ = utils.SIGDescribe("CSI Mock volume snapshot", func() {
    47  	f := framework.NewDefaultFramework("csi-mock-volumes-snapshot")
    48  	f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
    49  	m := newMockDriverSetup(f)
    50  
    51  	f.Context("CSI Volume Snapshots", feature.VolumeSnapshotDataSource, func() {
    52  		tests := []struct {
    53  			name               string
    54  			createSnapshotHook func(counter int64) error
    55  		}{
    56  			{
    57  				name: "volumesnapshotcontent and pvc in Bound state with deletion timestamp set should not get deleted while snapshot finalizer exists",
    58  				createSnapshotHook: func(counter int64) error {
    59  					if counter < 8 {
    60  						return status.Error(codes.DeadlineExceeded, "fake error")
    61  					}
    62  					return nil
    63  				},
    64  			},
    65  		}
    66  		for _, test := range tests {
    67  			test := test
    68  			ginkgo.It(test.name, func(ctx context.Context) {
    69  				var hooks *drivers.Hooks
    70  				if test.createSnapshotHook != nil {
    71  					hooks = createPreHook("CreateSnapshot", test.createSnapshotHook)
    72  				}
    73  				m.init(ctx, testParameters{
    74  					disableAttach:  true,
    75  					registerDriver: true,
    76  					enableSnapshot: true,
    77  					hooks:          hooks,
    78  				})
    79  				sDriver, ok := m.driver.(storageframework.SnapshottableTestDriver)
    80  				if !ok {
    81  					e2eskipper.Skipf("mock driver %s does not support snapshots -- skipping", m.driver.GetDriverInfo().Name)
    82  
    83  				}
    84  				ctx, cancel := context.WithTimeout(ctx, csiPodRunningTimeout)
    85  				defer cancel()
    86  				ginkgo.DeferCleanup(m.cleanup)
    87  
    88  				sc := m.driver.GetDynamicProvisionStorageClass(ctx, m.config, "")
    89  				ginkgo.By("Creating storage class")
    90  				class, err := m.cs.StorageV1().StorageClasses().Create(context.TODO(), sc, metav1.CreateOptions{})
    91  				framework.ExpectNoError(err, "Failed to create class: %v", err)
    92  				m.sc[class.Name] = class
    93  				claim := e2epv.MakePersistentVolumeClaim(e2epv.PersistentVolumeClaimConfig{
    94  					// Use static name so that the volumesnapshot can be created before the pvc.
    95  					Name:             "snapshot-test-pvc",
    96  					StorageClassName: &(class.Name),
    97  				}, f.Namespace.Name)
    98  
    99  				ginkgo.By("Creating snapshot")
   100  				// TODO: Test VolumeSnapshots with Retain policy
   101  				parameters := map[string]string{}
   102  				snapshotClass, snapshot := storageframework.CreateSnapshot(ctx, sDriver, m.config, storageframework.DynamicSnapshotDelete, claim.Name, claim.Namespace, f.Timeouts, parameters)
   103  				framework.ExpectNoError(err, "failed to create snapshot")
   104  				m.vsc[snapshotClass.GetName()] = snapshotClass
   105  				volumeSnapshotName := snapshot.GetName()
   106  
   107  				ginkgo.By(fmt.Sprintf("Creating PVC %s/%s", claim.Namespace, claim.Name))
   108  				claim, err = m.cs.CoreV1().PersistentVolumeClaims(f.Namespace.Name).Create(context.TODO(), claim, metav1.CreateOptions{})
   109  				framework.ExpectNoError(err, "Failed to create claim: %v", err)
   110  
   111  				ginkgo.By(fmt.Sprintf("Wait for finalizer to be added to claim %s/%s", claim.Namespace, claim.Name))
   112  				err = e2epv.WaitForPVCFinalizer(ctx, m.cs, claim.Name, claim.Namespace, pvcAsSourceProtectionFinalizer, 1*time.Millisecond, 1*time.Minute)
   113  				framework.ExpectNoError(err)
   114  
   115  				ginkgo.By("Wait for PVC to be Bound")
   116  				_, err = e2epv.WaitForPVClaimBoundPhase(ctx, m.cs, []*v1.PersistentVolumeClaim{claim}, 1*time.Minute)
   117  				framework.ExpectNoError(err, "Failed to create claim: %v", err)
   118  
   119  				ginkgo.By(fmt.Sprintf("Delete PVC %s", claim.Name))
   120  				err = e2epv.DeletePersistentVolumeClaim(ctx, m.cs, claim.Name, claim.Namespace)
   121  				framework.ExpectNoError(err, "failed to delete pvc")
   122  
   123  				ginkgo.By("Get PVC from API server and verify deletion timestamp is set")
   124  				claim, err = m.cs.CoreV1().PersistentVolumeClaims(f.Namespace.Name).Get(context.TODO(), claim.Name, metav1.GetOptions{})
   125  				if err != nil {
   126  					if !apierrors.IsNotFound(err) {
   127  						framework.ExpectNoError(err, "Failed to get claim: %v", err)
   128  					}
   129  					framework.Logf("PVC not found. Continuing to test VolumeSnapshotContent finalizer")
   130  				} else if claim.DeletionTimestamp == nil {
   131  					framework.Failf("Expected deletion timestamp to be set on PVC: %v", claim)
   132  				}
   133  
   134  				ginkgo.By(fmt.Sprintf("Get VolumeSnapshotContent bound to VolumeSnapshot %s", snapshot.GetName()))
   135  				snapshotContent := utils.GetSnapshotContentFromSnapshot(ctx, m.config.Framework.DynamicClient, snapshot, f.Timeouts.SnapshotCreate)
   136  				volumeSnapshotContentName := snapshotContent.GetName()
   137  
   138  				ginkgo.By(fmt.Sprintf("Verify VolumeSnapshotContent %s contains finalizer %s", snapshot.GetName(), volumeSnapshotContentFinalizer))
   139  				err = utils.WaitForGVRFinalizer(ctx, m.config.Framework.DynamicClient, utils.SnapshotContentGVR, volumeSnapshotContentName, "", volumeSnapshotContentFinalizer, 1*time.Millisecond, 1*time.Minute)
   140  				framework.ExpectNoError(err)
   141  
   142  				ginkgo.By(fmt.Sprintf("Delete VolumeSnapshotContent %s", snapshotContent.GetName()))
   143  				err = m.config.Framework.DynamicClient.Resource(utils.SnapshotContentGVR).Delete(ctx, snapshotContent.GetName(), metav1.DeleteOptions{})
   144  				framework.ExpectNoError(err, "Failed to delete snapshotcontent: %v", err)
   145  
   146  				ginkgo.By("Get VolumeSnapshotContent from API server and verify deletion timestamp is set")
   147  				snapshotContent, err = m.config.Framework.DynamicClient.Resource(utils.SnapshotContentGVR).Get(context.TODO(), snapshotContent.GetName(), metav1.GetOptions{})
   148  				framework.ExpectNoError(err)
   149  
   150  				if snapshotContent.GetDeletionTimestamp() == nil {
   151  					framework.Failf("Expected deletion timestamp to be set on snapshotcontent")
   152  				}
   153  
   154  				// If the claim is non existent, the Get() call on the API server returns
   155  				// an non-nil claim object with all fields unset.
   156  				// Refer https://github.com/kubernetes/kubernetes/pull/99167#issuecomment-781670012
   157  				if claim != nil && claim.Spec.VolumeName != "" {
   158  					ginkgo.By(fmt.Sprintf("Wait for PV %s to be deleted", claim.Spec.VolumeName))
   159  					err = e2epv.WaitForPersistentVolumeDeleted(ctx, m.cs, claim.Spec.VolumeName, framework.Poll, 3*time.Minute)
   160  					framework.ExpectNoError(err, fmt.Sprintf("failed to delete PV %s", claim.Spec.VolumeName))
   161  				}
   162  
   163  				ginkgo.By(fmt.Sprintf("Verify VolumeSnapshot %s contains finalizer %s", snapshot.GetName(), volumeSnapshotBoundFinalizer))
   164  				err = utils.WaitForGVRFinalizer(ctx, m.config.Framework.DynamicClient, utils.SnapshotGVR, volumeSnapshotName, f.Namespace.Name, volumeSnapshotBoundFinalizer, 1*time.Millisecond, 1*time.Minute)
   165  				framework.ExpectNoError(err)
   166  
   167  				ginkgo.By("Delete VolumeSnapshot")
   168  				err = utils.DeleteAndWaitSnapshot(ctx, m.config.Framework.DynamicClient, f.Namespace.Name, volumeSnapshotName, framework.Poll, framework.SnapshotDeleteTimeout)
   169  				framework.ExpectNoError(err, fmt.Sprintf("failed to delete VolumeSnapshot %s", volumeSnapshotName))
   170  
   171  				ginkgo.By(fmt.Sprintf("Wait for VolumeSnapshotContent %s to be deleted", volumeSnapshotContentName))
   172  				err = utils.WaitForGVRDeletion(ctx, m.config.Framework.DynamicClient, utils.SnapshotContentGVR, volumeSnapshotContentName, framework.Poll, framework.SnapshotDeleteTimeout)
   173  				framework.ExpectNoError(err, fmt.Sprintf("failed to delete VolumeSnapshotContent %s", volumeSnapshotContentName))
   174  			})
   175  		}
   176  	})
   177  
   178  	f.Context("CSI Volume Snapshots secrets", feature.VolumeSnapshotDataSource, func() {
   179  
   180  		var (
   181  			// CSISnapshotterSecretName is the name of the secret to be created
   182  			CSISnapshotterSecretName string = "snapshot-secret"
   183  
   184  			// CSISnapshotterSecretNameAnnotation is the annotation key for the CSI snapshotter secret name in VolumeSnapshotClass.parameters
   185  			CSISnapshotterSecretNameAnnotation string = "csi.storage.k8s.io/snapshotter-secret-name"
   186  
   187  			// CSISnapshotterSecretNamespaceAnnotation is the annotation key for the CSI snapshotter secret namespace in VolumeSnapshotClass.parameters
   188  			CSISnapshotterSecretNamespaceAnnotation string = "csi.storage.k8s.io/snapshotter-secret-namespace"
   189  
   190  			// anotations holds the annotations object
   191  			annotations interface{}
   192  		)
   193  
   194  		tests := []struct {
   195  			name               string
   196  			createSnapshotHook func(counter int64) error
   197  		}{
   198  			{
   199  				// volume snapshot should be created using secrets successfully even if there is a failure in the first few attempts,
   200  				name: "volume snapshot create/delete with secrets",
   201  				// Fail the first 8 calls to create snapshot and succeed the  9th call.
   202  				createSnapshotHook: func(counter int64) error {
   203  					if counter < 8 {
   204  						return status.Error(codes.DeadlineExceeded, "fake error")
   205  					}
   206  					return nil
   207  				},
   208  			},
   209  		}
   210  		for _, test := range tests {
   211  			test := test
   212  			ginkgo.It(test.name, func(ctx context.Context) {
   213  				hooks := createPreHook("CreateSnapshot", test.createSnapshotHook)
   214  				m.init(ctx, testParameters{
   215  					disableAttach:  true,
   216  					registerDriver: true,
   217  					enableSnapshot: true,
   218  					hooks:          hooks,
   219  				})
   220  
   221  				sDriver, ok := m.driver.(storageframework.SnapshottableTestDriver)
   222  				if !ok {
   223  					e2eskipper.Skipf("mock driver does not support snapshots -- skipping")
   224  				}
   225  				ginkgo.DeferCleanup(m.cleanup)
   226  
   227  				var sc *storagev1.StorageClass
   228  				if dDriver, ok := m.driver.(storageframework.DynamicPVTestDriver); ok {
   229  					sc = dDriver.GetDynamicProvisionStorageClass(ctx, m.config, "")
   230  				}
   231  				ginkgo.By("Creating storage class")
   232  				class, err := m.cs.StorageV1().StorageClasses().Create(context.TODO(), sc, metav1.CreateOptions{})
   233  				framework.ExpectNoError(err, "Failed to create storage class: %v", err)
   234  				m.sc[class.Name] = class
   235  				pvc := e2epv.MakePersistentVolumeClaim(e2epv.PersistentVolumeClaimConfig{
   236  					Name:             "snapshot-test-pvc",
   237  					StorageClassName: &(class.Name),
   238  				}, f.Namespace.Name)
   239  
   240  				ginkgo.By(fmt.Sprintf("Creating PVC %s/%s", pvc.Namespace, pvc.Name))
   241  				pvc, err = m.cs.CoreV1().PersistentVolumeClaims(f.Namespace.Name).Create(context.TODO(), pvc, metav1.CreateOptions{})
   242  				framework.ExpectNoError(err, "Failed to create claim: %v", err)
   243  
   244  				ginkgo.By("Wait for PVC to be Bound")
   245  				_, err = e2epv.WaitForPVClaimBoundPhase(ctx, m.cs, []*v1.PersistentVolumeClaim{pvc}, 1*time.Minute)
   246  				framework.ExpectNoError(err, "Failed to create claim: %v", err)
   247  
   248  				m.pvcs = append(m.pvcs, pvc)
   249  
   250  				ginkgo.By("Creating Secret")
   251  				secret := &v1.Secret{
   252  					ObjectMeta: metav1.ObjectMeta{
   253  						Namespace: f.Namespace.Name,
   254  						Name:      CSISnapshotterSecretName,
   255  					},
   256  					Data: map[string][]byte{
   257  						"secret-data": []byte("secret-value-1"),
   258  					},
   259  				}
   260  
   261  				if secret, err := m.cs.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), secret, metav1.CreateOptions{}); err != nil {
   262  					framework.Failf("unable to create test secret %s: %v", secret.Name, err)
   263  				}
   264  
   265  				ginkgo.By("Creating snapshot with secrets")
   266  				parameters := map[string]string{
   267  					CSISnapshotterSecretNameAnnotation:      CSISnapshotterSecretName,
   268  					CSISnapshotterSecretNamespaceAnnotation: f.Namespace.Name,
   269  				}
   270  
   271  				_, snapshot := storageframework.CreateSnapshot(ctx, sDriver, m.config, storageframework.DynamicSnapshotDelete, pvc.Name, pvc.Namespace, f.Timeouts, parameters)
   272  				framework.ExpectNoError(err, "failed to create snapshot")
   273  				snapshotcontent := utils.GetSnapshotContentFromSnapshot(ctx, m.config.Framework.DynamicClient, snapshot, f.Timeouts.SnapshotCreate)
   274  				if annotations, ok = snapshotcontent.Object["metadata"].(map[string]interface{})["annotations"]; !ok {
   275  					framework.Failf("Unable to get volume snapshot content annotations")
   276  				}
   277  
   278  				// checks if delete snapshot secrets annotation is applied to the VolumeSnapshotContent.
   279  				checkDeleteSnapshotSecrets(m.cs, annotations)
   280  
   281  				// delete the snapshot and check if the snapshot is deleted.
   282  				deleteSnapshot(m.cs, m.config, snapshot)
   283  			})
   284  		}
   285  	})
   286  
   287  	f.Context("CSI Snapshot Controller metrics", feature.VolumeSnapshotDataSource, func() {
   288  		tests := []struct {
   289  			name    string
   290  			pattern storageframework.TestPattern
   291  		}{
   292  			{
   293  				name:    "snapshot controller should emit dynamic CreateSnapshot, CreateSnapshotAndReady, and DeleteSnapshot metrics",
   294  				pattern: storageframework.DynamicSnapshotDelete,
   295  			},
   296  			{
   297  				name:    "snapshot controller should emit pre-provisioned CreateSnapshot, CreateSnapshotAndReady, and DeleteSnapshot metrics",
   298  				pattern: storageframework.PreprovisionedSnapshotDelete,
   299  			},
   300  		}
   301  		for _, test := range tests {
   302  			test := test
   303  			ginkgo.It(test.name, func(ctx context.Context) {
   304  				m.init(ctx, testParameters{
   305  					disableAttach:  true,
   306  					registerDriver: true,
   307  					enableSnapshot: true,
   308  				})
   309  
   310  				sDriver, ok := m.driver.(storageframework.SnapshottableTestDriver)
   311  				if !ok {
   312  					e2eskipper.Skipf("mock driver does not support snapshots -- skipping")
   313  				}
   314  				ginkgo.DeferCleanup(m.cleanup)
   315  
   316  				metricsGrabber, err := e2emetrics.NewMetricsGrabber(ctx, m.config.Framework.ClientSet, nil, f.ClientConfig(), false, false, false, false, false, true)
   317  				if err != nil {
   318  					framework.Failf("Error creating metrics grabber : %v", err)
   319  				}
   320  
   321  				// Grab initial metrics - if this fails, snapshot controller metrics are not setup. Skip in this case.
   322  				_, err = metricsGrabber.GrabFromSnapshotController(ctx, framework.TestContext.SnapshotControllerPodName, framework.TestContext.SnapshotControllerHTTPPort)
   323  				if err != nil {
   324  					e2eskipper.Skipf("Snapshot controller metrics not found -- skipping")
   325  				}
   326  
   327  				ginkgo.By("getting all initial metric values")
   328  				metricsTestConfig := newSnapshotMetricsTestConfig("snapshot_controller_operation_total_seconds_count",
   329  					"count",
   330  					m.config.GetUniqueDriverName(),
   331  					"CreateSnapshot",
   332  					"success",
   333  					"",
   334  					test.pattern)
   335  				createSnapshotMetrics := newSnapshotControllerMetrics(metricsTestConfig, metricsGrabber)
   336  				originalCreateSnapshotCount, _ := createSnapshotMetrics.getSnapshotControllerMetricValue(ctx)
   337  				metricsTestConfig.operationName = "CreateSnapshotAndReady"
   338  				createSnapshotAndReadyMetrics := newSnapshotControllerMetrics(metricsTestConfig, metricsGrabber)
   339  				originalCreateSnapshotAndReadyCount, _ := createSnapshotAndReadyMetrics.getSnapshotControllerMetricValue(ctx)
   340  
   341  				metricsTestConfig.operationName = "DeleteSnapshot"
   342  				deleteSnapshotMetrics := newSnapshotControllerMetrics(metricsTestConfig, metricsGrabber)
   343  				originalDeleteSnapshotCount, _ := deleteSnapshotMetrics.getSnapshotControllerMetricValue(ctx)
   344  
   345  				ginkgo.By("Creating storage class")
   346  				var sc *storagev1.StorageClass
   347  				if dDriver, ok := m.driver.(storageframework.DynamicPVTestDriver); ok {
   348  					sc = dDriver.GetDynamicProvisionStorageClass(ctx, m.config, "")
   349  				}
   350  				class, err := m.cs.StorageV1().StorageClasses().Create(context.TODO(), sc, metav1.CreateOptions{})
   351  				framework.ExpectNoError(err, "Failed to create storage class: %v", err)
   352  				m.sc[class.Name] = class
   353  				pvc := e2epv.MakePersistentVolumeClaim(e2epv.PersistentVolumeClaimConfig{
   354  					Name:             "snapshot-test-pvc",
   355  					StorageClassName: &(class.Name),
   356  				}, f.Namespace.Name)
   357  
   358  				ginkgo.By(fmt.Sprintf("Creating PVC %s/%s", pvc.Namespace, pvc.Name))
   359  				pvc, err = m.cs.CoreV1().PersistentVolumeClaims(f.Namespace.Name).Create(context.TODO(), pvc, metav1.CreateOptions{})
   360  				framework.ExpectNoError(err, "Failed to create claim: %v", err)
   361  
   362  				ginkgo.By("Wait for PVC to be Bound")
   363  				_, err = e2epv.WaitForPVClaimBoundPhase(ctx, m.cs, []*v1.PersistentVolumeClaim{pvc}, 1*time.Minute)
   364  				framework.ExpectNoError(err, "Failed to create claim: %v", err)
   365  
   366  				ginkgo.By("Creating snapshot")
   367  				parameters := map[string]string{}
   368  				sr := storageframework.CreateSnapshotResource(ctx, sDriver, m.config, test.pattern, pvc.Name, pvc.Namespace, f.Timeouts, parameters)
   369  				framework.ExpectNoError(err, "failed to create snapshot")
   370  
   371  				ginkgo.By("Checking for CreateSnapshot metrics")
   372  				createSnapshotMetrics.waitForSnapshotControllerMetric(ctx, originalCreateSnapshotCount+1.0, f.Timeouts.SnapshotControllerMetrics)
   373  
   374  				ginkgo.By("Checking for CreateSnapshotAndReady metrics")
   375  				err = utils.WaitForSnapshotReady(ctx, m.config.Framework.DynamicClient, pvc.Namespace, sr.Vs.GetName(), framework.Poll, f.Timeouts.SnapshotCreate)
   376  				framework.ExpectNoError(err, "failed to wait for snapshot ready")
   377  				createSnapshotAndReadyMetrics.waitForSnapshotControllerMetric(ctx, originalCreateSnapshotAndReadyCount+1.0, f.Timeouts.SnapshotControllerMetrics)
   378  
   379  				// delete the snapshot and check if the snapshot is deleted
   380  				deleteSnapshot(m.cs, m.config, sr.Vs)
   381  
   382  				ginkgo.By("check for delete metrics")
   383  				metricsTestConfig.operationName = "DeleteSnapshot"
   384  				deleteSnapshotMetrics.waitForSnapshotControllerMetric(ctx, originalDeleteSnapshotCount+1.0, f.Timeouts.SnapshotControllerMetrics)
   385  			})
   386  		}
   387  	})
   388  })
   389  
   390  // checkDeleteSnapshotSecrets checks if delete snapshot secrets annotation is applied to the VolumeSnapshotContent.
   391  func checkDeleteSnapshotSecrets(cs clientset.Interface, annotations interface{}) error {
   392  	ginkgo.By("checking if delete snapshot secrets annotation is applied to the VolumeSnapshotContent")
   393  
   394  	var (
   395  		annDeletionSecretName      string
   396  		annDeletionSecretNamespace string
   397  		ok                         bool
   398  		err                        error
   399  
   400  		// CSISnapshotterDeleteSecretNameAnnotation is the annotation key for the CSI snapshotter delete secret name in VolumeSnapshotClass.parameters
   401  		CSISnapshotterDeleteSecretNameAnnotation string = "snapshot.storage.kubernetes.io/deletion-secret-name"
   402  
   403  		// CSISnapshotterDeleteSecretNamespaceAnnotation is the annotation key for the CSI snapshotter delete secret namespace in VolumeSnapshotClass.parameters
   404  		CSISnapshotterDeleteSecretNamespaceAnnotation string = "snapshot.storage.kubernetes.io/deletion-secret-namespace"
   405  	)
   406  
   407  	annotationsObj, ok := annotations.(map[string]interface{})
   408  	if !ok {
   409  		framework.Failf("failed to get annotations from annotations object")
   410  	}
   411  
   412  	if annDeletionSecretName, ok = annotationsObj[CSISnapshotterDeleteSecretNameAnnotation].(string); !ok {
   413  		framework.Failf("unable to get secret annotation name")
   414  	}
   415  	if annDeletionSecretNamespace, ok = annotationsObj[CSISnapshotterDeleteSecretNamespaceAnnotation].(string); !ok {
   416  		framework.Failf("unable to get secret annotation namespace")
   417  	}
   418  
   419  	// verify if secrets exists
   420  	if _, err = cs.CoreV1().Secrets(annDeletionSecretNamespace).Get(context.TODO(), annDeletionSecretName, metav1.GetOptions{}); err != nil {
   421  		framework.Failf("unable to get test secret %s: %v", annDeletionSecretName, err)
   422  	}
   423  
   424  	return err
   425  }
   426  
   427  func deleteSnapshot(cs clientset.Interface, config *storageframework.PerTestConfig, snapshot *unstructured.Unstructured) {
   428  	// delete the given snapshot
   429  	dc := config.Framework.DynamicClient
   430  	err := dc.Resource(utils.SnapshotGVR).Namespace(snapshot.GetNamespace()).Delete(context.TODO(), snapshot.GetName(), metav1.DeleteOptions{})
   431  	framework.ExpectNoError(err)
   432  
   433  	// check if the snapshot is deleted
   434  	_, err = dc.Resource(utils.SnapshotGVR).Get(context.TODO(), snapshot.GetName(), metav1.GetOptions{})
   435  	gomega.Expect(err).To(gomega.MatchError(apierrors.IsNotFound, "the snapshot is not deleted"))
   436  }
   437  
   438  type snapshotMetricsTestConfig struct {
   439  	// expected values
   440  	metricName      string
   441  	metricType      string
   442  	driverName      string
   443  	operationName   string
   444  	operationStatus string
   445  	snapshotType    string
   446  	le              string
   447  }
   448  
   449  type snapshotControllerMetrics struct {
   450  	// configuration for metric
   451  	cfg            snapshotMetricsTestConfig
   452  	metricsGrabber *e2emetrics.Grabber
   453  
   454  	// results
   455  	countMetrics  map[string]float64
   456  	sumMetrics    map[string]float64
   457  	bucketMetrics map[string]float64
   458  }
   459  
   460  func newSnapshotMetricsTestConfig(metricName, metricType, driverName, operationName, operationStatus, le string, pattern storageframework.TestPattern) snapshotMetricsTestConfig {
   461  	var snapshotType string
   462  	switch pattern.SnapshotType {
   463  	case storageframework.DynamicCreatedSnapshot:
   464  		snapshotType = "dynamic"
   465  
   466  	case storageframework.PreprovisionedCreatedSnapshot:
   467  		snapshotType = "pre-provisioned"
   468  
   469  	default:
   470  		framework.Failf("invalid snapshotType: %v", pattern.SnapshotType)
   471  	}
   472  
   473  	return snapshotMetricsTestConfig{
   474  		metricName:      metricName,
   475  		metricType:      metricType,
   476  		driverName:      driverName,
   477  		operationName:   operationName,
   478  		operationStatus: operationStatus,
   479  		snapshotType:    snapshotType,
   480  		le:              le,
   481  	}
   482  }
   483  
   484  func newSnapshotControllerMetrics(cfg snapshotMetricsTestConfig, metricsGrabber *e2emetrics.Grabber) *snapshotControllerMetrics {
   485  	return &snapshotControllerMetrics{
   486  		cfg:            cfg,
   487  		metricsGrabber: metricsGrabber,
   488  
   489  		countMetrics:  make(map[string]float64),
   490  		sumMetrics:    make(map[string]float64),
   491  		bucketMetrics: make(map[string]float64),
   492  	}
   493  }
   494  
   495  func (scm *snapshotControllerMetrics) waitForSnapshotControllerMetric(ctx context.Context, expectedValue float64, timeout time.Duration) {
   496  	metricKey := scm.getMetricKey()
   497  	if successful := utils.WaitUntil(10*time.Second, timeout, func() bool {
   498  		// get metric value
   499  		actualValue, err := scm.getSnapshotControllerMetricValue(ctx)
   500  		if err != nil {
   501  			return false
   502  		}
   503  
   504  		// Another operation could have finished from a previous test,
   505  		// so we check if we have at least the expected value.
   506  		if actualValue < expectedValue {
   507  			return false
   508  		}
   509  
   510  		return true
   511  	}); successful {
   512  		return
   513  	}
   514  
   515  	scm.showMetricsFailure(metricKey)
   516  	framework.Failf("Unable to get valid snapshot controller metrics after %v", timeout)
   517  }
   518  
   519  func (scm *snapshotControllerMetrics) getSnapshotControllerMetricValue(ctx context.Context) (float64, error) {
   520  	metricKey := scm.getMetricKey()
   521  
   522  	// grab and parse into readable format
   523  	err := scm.grabSnapshotControllerMetrics(ctx)
   524  	if err != nil {
   525  		return 0, err
   526  	}
   527  
   528  	metrics := scm.getMetricsTable()
   529  	actual, ok := metrics[metricKey]
   530  	if !ok {
   531  		return 0, fmt.Errorf("did not find metric for key %s", metricKey)
   532  	}
   533  
   534  	return actual, nil
   535  }
   536  
   537  func (scm *snapshotControllerMetrics) getMetricsTable() map[string]float64 {
   538  	var metrics map[string]float64
   539  	switch scm.cfg.metricType {
   540  	case "count":
   541  		metrics = scm.countMetrics
   542  
   543  	case "sum":
   544  		metrics = scm.sumMetrics
   545  
   546  	case "bucket":
   547  		metrics = scm.bucketMetrics
   548  	}
   549  
   550  	return metrics
   551  }
   552  
   553  func (scm *snapshotControllerMetrics) showMetricsFailure(metricKey string) {
   554  	framework.Logf("failed to find metric key %s inside of the following metrics:", metricKey)
   555  
   556  	metrics := scm.getMetricsTable()
   557  	for k, v := range metrics {
   558  		framework.Logf("%s: %v", k, v)
   559  	}
   560  }
   561  
   562  func (scm *snapshotControllerMetrics) grabSnapshotControllerMetrics(ctx context.Context) error {
   563  	// pull all metrics
   564  	metrics, err := scm.metricsGrabber.GrabFromSnapshotController(ctx, framework.TestContext.SnapshotControllerPodName, framework.TestContext.SnapshotControllerHTTPPort)
   565  	if err != nil {
   566  		return err
   567  	}
   568  
   569  	for method, samples := range metrics {
   570  
   571  		for _, sample := range samples {
   572  			operationName := string(sample.Metric["operation_name"])
   573  			driverName := string(sample.Metric["driver_name"])
   574  			operationStatus := string(sample.Metric["operation_status"])
   575  			snapshotType := string(sample.Metric["snapshot_type"])
   576  			le := string(sample.Metric["le"])
   577  			key := snapshotMetricKey(scm.cfg.metricName, driverName, operationName, operationStatus, snapshotType, le)
   578  
   579  			switch method {
   580  			case "snapshot_controller_operation_total_seconds_count":
   581  				for _, sample := range samples {
   582  					scm.countMetrics[key] = float64(sample.Value)
   583  				}
   584  
   585  			case "snapshot_controller_operation_total_seconds_sum":
   586  				for _, sample := range samples {
   587  					scm.sumMetrics[key] = float64(sample.Value)
   588  				}
   589  
   590  			case "snapshot_controller_operation_total_seconds_bucket":
   591  				for _, sample := range samples {
   592  					scm.bucketMetrics[key] = float64(sample.Value)
   593  				}
   594  			}
   595  		}
   596  	}
   597  
   598  	return nil
   599  }
   600  
   601  func (scm *snapshotControllerMetrics) getMetricKey() string {
   602  	return snapshotMetricKey(scm.cfg.metricName, scm.cfg.driverName, scm.cfg.operationName, scm.cfg.operationStatus, scm.cfg.snapshotType, scm.cfg.le)
   603  }
   604  
   605  func snapshotMetricKey(metricName, driverName, operationName, operationStatus, snapshotType, le string) string {
   606  	key := driverName
   607  
   608  	// build key for shorthand metrics storage
   609  	for _, s := range []string{metricName, operationName, operationStatus, snapshotType, le} {
   610  		if s != "" {
   611  			key = fmt.Sprintf("%s_%s", key, s)
   612  		}
   613  	}
   614  
   615  	return key
   616  }