k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/e2e/storage/volume_attachment.go (about) 1 /* 2 Copyright 2024 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 "fmt" 22 "math/rand" 23 "time" 24 25 storagev1 "k8s.io/api/storage/v1" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/labels" 28 "k8s.io/apimachinery/pkg/types" 29 utilrand "k8s.io/apimachinery/pkg/util/rand" 30 "k8s.io/client-go/util/retry" 31 "k8s.io/kubernetes/test/e2e/framework" 32 "k8s.io/kubernetes/test/e2e/storage/utils" 33 34 "github.com/onsi/ginkgo/v2" 35 "github.com/onsi/gomega" 36 ) 37 38 var _ = utils.SIGDescribe("VolumeAttachment", func() { 39 40 f := framework.NewDefaultFramework("volumeattachment") 41 42 /* 43 Release: v1.30 44 Testname: VolumeAttachment, lifecycle 45 Description: Creating an initial VolumeAttachment MUST succeed. Reading the VolumeAttachment 46 MUST succeed with with required name retrieved. Patching a VolumeAttachment MUST 47 succeed with its new label found. Listing VolumeAttachment with a labelSelector 48 MUST succeed with a single item retrieved. Deleting a VolumeAttachment MUST succeed 49 and it MUST be confirmed. Creating a second VolumeAttachment MUST succeed. Updating 50 the second VolumentAttachment with a new label MUST succeed with its new label 51 found. Creating a third VolumeAttachment MUST succeed. Updating the third VolumentAttachment 52 with a new label MUST succeed with its new label found. Deleting both VolumeAttachments 53 via deleteCollection MUST succeed and it MUST be confirmed. 54 */ 55 ginkgo.Describe("Conformance", func() { 56 57 framework.ConformanceIt("should run through the lifecycle of a VolumeAttachment", func(ctx context.Context) { 58 59 vaClient := f.ClientSet.StorageV1().VolumeAttachments() 60 61 firstVA, vaNodeName := createVolumeAttachment(f, ctx) 62 ginkgo.By(fmt.Sprintf("Get VolumeAttachment %q on node %q", firstVA, vaNodeName)) 63 retrievedVA, err := vaClient.Get(ctx, firstVA, metav1.GetOptions{}) 64 framework.ExpectNoError(err, "failed to get VolumeAttachment %q", firstVA) 65 gomega.Expect(retrievedVA.Name).To(gomega.Equal(firstVA), "Checking that retrieved VolumeAttachment has the correct name") 66 67 ginkgo.By(fmt.Sprintf("Patch VolumeAttachment %q on node %q", firstVA, vaNodeName)) 68 payload := "{\"metadata\":{\"labels\":{\"" + retrievedVA.Name + "\":\"patched\"}}}" 69 patchedVA, err := vaClient.Patch(ctx, retrievedVA.Name, types.MergePatchType, []byte(payload), metav1.PatchOptions{}) 70 framework.ExpectNoError(err, "failed to patch PV %q", firstVA) 71 gomega.Expect(patchedVA.Labels).To(gomega.HaveKeyWithValue(patchedVA.Name, "patched"), "Checking that patched label has been applied") 72 73 patchedSelector := labels.Set{patchedVA.Name: "patched"}.AsSelector().String() 74 ginkgo.By(fmt.Sprintf("List VolumeAttachments with %q label", patchedSelector)) 75 vaList, err := vaClient.List(ctx, metav1.ListOptions{LabelSelector: patchedSelector}) 76 framework.ExpectNoError(err, "failed to list VolumeAttachments") 77 gomega.Expect(vaList.Items).To(gomega.HaveLen(1)) 78 79 ginkgo.By(fmt.Sprintf("Delete VolumeAttachment %q on node %q", firstVA, vaNodeName)) 80 err = vaClient.Delete(ctx, firstVA, metav1.DeleteOptions{}) 81 framework.ExpectNoError(err, "failed to delete VolumeAttachment %q", firstVA) 82 83 ginkgo.By(fmt.Sprintf("Confirm deletion of VolumeAttachment %q on node %q", firstVA, vaNodeName)) 84 85 type state struct { 86 VolumeAttachments []storagev1.VolumeAttachment 87 } 88 89 err = framework.Gomega().Eventually(ctx, framework.HandleRetry(func(ctx context.Context) (*state, error) { 90 vaList, err := vaClient.List(ctx, metav1.ListOptions{LabelSelector: patchedSelector}) 91 if err != nil { 92 return nil, fmt.Errorf("failed to list VolumeAttachment: %w", err) 93 } 94 return &state{ 95 VolumeAttachments: vaList.Items, 96 }, nil 97 })).WithTimeout(30 * time.Second).Should(framework.MakeMatcher(func(s *state) (func() string, error) { 98 if len(s.VolumeAttachments) == 0 { 99 return nil, nil 100 } 101 return func() string { 102 return fmt.Sprintf("Expected VolumeAttachment to be deleted, found %q", s.VolumeAttachments[0].Name) 103 }, nil 104 })) 105 framework.ExpectNoError(err, "Timeout while waiting to confirm VolumeAttachment %q deletion", firstVA) 106 107 secondVA, vaNodeName := createVolumeAttachment(f, ctx) 108 updatedLabel := map[string]string{"va-e2e": "updated"} 109 updatedSelector := labels.Set{"va-e2e": "updated"}.AsSelector().String() 110 ginkgo.By(fmt.Sprintf("Update the VolumeAttachment %q on node %q with label %q", secondVA, vaNodeName, updatedSelector)) 111 var updatedVA *storagev1.VolumeAttachment 112 113 err = retry.RetryOnConflict(retry.DefaultRetry, func() error { 114 currentVA, err := vaClient.Get(ctx, secondVA, metav1.GetOptions{}) 115 framework.ExpectNoError(err, "failed to get VolumeAttachment %q", patchedVA.Name) 116 currentVA.Labels = updatedLabel 117 updatedVA, err = vaClient.Update(ctx, currentVA, metav1.UpdateOptions{}) 118 119 return err 120 }) 121 framework.ExpectNoError(err, "failed to update VolumeAttachment %q on node %q", secondVA, vaNodeName) 122 gomega.Expect(updatedVA.Labels).To(gomega.HaveKeyWithValue("va-e2e", "updated"), "Checking that updated label has been applied") 123 124 thirdVA, vaNodeName := createVolumeAttachment(f, ctx) 125 ginkgo.By(fmt.Sprintf("Update the VolumeAttachment %q on node %q with label %q", thirdVA, vaNodeName, updatedSelector)) 126 127 err = retry.RetryOnConflict(retry.DefaultRetry, func() error { 128 currentVA, err := vaClient.Get(ctx, thirdVA, metav1.GetOptions{}) 129 framework.ExpectNoError(err, "failed to get VolumeAttachment %q", patchedVA.Name) 130 currentVA.Labels = updatedLabel 131 updatedVA, err = vaClient.Update(ctx, currentVA, metav1.UpdateOptions{}) 132 133 return err 134 }) 135 framework.ExpectNoError(err, "failed to update VolumeAttachment %q on node %q", thirdVA, vaNodeName) 136 gomega.Expect(updatedVA.Labels).To(gomega.HaveKeyWithValue("va-e2e", "updated"), "Checking that updated label has been applied") 137 138 ginkgo.By(fmt.Sprintf("DeleteCollection of VolumeAttachments with %q label", updatedSelector)) 139 err = vaClient.DeleteCollection(ctx, metav1.DeleteOptions{}, metav1.ListOptions{LabelSelector: updatedSelector}) 140 framework.ExpectNoError(err, "failed to delete VolumeAttachment collection") 141 142 ginkgo.By(fmt.Sprintf("Confirm deleteCollection of VolumeAttachments with %q label", updatedSelector)) 143 144 err = framework.Gomega().Eventually(ctx, framework.HandleRetry(func(ctx context.Context) (*state, error) { 145 vaList, err := vaClient.List(ctx, metav1.ListOptions{LabelSelector: updatedSelector}) 146 if err != nil { 147 return nil, fmt.Errorf("failed to list VolumeAttachment: %w", err) 148 } 149 return &state{ 150 VolumeAttachments: vaList.Items, 151 }, nil 152 })).WithTimeout(30 * time.Second).Should(framework.MakeMatcher(func(s *state) (func() string, error) { 153 if len(s.VolumeAttachments) == 0 { 154 return nil, nil 155 } 156 return func() string { 157 list := []string{} 158 for _, va := range s.VolumeAttachments { 159 list = append(list, va.Name) 160 } 161 162 return fmt.Sprintf("Expected VolumeAttachment(s) to be deleted, found %v", list) 163 }, nil 164 })) 165 framework.ExpectNoError(err, "Timeout while waiting to confirm deletion of all VolumeAttachments") 166 }) 167 }) 168 }) 169 170 func NewVolumeAttachment(vaName, pvName, nodeName string, status bool) *storagev1.VolumeAttachment { 171 return &storagev1.VolumeAttachment{ 172 173 ObjectMeta: metav1.ObjectMeta{ 174 UID: types.UID(vaName), 175 Name: vaName, 176 }, 177 Spec: storagev1.VolumeAttachmentSpec{ 178 Attacher: "e2e-test.storage.k8s.io", 179 NodeName: nodeName, 180 Source: storagev1.VolumeAttachmentSource{ 181 PersistentVolumeName: &pvName, 182 }, 183 }, 184 Status: storagev1.VolumeAttachmentStatus{ 185 Attached: status, 186 }, 187 } 188 } 189 190 func createVolumeAttachment(f *framework.Framework, ctx context.Context) (string, string) { 191 192 randUID := "e2e-" + utilrand.String(5) 193 vaName := "va-" + randUID 194 pvName := "pv-" + randUID 195 196 nodes, err := f.ClientSet.CoreV1().Nodes().List(ctx, metav1.ListOptions{}) 197 framework.ExpectNoError(err, "failed to list nodes") 198 randNode := rand.Intn(len(nodes.Items)) 199 vaNodeName := nodes.Items[randNode].Name 200 vaAttachStatus := false 201 202 ginkgo.By(fmt.Sprintf("Create VolumeAttachment %q on node %q", vaName, vaNodeName)) 203 va := NewVolumeAttachment(vaName, pvName, vaNodeName, vaAttachStatus) 204 205 createdVA, err := f.ClientSet.StorageV1().VolumeAttachments().Create(ctx, va, metav1.CreateOptions{}) 206 framework.ExpectNoError(err, "failed to create VolumeAttachment %q", vaName) 207 gomega.Expect(createdVA.Name).To(gomega.Equal(vaName), "Checking that the created VolumeAttachment has the correct name") 208 209 return createdVA.Name, vaNodeName 210 }