1
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
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
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 }
198
View as plain text