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  })