k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/e2e/apimachinery/crd_watch.go (about) 1 /* 2 Copyright 2018 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 apimachinery 18 19 import ( 20 "context" 21 "fmt" 22 23 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 24 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" 25 "k8s.io/apiextensions-apiserver/test/integration/fixtures" 26 "k8s.io/apimachinery/pkg/api/meta" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 29 "k8s.io/apimachinery/pkg/runtime/schema" 30 "k8s.io/apimachinery/pkg/types" 31 "k8s.io/apimachinery/pkg/watch" 32 "k8s.io/client-go/dynamic" 33 "k8s.io/kubernetes/test/e2e/framework" 34 admissionapi "k8s.io/pod-security-admission/api" 35 36 "github.com/onsi/ginkgo/v2" 37 ) 38 39 var _ = SIGDescribe("CustomResourceDefinition Watch [Privileged:ClusterAdmin]", func() { 40 41 f := framework.NewDefaultFramework("crd-watch") 42 f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged 43 44 ginkgo.Context("CustomResourceDefinition Watch", func() { 45 /* 46 Release: v1.16 47 Testname: Custom Resource Definition, watch 48 Description: Create a Custom Resource Definition. Attempt to watch it; the watch MUST observe create, 49 modify and delete events. 50 */ 51 framework.ConformanceIt("watch on custom resource definition objects", func(ctx context.Context) { 52 53 const ( 54 watchCRNameA = "name1" 55 watchCRNameB = "name2" 56 ) 57 58 config, err := framework.LoadConfig() 59 if err != nil { 60 framework.Failf("failed to load config: %v", err) 61 } 62 63 apiExtensionClient, err := clientset.NewForConfig(config) 64 if err != nil { 65 framework.Failf("failed to initialize apiExtensionClient: %v", err) 66 } 67 68 noxuDefinition := fixtures.NewNoxuV1CustomResourceDefinition(apiextensionsv1.ClusterScoped) 69 noxuDefinition, err = fixtures.CreateNewV1CustomResourceDefinition(noxuDefinition, apiExtensionClient, f.DynamicClient) 70 if err != nil { 71 framework.Failf("failed to create CustomResourceDefinition: %v", err) 72 } 73 74 defer func() { 75 err = fixtures.DeleteV1CustomResourceDefinition(noxuDefinition, apiExtensionClient) 76 if err != nil { 77 framework.Failf("failed to delete CustomResourceDefinition: %v", err) 78 } 79 }() 80 81 ns := "" 82 noxuResourceClient, err := newNamespacedCustomResourceClient(ns, f.DynamicClient, noxuDefinition) 83 framework.ExpectNoError(err, "creating custom resource client") 84 85 watchA, err := watchCRWithName(ctx, noxuResourceClient, watchCRNameA) 86 framework.ExpectNoError(err, "failed to watch custom resource: %s", watchCRNameA) 87 88 watchB, err := watchCRWithName(ctx, noxuResourceClient, watchCRNameB) 89 framework.ExpectNoError(err, "failed to watch custom resource: %s", watchCRNameB) 90 91 testCrA := fixtures.NewNoxuInstance(ns, watchCRNameA) 92 testCrB := fixtures.NewNoxuInstance(ns, watchCRNameB) 93 94 ginkgo.By("Creating first CR ") 95 testCrA, err = instantiateCustomResource(ctx, testCrA, noxuResourceClient, noxuDefinition) 96 framework.ExpectNoError(err, "failed to instantiate custom resource: %+v", testCrA) 97 expectEvent(watchA, watch.Added, testCrA) 98 expectNoEvent(watchB, watch.Added, testCrA) 99 100 ginkgo.By("Creating second CR") 101 testCrB, err = instantiateCustomResource(ctx, testCrB, noxuResourceClient, noxuDefinition) 102 framework.ExpectNoError(err, "failed to instantiate custom resource: %+v", testCrB) 103 expectEvent(watchB, watch.Added, testCrB) 104 expectNoEvent(watchA, watch.Added, testCrB) 105 106 ginkgo.By("Modifying first CR") 107 err = patchCustomResource(ctx, noxuResourceClient, watchCRNameA) 108 framework.ExpectNoError(err, "failed to patch custom resource: %s", watchCRNameA) 109 expectEvent(watchA, watch.Modified, nil) 110 expectNoEvent(watchB, watch.Modified, nil) 111 112 ginkgo.By("Modifying second CR") 113 err = patchCustomResource(ctx, noxuResourceClient, watchCRNameB) 114 framework.ExpectNoError(err, "failed to patch custom resource: %s", watchCRNameB) 115 expectEvent(watchB, watch.Modified, nil) 116 expectNoEvent(watchA, watch.Modified, nil) 117 118 ginkgo.By("Deleting first CR") 119 err = deleteCustomResource(ctx, noxuResourceClient, watchCRNameA) 120 framework.ExpectNoError(err, "failed to delete custom resource: %s", watchCRNameA) 121 expectEvent(watchA, watch.Deleted, nil) 122 expectNoEvent(watchB, watch.Deleted, nil) 123 124 ginkgo.By("Deleting second CR") 125 err = deleteCustomResource(ctx, noxuResourceClient, watchCRNameB) 126 framework.ExpectNoError(err, "failed to delete custom resource: %s", watchCRNameB) 127 expectEvent(watchB, watch.Deleted, nil) 128 expectNoEvent(watchA, watch.Deleted, nil) 129 }) 130 }) 131 }) 132 133 func watchCRWithName(ctx context.Context, crdResourceClient dynamic.ResourceInterface, name string) (watch.Interface, error) { 134 return crdResourceClient.Watch( 135 ctx, 136 metav1.ListOptions{ 137 FieldSelector: "metadata.name=" + name, 138 TimeoutSeconds: int64ptr(600), 139 }, 140 ) 141 } 142 143 func instantiateCustomResource(ctx context.Context, instanceToCreate *unstructured.Unstructured, client dynamic.ResourceInterface, definition *apiextensionsv1.CustomResourceDefinition) (*unstructured.Unstructured, error) { 144 createdInstance, err := client.Create(ctx, instanceToCreate, metav1.CreateOptions{}) 145 if err != nil { 146 return nil, err 147 } 148 createdObjectMeta, err := meta.Accessor(createdInstance) 149 if err != nil { 150 return nil, err 151 } 152 // it should have a UUID 153 if len(createdObjectMeta.GetUID()) == 0 { 154 return nil, fmt.Errorf("missing uuid: %#v", createdInstance) 155 } 156 createdTypeMeta, err := meta.TypeAccessor(createdInstance) 157 if err != nil { 158 return nil, err 159 } 160 if len(definition.Spec.Versions) != 1 { 161 return nil, fmt.Errorf("expected exactly one version, got %v", definition.Spec.Versions) 162 } 163 if e, a := definition.Spec.Group+"/"+definition.Spec.Versions[0].Name, createdTypeMeta.GetAPIVersion(); e != a { 164 return nil, fmt.Errorf("expected %v, got %v", e, a) 165 } 166 if e, a := definition.Spec.Names.Kind, createdTypeMeta.GetKind(); e != a { 167 return nil, fmt.Errorf("expected %v, got %v", e, a) 168 } 169 return createdInstance, nil 170 } 171 172 func patchCustomResource(ctx context.Context, client dynamic.ResourceInterface, name string) error { 173 _, err := client.Patch( 174 ctx, 175 name, 176 types.JSONPatchType, 177 []byte(`[{ "op": "add", "path": "/dummy", "value": "test" }]`), 178 metav1.PatchOptions{}) 179 return err 180 } 181 182 func deleteCustomResource(ctx context.Context, client dynamic.ResourceInterface, name string) error { 183 return client.Delete(ctx, name, metav1.DeleteOptions{}) 184 } 185 186 func newNamespacedCustomResourceClient(ns string, client dynamic.Interface, crd *apiextensionsv1.CustomResourceDefinition) (dynamic.ResourceInterface, error) { 187 if len(crd.Spec.Versions) != 1 { 188 return nil, fmt.Errorf("expected exactly one version, got %v", crd.Spec.Versions) 189 } 190 gvr := schema.GroupVersionResource{Group: crd.Spec.Group, Version: crd.Spec.Versions[0].Name, Resource: crd.Spec.Names.Plural} 191 192 if crd.Spec.Scope != apiextensionsv1.ClusterScoped { 193 return client.Resource(gvr).Namespace(ns), nil 194 } 195 return client.Resource(gvr), nil 196 197 }