k8s.io/kubernetes@v1.29.3/test/e2e/storage/csistoragecapacity.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 storage 18 19 import ( 20 "context" 21 "time" 22 23 storagev1 "k8s.io/api/storage/v1" 24 apierrors "k8s.io/apimachinery/pkg/api/errors" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 types "k8s.io/apimachinery/pkg/types" 27 "k8s.io/apimachinery/pkg/util/wait" 28 "k8s.io/apimachinery/pkg/watch" 29 "k8s.io/kubernetes/test/e2e/framework" 30 "k8s.io/kubernetes/test/e2e/storage/utils" 31 admissionapi "k8s.io/pod-security-admission/api" 32 33 "github.com/onsi/ginkgo/v2" 34 "github.com/onsi/gomega" 35 ) 36 37 var _ = utils.SIGDescribe("CSIStorageCapacity", func() { 38 f := framework.NewDefaultFramework("csistoragecapacity") 39 f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged 40 41 /* 42 Release: v1.24 43 Testname: CSIStorageCapacity API 44 Description: 45 The storage.k8s.io API group MUST exist in the /apis discovery document. 46 The storage.k8s.io/v1 API group/version MUST exist in the /apis/mode.k8s.io discovery document. 47 The csistoragecapacities resource MUST exist in the /apis/storage.k8s.io/v1 discovery document. 48 The csistoragecapacities resource must support create, get, list, watch, update, patch, delete, and deletecollection. 49 */ 50 framework.ConformanceIt("should support CSIStorageCapacities API operations", func(ctx context.Context) { 51 // Setup 52 cscVersion := "v1" 53 cscClient := f.ClientSet.StorageV1().CSIStorageCapacities(f.Namespace.Name) 54 cscClientNoNamespace := f.ClientSet.StorageV1().CSIStorageCapacities("") 55 56 // The fictional StorageClass for these objects. 57 scName := "e2e.example.com" 58 59 // All CRUD operations in this test are limited to the objects with the label test=f.UniqueName 60 newCSIStorageCapacity := func(nameSuffix string) *storagev1.CSIStorageCapacity { 61 return &storagev1.CSIStorageCapacity{ 62 ObjectMeta: metav1.ObjectMeta{ 63 Name: f.UniqueName + nameSuffix, 64 Labels: map[string]string{ 65 "test": f.UniqueName, 66 }, 67 }, 68 StorageClassName: scName, 69 } 70 } 71 csc := newCSIStorageCapacity("-csc1") 72 csc2 := newCSIStorageCapacity("-csc2") 73 csc3 := newCSIStorageCapacity("-csc3") 74 75 // Discovery 76 77 ginkgo.By("getting /apis") 78 { 79 discoveryGroups, err := f.ClientSet.Discovery().ServerGroups() 80 framework.ExpectNoError(err) 81 found := false 82 for _, group := range discoveryGroups.Groups { 83 if group.Name == storagev1.GroupName { 84 for _, version := range group.Versions { 85 if version.Version == cscVersion { 86 found = true 87 break 88 } 89 } 90 } 91 } 92 if !found { 93 framework.Failf("expected CSIStorageCapacity API group/version, got %#v", discoveryGroups.Groups) 94 } 95 } 96 97 ginkgo.By("getting /apis/storage.k8s.io") 98 { 99 group := &metav1.APIGroup{} 100 err := f.ClientSet.Discovery().RESTClient().Get().AbsPath("/apis/storage.k8s.io").Do(ctx).Into(group) 101 framework.ExpectNoError(err) 102 found := false 103 for _, version := range group.Versions { 104 if version.Version == cscVersion { 105 found = true 106 break 107 } 108 } 109 if !found { 110 framework.Failf("expected CSIStorageCapacity API version, got %#v", group.Versions) 111 } 112 } 113 114 ginkgo.By("getting /apis/storage.k8s.io/" + cscVersion) 115 { 116 resources, err := f.ClientSet.Discovery().ServerResourcesForGroupVersion(storagev1.SchemeGroupVersion.String()) 117 framework.ExpectNoError(err) 118 found := false 119 for _, resource := range resources.APIResources { 120 switch resource.Name { 121 case "csistoragecapacities": 122 found = true 123 } 124 } 125 if !found { 126 framework.Failf("expected csistoragecapacities, got %#v", resources.APIResources) 127 } 128 } 129 130 // Main resource create/read/update/watch operations 131 132 ginkgo.By("creating") 133 createdCSC, err := cscClient.Create(ctx, csc, metav1.CreateOptions{}) 134 framework.ExpectNoError(err) 135 _, err = cscClient.Create(ctx, csc, metav1.CreateOptions{}) 136 if !apierrors.IsAlreadyExists(err) { 137 framework.Failf("expected 409, got %#v", err) 138 } 139 _, err = cscClient.Create(ctx, csc2, metav1.CreateOptions{}) 140 framework.ExpectNoError(err) 141 142 ginkgo.By("watching") 143 framework.Logf("starting watch") 144 cscWatch, err := cscClient.Watch(ctx, metav1.ListOptions{LabelSelector: "test=" + f.UniqueName}) 145 framework.ExpectNoError(err) 146 cscWatchNoNamespace, err := cscClientNoNamespace.Watch(ctx, metav1.ListOptions{LabelSelector: "test=" + f.UniqueName}) 147 framework.ExpectNoError(err) 148 149 // added for a watch 150 _, err = cscClient.Create(ctx, csc3, metav1.CreateOptions{}) 151 framework.ExpectNoError(err) 152 153 ginkgo.By("getting") 154 gottenCSC, err := cscClient.Get(ctx, csc.Name, metav1.GetOptions{}) 155 framework.ExpectNoError(err) 156 gomega.Expect(gottenCSC.UID).To(gomega.Equal(createdCSC.UID)) 157 158 ginkgo.By("listing in namespace") 159 cscs, err := cscClient.List(ctx, metav1.ListOptions{LabelSelector: "test=" + f.UniqueName}) 160 framework.ExpectNoError(err) 161 gomega.Expect(cscs.Items).To(gomega.HaveLen(3), "filtered list should have 3 items, got: %s", cscs) 162 163 ginkgo.By("listing across namespaces") 164 cscs, err = cscClientNoNamespace.List(ctx, metav1.ListOptions{LabelSelector: "test=" + f.UniqueName}) 165 framework.ExpectNoError(err) 166 gomega.Expect(cscs.Items).To(gomega.HaveLen(3), "filtered list should have 3 items, got: %s", cscs) 167 168 ginkgo.By("patching") 169 patchedCSC, err := cscClient.Patch(ctx, createdCSC.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"patched":"true"}}}`), metav1.PatchOptions{}) 170 framework.ExpectNoError(err) 171 gomega.Expect(patchedCSC.Annotations).To(gomega.HaveKeyWithValue("patched", "true"), "patched object should have the applied annotation") 172 173 ginkgo.By("updating") 174 csrToUpdate := patchedCSC.DeepCopy() 175 csrToUpdate.Annotations["updated"] = "true" 176 updatedCSC, err := cscClient.Update(ctx, csrToUpdate, metav1.UpdateOptions{}) 177 framework.ExpectNoError(err) 178 gomega.Expect(updatedCSC.Annotations).To(gomega.HaveKeyWithValue("updated", "true"), "updated object should have the applied annotation") 179 180 expectWatchResult := func(kind string, w watch.Interface) { 181 framework.Logf("waiting for watch events with expected annotations %s", kind) 182 for sawAdded, sawPatched, sawUpdated := false, false, false; !sawAdded && !sawPatched && !sawUpdated; { 183 select { 184 case evt, ok := <-w.ResultChan(): 185 if !ok { 186 framework.Failf("%s: watch channel should not close", kind) 187 } 188 if evt.Type == watch.Modified { 189 watchedCSC, isCSC := evt.Object.(*storagev1.CSIStorageCapacity) 190 if !isCSC { 191 framework.Failf("%s: expected CSC, got %T", kind, evt.Object) 192 } 193 if watchedCSC.Annotations["patched"] == "true" { 194 framework.Logf("%s: saw patched annotations", kind) 195 sawPatched = true 196 } else if watchedCSC.Annotations["updated"] == "true" { 197 framework.Logf("%s: saw updated annotations", kind) 198 sawUpdated = true 199 } else { 200 framework.Logf("%s: missing expected annotations, waiting: %#v", kind, watchedCSC.Annotations) 201 } 202 } else if evt.Type == watch.Added { 203 _, isCSC := evt.Object.(*storagev1.CSIStorageCapacity) 204 if !isCSC { 205 framework.Failf("%s: expected CSC, got %T", kind, evt.Object) 206 } 207 sawAdded = true 208 } 209 210 case <-time.After(wait.ForeverTestTimeout): 211 framework.Failf("%s: timed out waiting for watch event", kind) 212 } 213 } 214 w.Stop() 215 } 216 expectWatchResult("in namespace", cscWatch) 217 expectWatchResult("across namespace", cscWatchNoNamespace) 218 219 // main resource delete operations 220 221 ginkgo.By("deleting") 222 err = cscClient.Delete(ctx, createdCSC.Name, metav1.DeleteOptions{}) 223 framework.ExpectNoError(err) 224 csc, err = cscClient.Get(ctx, createdCSC.Name, metav1.GetOptions{}) 225 min := 2 226 max := min 227 switch { 228 case apierrors.IsNotFound(err): 229 // Okay, normal case. 230 case err != nil: 231 // Unexpected error. 232 framework.Failf("expected 404, got %#v", err) 233 case csc.DeletionTimestamp != nil && len(csc.Finalizers) > 0: 234 // Deletion was prevented by a finalizer, but it might 235 // still get deleted before we list them below. 236 max++ 237 default: 238 framework.Failf("CSIStorageCapacitity should have been deleted or have DeletionTimestamp and Finalizers, but instead got: %s", csc) 239 } 240 cscs, err = cscClient.List(ctx, metav1.ListOptions{LabelSelector: "test=" + f.UniqueName}) 241 framework.ExpectNoError(err) 242 actualLen := len(cscs.Items) 243 if actualLen < min || actualLen > max { 244 framework.Failf("expected <= %d and >= %d remaining CSIStorageCapacity objects, got %d: %v", max, min, actualLen, cscs.Items) 245 } 246 247 ginkgo.By("deleting a collection") 248 err = cscClient.DeleteCollection(ctx, metav1.DeleteOptions{}, metav1.ListOptions{LabelSelector: "test=" + f.UniqueName}) 249 framework.ExpectNoError(err) 250 cscs, err = cscClient.List(ctx, metav1.ListOptions{LabelSelector: "test=" + f.UniqueName}) 251 framework.ExpectNoError(err) 252 for _, csc := range cscs.Items { 253 // Any remaining objects should be marked for deletion 254 // and only held back by a Finalizer. 255 if csc.DeletionTimestamp == nil || len(csc.Finalizers) == 0 { 256 framework.Failf("CSIStorageCapacity should have been deleted or have DeletionTimestamp and Finalizers, but instead got: %s", &csc) 257 } 258 } 259 }) 260 })