k8s.io/kubernetes@v1.29.3/test/e2e/storage/regional_pd.go (about) 1 /* 2 Copyright 2016 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 storage 18 19 import ( 20 "context" 21 22 "github.com/onsi/ginkgo/v2" 23 "github.com/onsi/gomega" 24 25 "fmt" 26 "strings" 27 "time" 28 29 "encoding/json" 30 31 appsv1 "k8s.io/api/apps/v1" 32 v1 "k8s.io/api/core/v1" 33 storagev1 "k8s.io/api/storage/v1" 34 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 35 "k8s.io/apimachinery/pkg/labels" 36 "k8s.io/apimachinery/pkg/types" 37 "k8s.io/apimachinery/pkg/util/sets" 38 "k8s.io/apimachinery/pkg/util/strategicpatch" 39 "k8s.io/apimachinery/pkg/util/wait" 40 clientset "k8s.io/client-go/kubernetes" 41 volumehelpers "k8s.io/cloud-provider/volume/helpers" 42 podutil "k8s.io/kubernetes/pkg/api/v1/pod" 43 "k8s.io/kubernetes/test/e2e/framework" 44 e2enode "k8s.io/kubernetes/test/e2e/framework/node" 45 e2epod "k8s.io/kubernetes/test/e2e/framework/pod" 46 e2epv "k8s.io/kubernetes/test/e2e/framework/pv" 47 e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper" 48 "k8s.io/kubernetes/test/e2e/storage/testsuites" 49 "k8s.io/kubernetes/test/e2e/storage/utils" 50 imageutils "k8s.io/kubernetes/test/utils/image" 51 admissionapi "k8s.io/pod-security-admission/api" 52 ) 53 54 const ( 55 pvDeletionTimeout = 3 * time.Minute 56 statefulSetReadyTimeout = 3 * time.Minute 57 taintKeyPrefix = "zoneTaint_" 58 repdMinSize = "200Gi" 59 pvcName = "regional-pd-vol" 60 ) 61 62 var _ = utils.SIGDescribe("Regional PD", func() { 63 f := framework.NewDefaultFramework("regional-pd") 64 f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged 65 66 // filled in BeforeEach 67 var c clientset.Interface 68 var ns string 69 70 ginkgo.BeforeEach(func(ctx context.Context) { 71 c = f.ClientSet 72 ns = f.Namespace.Name 73 74 e2eskipper.SkipUnlessProviderIs("gce", "gke") 75 e2eskipper.SkipUnlessMultizone(ctx, c) 76 }) 77 78 ginkgo.Describe("RegionalPD", func() { 79 f.It("should provision storage", f.WithSlow(), func(ctx context.Context) { 80 testVolumeProvisioning(ctx, c, f.Timeouts, ns) 81 }) 82 83 f.It("should provision storage with delayed binding", f.WithSlow(), func(ctx context.Context) { 84 testRegionalDelayedBinding(ctx, c, ns, 1 /* pvcCount */) 85 testRegionalDelayedBinding(ctx, c, ns, 3 /* pvcCount */) 86 }) 87 88 f.It("should provision storage in the allowedTopologies", f.WithSlow(), func(ctx context.Context) { 89 testRegionalAllowedTopologies(ctx, c, ns) 90 }) 91 92 f.It("should provision storage in the allowedTopologies with delayed binding", f.WithSlow(), func(ctx context.Context) { 93 testRegionalAllowedTopologiesWithDelayedBinding(ctx, c, ns, 1 /* pvcCount */) 94 testRegionalAllowedTopologiesWithDelayedBinding(ctx, c, ns, 3 /* pvcCount */) 95 }) 96 97 f.It("should failover to a different zone when all nodes in one zone become unreachable", f.WithSlow(), f.WithDisruptive(), func(ctx context.Context) { 98 testZonalFailover(ctx, c, ns) 99 }) 100 }) 101 }) 102 103 func testVolumeProvisioning(ctx context.Context, c clientset.Interface, t *framework.TimeoutContext, ns string) { 104 cloudZones := getTwoRandomZones(ctx, c) 105 106 // This test checks that dynamic provisioning can provision a volume 107 // that can be used to persist data among pods. 108 tests := []testsuites.StorageClassTest{ 109 { 110 Name: "HDD Regional PD on GCE/GKE", 111 CloudProviders: []string{"gce", "gke"}, 112 Provisioner: "kubernetes.io/gce-pd", 113 Timeouts: framework.NewTimeoutContext(), 114 Parameters: map[string]string{ 115 "type": "pd-standard", 116 "zones": strings.Join(cloudZones, ","), 117 "replication-type": "regional-pd", 118 }, 119 ClaimSize: repdMinSize, 120 ExpectedSize: repdMinSize, 121 PvCheck: func(ctx context.Context, claim *v1.PersistentVolumeClaim) { 122 volume := testsuites.PVWriteReadSingleNodeCheck(ctx, c, t, claim, e2epod.NodeSelection{}) 123 gomega.Expect(volume).NotTo(gomega.BeNil()) 124 125 err := checkGCEPD(volume, "pd-standard") 126 framework.ExpectNoError(err, "checkGCEPD") 127 err = verifyZonesInPV(volume, sets.NewString(cloudZones...), true /* match */) 128 framework.ExpectNoError(err, "verifyZonesInPV") 129 130 }, 131 }, 132 { 133 Name: "HDD Regional PD with auto zone selection on GCE/GKE", 134 CloudProviders: []string{"gce", "gke"}, 135 Provisioner: "kubernetes.io/gce-pd", 136 Timeouts: framework.NewTimeoutContext(), 137 Parameters: map[string]string{ 138 "type": "pd-standard", 139 "replication-type": "regional-pd", 140 }, 141 ClaimSize: repdMinSize, 142 ExpectedSize: repdMinSize, 143 PvCheck: func(ctx context.Context, claim *v1.PersistentVolumeClaim) { 144 volume := testsuites.PVWriteReadSingleNodeCheck(ctx, c, t, claim, e2epod.NodeSelection{}) 145 gomega.Expect(volume).NotTo(gomega.BeNil()) 146 147 err := checkGCEPD(volume, "pd-standard") 148 framework.ExpectNoError(err, "checkGCEPD") 149 zones, err := e2enode.GetClusterZones(ctx, c) 150 framework.ExpectNoError(err, "GetClusterZones") 151 err = verifyZonesInPV(volume, zones, false /* match */) 152 framework.ExpectNoError(err, "verifyZonesInPV") 153 }, 154 }, 155 } 156 157 for _, test := range tests { 158 test.Client = c 159 computedStorageClass := testsuites.SetupStorageClass(ctx, test.Client, newStorageClass(test, ns, "" /* suffix */)) 160 test.Class = computedStorageClass 161 test.Claim = e2epv.MakePersistentVolumeClaim(e2epv.PersistentVolumeClaimConfig{ 162 ClaimSize: test.ClaimSize, 163 StorageClassName: &(test.Class.Name), 164 VolumeMode: &test.VolumeMode, 165 }, ns) 166 167 test.TestDynamicProvisioning(ctx) 168 } 169 } 170 171 func testZonalFailover(ctx context.Context, c clientset.Interface, ns string) { 172 cloudZones := getTwoRandomZones(ctx, c) 173 testSpec := testsuites.StorageClassTest{ 174 Name: "Regional PD Failover on GCE/GKE", 175 CloudProviders: []string{"gce", "gke"}, 176 Timeouts: framework.NewTimeoutContext(), 177 Provisioner: "kubernetes.io/gce-pd", 178 Parameters: map[string]string{ 179 "type": "pd-standard", 180 "zones": strings.Join(cloudZones, ","), 181 "replication-type": "regional-pd", 182 }, 183 ClaimSize: repdMinSize, 184 ExpectedSize: repdMinSize, 185 } 186 class := newStorageClass(testSpec, ns, "" /* suffix */) 187 claimTemplate := e2epv.MakePersistentVolumeClaim(e2epv.PersistentVolumeClaimConfig{ 188 NamePrefix: pvcName, 189 ClaimSize: testSpec.ClaimSize, 190 StorageClassName: &(class.Name), 191 VolumeMode: &testSpec.VolumeMode, 192 }, ns) 193 statefulSet, service, regionalPDLabels := newStatefulSet(claimTemplate, ns) 194 195 ginkgo.By("creating a StorageClass " + class.Name) 196 _, err := c.StorageV1().StorageClasses().Create(ctx, class, metav1.CreateOptions{}) 197 framework.ExpectNoError(err) 198 defer func() { 199 framework.Logf("deleting storage class %s", class.Name) 200 framework.ExpectNoError(c.StorageV1().StorageClasses().Delete(ctx, class.Name, metav1.DeleteOptions{}), 201 "Error deleting StorageClass %s", class.Name) 202 }() 203 204 ginkgo.By("creating a StatefulSet") 205 _, err = c.CoreV1().Services(ns).Create(ctx, service, metav1.CreateOptions{}) 206 framework.ExpectNoError(err) 207 _, err = c.AppsV1().StatefulSets(ns).Create(ctx, statefulSet, metav1.CreateOptions{}) 208 framework.ExpectNoError(err) 209 210 ginkgo.DeferCleanup(func(ctx context.Context) { 211 framework.Logf("deleting statefulset%q/%q", statefulSet.Namespace, statefulSet.Name) 212 // typically this claim has already been deleted 213 framework.ExpectNoError(c.AppsV1().StatefulSets(ns).Delete(ctx, statefulSet.Name, metav1.DeleteOptions{}), 214 "Error deleting StatefulSet %s", statefulSet.Name) 215 216 framework.Logf("deleting claims in namespace %s", ns) 217 pvc := getPVC(ctx, c, ns, regionalPDLabels) 218 framework.ExpectNoError(c.CoreV1().PersistentVolumeClaims(pvc.Namespace).Delete(ctx, pvc.Name, metav1.DeleteOptions{}), 219 "Error deleting claim %s.", pvc.Name) 220 if pvc.Spec.VolumeName != "" { 221 err = e2epv.WaitForPersistentVolumeDeleted(ctx, c, pvc.Spec.VolumeName, framework.Poll, pvDeletionTimeout) 222 if err != nil { 223 framework.Logf("WARNING: PV %s is not yet deleted, and subsequent tests may be affected.", pvc.Spec.VolumeName) 224 } 225 } 226 }) 227 228 err = waitForStatefulSetReplicasReady(ctx, statefulSet.Name, ns, c, framework.Poll, statefulSetReadyTimeout) 229 if err != nil { 230 pod := getPod(ctx, c, ns, regionalPDLabels) 231 if !podutil.IsPodReadyConditionTrue(pod.Status) { 232 framework.Failf("The statefulset pod %s was expected to be ready, instead has the following conditions: %v", pod.Name, pod.Status.Conditions) 233 } 234 framework.ExpectNoError(err) 235 } 236 237 pvc := getPVC(ctx, c, ns, regionalPDLabels) 238 239 ginkgo.By("getting zone information from pod") 240 pod := getPod(ctx, c, ns, regionalPDLabels) 241 nodeName := pod.Spec.NodeName 242 node, err := c.CoreV1().Nodes().Get(ctx, nodeName, metav1.GetOptions{}) 243 framework.ExpectNoError(err) 244 podZone := node.Labels[v1.LabelTopologyZone] 245 246 ginkgo.By("tainting nodes in the zone the pod is scheduled in") 247 selector := labels.SelectorFromSet(labels.Set(map[string]string{v1.LabelTopologyZone: podZone})) 248 nodesInZone, err := c.CoreV1().Nodes().List(ctx, metav1.ListOptions{LabelSelector: selector.String()}) 249 framework.ExpectNoError(err) 250 addTaint(ctx, c, ns, nodesInZone.Items, podZone) 251 252 ginkgo.By("deleting StatefulSet pod") 253 err = c.CoreV1().Pods(ns).Delete(ctx, pod.Name, metav1.DeleteOptions{}) 254 255 // Verify the pod is scheduled in the other zone. 256 ginkgo.By("verifying the pod is scheduled in a different zone.") 257 var otherZone string 258 if cloudZones[0] == podZone { 259 otherZone = cloudZones[1] 260 } else { 261 otherZone = cloudZones[0] 262 } 263 waitErr := wait.PollUntilContextTimeout(ctx, framework.Poll, statefulSetReadyTimeout, true, func(ctx context.Context) (bool, error) { 264 framework.Logf("Checking whether new pod is scheduled in zone %q", otherZone) 265 pod := getPod(ctx, c, ns, regionalPDLabels) 266 node, err := c.CoreV1().Nodes().Get(ctx, pod.Spec.NodeName, metav1.GetOptions{}) 267 if err != nil { 268 return false, nil 269 } 270 newPodZone := node.Labels[v1.LabelTopologyZone] 271 return newPodZone == otherZone, nil 272 }) 273 framework.ExpectNoError(waitErr, "Error waiting for pod to be scheduled in a different zone (%q): %v", otherZone, err) 274 275 err = waitForStatefulSetReplicasReady(ctx, statefulSet.Name, ns, c, 3*time.Second, framework.RestartPodReadyAgainTimeout) 276 if err != nil { 277 pod := getPod(ctx, c, ns, regionalPDLabels) 278 if !podutil.IsPodReadyConditionTrue(pod.Status) { 279 framework.Failf("The statefulset pod %s was expected to be ready, instead has the following conditions: %v", pod.Name, pod.Status.Conditions) 280 } 281 framework.ExpectNoError(err) 282 } 283 284 ginkgo.By("verifying the same PVC is used by the new pod") 285 gomega.Expect(getPVC(ctx, c, ns, regionalPDLabels).Name).To(gomega.Equal(pvc.Name), "The same PVC should be used after failover.") 286 287 ginkgo.By("verifying the container output has 2 lines, indicating the pod has been created twice using the same regional PD.") 288 logs, err := e2epod.GetPodLogs(ctx, c, ns, pod.Name, "") 289 framework.ExpectNoError(err, 290 "Error getting logs from pod %s in namespace %s", pod.Name, ns) 291 lineCount := len(strings.Split(strings.TrimSpace(logs), "\n")) 292 expectedLineCount := 2 293 gomega.Expect(lineCount).To(gomega.Equal(expectedLineCount), "Line count of the written file should be %d.", expectedLineCount) 294 295 } 296 297 func addTaint(ctx context.Context, c clientset.Interface, ns string, nodes []v1.Node, podZone string) { 298 for _, node := range nodes { 299 oldData, err := json.Marshal(node) 300 framework.ExpectNoError(err) 301 302 node.Spec.Taints = append(node.Spec.Taints, v1.Taint{ 303 Key: taintKeyPrefix + ns, 304 Value: podZone, 305 Effect: v1.TaintEffectNoSchedule, 306 }) 307 308 newData, err := json.Marshal(node) 309 framework.ExpectNoError(err) 310 311 patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, v1.Node{}) 312 framework.ExpectNoError(err) 313 314 reversePatchBytes, err := strategicpatch.CreateTwoWayMergePatch(newData, oldData, v1.Node{}) 315 framework.ExpectNoError(err) 316 317 _, err = c.CoreV1().Nodes().Patch(ctx, node.Name, types.StrategicMergePatchType, patchBytes, metav1.PatchOptions{}) 318 framework.ExpectNoError(err) 319 320 nodeName := node.Name 321 ginkgo.DeferCleanup(func(ctx context.Context) { 322 framework.Logf("removing taint for node %q", nodeName) 323 _, err := c.CoreV1().Nodes().Patch(ctx, nodeName, types.StrategicMergePatchType, reversePatchBytes, metav1.PatchOptions{}) 324 framework.ExpectNoError(err) 325 }) 326 } 327 } 328 329 func testRegionalDelayedBinding(ctx context.Context, c clientset.Interface, ns string, pvcCount int) { 330 test := testsuites.StorageClassTest{ 331 Client: c, 332 Name: "Regional PD storage class with waitForFirstConsumer test on GCE", 333 Provisioner: "kubernetes.io/gce-pd", 334 Timeouts: framework.NewTimeoutContext(), 335 Parameters: map[string]string{ 336 "type": "pd-standard", 337 "replication-type": "regional-pd", 338 }, 339 ClaimSize: repdMinSize, 340 DelayBinding: true, 341 } 342 343 suffix := "delayed-regional" 344 345 test.Class = testsuites.SetupStorageClass(ctx, test.Client, newStorageClass(test, ns, suffix)) 346 var claims []*v1.PersistentVolumeClaim 347 for i := 0; i < pvcCount; i++ { 348 claim := e2epv.MakePersistentVolumeClaim(e2epv.PersistentVolumeClaimConfig{ 349 ClaimSize: test.ClaimSize, 350 StorageClassName: &(test.Class.Name), 351 VolumeMode: &test.VolumeMode, 352 }, ns) 353 claims = append(claims, claim) 354 } 355 pvs, node := test.TestBindingWaitForFirstConsumerMultiPVC(ctx, claims, nil /* node selector */, false /* expect unschedulable */) 356 if node == nil { 357 framework.Failf("unexpected nil node found") 358 } 359 zone, ok := node.Labels[v1.LabelTopologyZone] 360 if !ok { 361 framework.Failf("label %s not found on Node", v1.LabelTopologyZone) 362 } 363 for _, pv := range pvs { 364 checkZoneFromLabelAndAffinity(pv, zone, false) 365 } 366 } 367 368 func testRegionalAllowedTopologies(ctx context.Context, c clientset.Interface, ns string) { 369 test := testsuites.StorageClassTest{ 370 Name: "Regional PD storage class with allowedTopologies test on GCE", 371 Provisioner: "kubernetes.io/gce-pd", 372 Timeouts: framework.NewTimeoutContext(), 373 Parameters: map[string]string{ 374 "type": "pd-standard", 375 "replication-type": "regional-pd", 376 }, 377 ClaimSize: repdMinSize, 378 ExpectedSize: repdMinSize, 379 } 380 381 suffix := "topo-regional" 382 test.Client = c 383 test.Class = testsuites.SetupStorageClass(ctx, test.Client, newStorageClass(test, ns, suffix)) 384 zones := getTwoRandomZones(ctx, c) 385 addAllowedTopologiesToStorageClass(c, test.Class, zones) 386 test.Claim = e2epv.MakePersistentVolumeClaim(e2epv.PersistentVolumeClaimConfig{ 387 NamePrefix: pvcName, 388 ClaimSize: test.ClaimSize, 389 StorageClassName: &(test.Class.Name), 390 VolumeMode: &test.VolumeMode, 391 }, ns) 392 393 pv := test.TestDynamicProvisioning(ctx) 394 checkZonesFromLabelAndAffinity(pv, sets.NewString(zones...), true) 395 } 396 397 func testRegionalAllowedTopologiesWithDelayedBinding(ctx context.Context, c clientset.Interface, ns string, pvcCount int) { 398 test := testsuites.StorageClassTest{ 399 Client: c, 400 Timeouts: framework.NewTimeoutContext(), 401 Name: "Regional PD storage class with allowedTopologies and waitForFirstConsumer test on GCE", 402 Provisioner: "kubernetes.io/gce-pd", 403 Parameters: map[string]string{ 404 "type": "pd-standard", 405 "replication-type": "regional-pd", 406 }, 407 ClaimSize: repdMinSize, 408 DelayBinding: true, 409 } 410 411 suffix := "topo-delayed-regional" 412 test.Class = testsuites.SetupStorageClass(ctx, test.Client, newStorageClass(test, ns, suffix)) 413 topoZones := getTwoRandomZones(ctx, c) 414 addAllowedTopologiesToStorageClass(c, test.Class, topoZones) 415 var claims []*v1.PersistentVolumeClaim 416 for i := 0; i < pvcCount; i++ { 417 claim := e2epv.MakePersistentVolumeClaim(e2epv.PersistentVolumeClaimConfig{ 418 ClaimSize: test.ClaimSize, 419 StorageClassName: &(test.Class.Name), 420 VolumeMode: &test.VolumeMode, 421 }, ns) 422 claims = append(claims, claim) 423 } 424 pvs, node := test.TestBindingWaitForFirstConsumerMultiPVC(ctx, claims, nil /* node selector */, false /* expect unschedulable */) 425 if node == nil { 426 framework.Failf("unexpected nil node found") 427 } 428 nodeZone, ok := node.Labels[v1.LabelTopologyZone] 429 if !ok { 430 framework.Failf("label %s not found on Node", v1.LabelTopologyZone) 431 } 432 zoneFound := false 433 for _, zone := range topoZones { 434 if zone == nodeZone { 435 zoneFound = true 436 break 437 } 438 } 439 if !zoneFound { 440 framework.Failf("zones specified in AllowedTopologies: %v does not contain zone of node where PV got provisioned: %s", topoZones, nodeZone) 441 } 442 for _, pv := range pvs { 443 checkZonesFromLabelAndAffinity(pv, sets.NewString(topoZones...), true) 444 } 445 } 446 447 func getPVC(ctx context.Context, c clientset.Interface, ns string, pvcLabels map[string]string) *v1.PersistentVolumeClaim { 448 selector := labels.Set(pvcLabels).AsSelector() 449 options := metav1.ListOptions{LabelSelector: selector.String()} 450 pvcList, err := c.CoreV1().PersistentVolumeClaims(ns).List(ctx, options) 451 framework.ExpectNoError(err) 452 gomega.Expect(pvcList.Items).To(gomega.HaveLen(1), "There should be exactly 1 PVC matched.") 453 454 return &pvcList.Items[0] 455 } 456 457 func getPod(ctx context.Context, c clientset.Interface, ns string, podLabels map[string]string) *v1.Pod { 458 selector := labels.Set(podLabels).AsSelector() 459 options := metav1.ListOptions{LabelSelector: selector.String()} 460 podList, err := c.CoreV1().Pods(ns).List(ctx, options) 461 framework.ExpectNoError(err) 462 gomega.Expect(podList.Items).To(gomega.HaveLen(1), "There should be exactly 1 pod matched.") 463 464 return &podList.Items[0] 465 } 466 467 func addAllowedTopologiesToStorageClass(c clientset.Interface, sc *storagev1.StorageClass, zones []string) { 468 term := v1.TopologySelectorTerm{ 469 MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{ 470 { 471 Key: v1.LabelTopologyZone, 472 Values: zones, 473 }, 474 }, 475 } 476 sc.AllowedTopologies = append(sc.AllowedTopologies, term) 477 } 478 479 // Generates the spec of a StatefulSet with 1 replica that mounts a Regional PD. 480 func newStatefulSet(claimTemplate *v1.PersistentVolumeClaim, ns string) (sts *appsv1.StatefulSet, svc *v1.Service, labels map[string]string) { 481 var replicas int32 = 1 482 labels = map[string]string{"app": "regional-pd-workload"} 483 484 svc = &v1.Service{ 485 ObjectMeta: metav1.ObjectMeta{ 486 Name: "regional-pd-service", 487 Namespace: ns, 488 Labels: labels, 489 }, 490 Spec: v1.ServiceSpec{ 491 Ports: []v1.ServicePort{{ 492 Port: 80, 493 Name: "web", 494 }}, 495 ClusterIP: v1.ClusterIPNone, 496 Selector: labels, 497 }, 498 } 499 500 sts = &appsv1.StatefulSet{ 501 ObjectMeta: metav1.ObjectMeta{ 502 Name: "regional-pd-sts", 503 Namespace: ns, 504 }, 505 Spec: appsv1.StatefulSetSpec{ 506 Selector: &metav1.LabelSelector{ 507 MatchLabels: labels, 508 }, 509 ServiceName: svc.Name, 510 Replicas: &replicas, 511 Template: *newPodTemplate(labels), 512 VolumeClaimTemplates: []v1.PersistentVolumeClaim{*claimTemplate}, 513 }, 514 } 515 516 return 517 } 518 519 func newPodTemplate(labels map[string]string) *v1.PodTemplateSpec { 520 return &v1.PodTemplateSpec{ 521 ObjectMeta: metav1.ObjectMeta{ 522 Labels: labels, 523 }, 524 Spec: v1.PodSpec{ 525 Containers: []v1.Container{ 526 // This container writes its pod name to a file in the Regional PD 527 // and prints the entire file to stdout. 528 { 529 Name: "busybox", 530 Image: imageutils.GetE2EImage(imageutils.BusyBox), 531 Command: []string{"sh", "-c"}, 532 Args: []string{ 533 "echo ${POD_NAME} >> /mnt/data/regional-pd/pods.txt;" + 534 "cat /mnt/data/regional-pd/pods.txt;" + 535 "sleep 3600;", 536 }, 537 Env: []v1.EnvVar{{ 538 Name: "POD_NAME", 539 ValueFrom: &v1.EnvVarSource{ 540 FieldRef: &v1.ObjectFieldSelector{ 541 FieldPath: "metadata.name", 542 }, 543 }, 544 }}, 545 Ports: []v1.ContainerPort{{ 546 ContainerPort: 80, 547 Name: "web", 548 }}, 549 VolumeMounts: []v1.VolumeMount{{ 550 Name: pvcName, 551 MountPath: "/mnt/data/regional-pd", 552 }}, 553 }, 554 }, 555 }, 556 } 557 } 558 559 func getTwoRandomZones(ctx context.Context, c clientset.Interface) []string { 560 zones, err := e2enode.GetClusterZones(ctx, c) 561 framework.ExpectNoError(err) 562 gomega.Expect(zones.Len()).To(gomega.BeNumerically(">=", 2), 563 "The test should only be run in multizone clusters.") 564 565 zone1, _ := zones.PopAny() 566 zone2, _ := zones.PopAny() 567 return []string{zone1, zone2} 568 } 569 570 // If match is true, check if zones in PV exactly match zones given. 571 // Otherwise, check whether zones in PV is superset of zones given. 572 func verifyZonesInPV(volume *v1.PersistentVolume, zones sets.String, match bool) error { 573 pvZones, err := volumehelpers.LabelZonesToSet(volume.Labels[v1.LabelTopologyZone]) 574 if err != nil { 575 return err 576 } 577 578 if match && zones.Equal(pvZones) || !match && zones.IsSuperset(pvZones) { 579 return nil 580 } 581 582 return fmt.Errorf("Zones in StorageClass are %v, but zones in PV are %v", zones, pvZones) 583 584 } 585 586 func checkZoneFromLabelAndAffinity(pv *v1.PersistentVolume, zone string, matchZone bool) { 587 checkZonesFromLabelAndAffinity(pv, sets.NewString(zone), matchZone) 588 } 589 590 // checkZoneLabelAndAffinity checks the LabelTopologyZone label of PV and terms 591 // with key LabelTopologyZone in PV's node affinity contains zone 592 // matchZones is used to indicate if zones should match perfectly 593 func checkZonesFromLabelAndAffinity(pv *v1.PersistentVolume, zones sets.String, matchZones bool) { 594 ginkgo.By("checking PV's zone label and node affinity terms match expected zone") 595 if pv == nil { 596 framework.Failf("nil pv passed") 597 } 598 pvLabel, ok := pv.Labels[v1.LabelTopologyZone] 599 if !ok { 600 framework.Failf("label %s not found on PV", v1.LabelTopologyZone) 601 } 602 603 zonesFromLabel, err := volumehelpers.LabelZonesToSet(pvLabel) 604 if err != nil { 605 framework.Failf("unable to parse zone labels %s: %v", pvLabel, err) 606 } 607 if matchZones && !zonesFromLabel.Equal(zones) { 608 framework.Failf("value[s] of %s label for PV: %v does not match expected zone[s]: %v", v1.LabelTopologyZone, zonesFromLabel, zones) 609 } 610 if !matchZones && !zonesFromLabel.IsSuperset(zones) { 611 framework.Failf("value[s] of %s label for PV: %v does not contain expected zone[s]: %v", v1.LabelTopologyZone, zonesFromLabel, zones) 612 } 613 if pv.Spec.NodeAffinity == nil { 614 framework.Failf("node affinity not found in PV spec %v", pv.Spec) 615 } 616 if len(pv.Spec.NodeAffinity.Required.NodeSelectorTerms) == 0 { 617 framework.Failf("node selector terms not found in PV spec %v", pv.Spec) 618 } 619 620 for _, term := range pv.Spec.NodeAffinity.Required.NodeSelectorTerms { 621 keyFound := false 622 for _, r := range term.MatchExpressions { 623 if r.Key != v1.LabelTopologyZone { 624 continue 625 } 626 keyFound = true 627 zonesFromNodeAffinity := sets.NewString(r.Values...) 628 if matchZones && !zonesFromNodeAffinity.Equal(zones) { 629 framework.Failf("zones from NodeAffinity of PV: %v does not equal expected zone[s]: %v", zonesFromNodeAffinity, zones) 630 } 631 if !matchZones && !zonesFromNodeAffinity.IsSuperset(zones) { 632 framework.Failf("zones from NodeAffinity of PV: %v does not contain expected zone[s]: %v", zonesFromNodeAffinity, zones) 633 } 634 break 635 } 636 if !keyFound { 637 framework.Failf("label %s not found in term %v", v1.LabelTopologyZone, term) 638 } 639 } 640 } 641 642 // waitForStatefulSetReplicasReady waits for all replicas of a StatefulSet to become ready or until timeout occurs, whichever comes first. 643 func waitForStatefulSetReplicasReady(ctx context.Context, statefulSetName, ns string, c clientset.Interface, Poll, timeout time.Duration) error { 644 framework.Logf("Waiting up to %v for StatefulSet %s to have all replicas ready", timeout, statefulSetName) 645 for start := time.Now(); time.Since(start) < timeout; time.Sleep(Poll) { 646 sts, err := c.AppsV1().StatefulSets(ns).Get(ctx, statefulSetName, metav1.GetOptions{}) 647 if err != nil { 648 framework.Logf("Get StatefulSet %s failed, ignoring for %v: %v", statefulSetName, Poll, err) 649 continue 650 } 651 if sts.Status.ReadyReplicas == *sts.Spec.Replicas { 652 framework.Logf("All %d replicas of StatefulSet %s are ready. (%v)", sts.Status.ReadyReplicas, statefulSetName, time.Since(start)) 653 return nil 654 } 655 framework.Logf("StatefulSet %s found but there are %d ready replicas and %d total replicas.", statefulSetName, sts.Status.ReadyReplicas, *sts.Spec.Replicas) 656 } 657 return fmt.Errorf("StatefulSet %s still has unready pods within %v", statefulSetName, timeout) 658 }