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 }