1
16
17 package apiserver
18
19 import (
20 "context"
21 "testing"
22 "time"
23
24 "github.com/stretchr/testify/require"
25 apidiscoveryv2 "k8s.io/api/apidiscovery/v2"
26 v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
27 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
28 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake"
29 "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions"
30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31 "k8s.io/apimachinery/pkg/runtime/schema"
32 "k8s.io/apiserver/pkg/endpoints/discovery"
33 "k8s.io/apiserver/pkg/endpoints/discovery/aggregated"
34 )
35
36 var coolFooCRD = &v1.CustomResourceDefinition{
37 TypeMeta: metav1.TypeMeta{
38 APIVersion: "apiextensions.k8s.io/v1",
39 Kind: "CustomResourceDefinition",
40 },
41 ObjectMeta: metav1.ObjectMeta{
42 Name: "coolfoo.stable.example.com",
43 },
44 Spec: v1.CustomResourceDefinitionSpec{
45 Group: "stable.example.com",
46 Names: v1.CustomResourceDefinitionNames{
47 Plural: "coolfoos",
48 Singular: "coolfoo",
49 ShortNames: []string{"foo"},
50 Kind: "CoolFoo",
51 ListKind: "CoolFooList",
52 Categories: []string{"cool"},
53 },
54 Scope: v1.ClusterScoped,
55 Versions: []v1.CustomResourceDefinitionVersion{
56 {
57 Name: "v1",
58 Served: true,
59 Storage: true,
60 Deprecated: false,
61 Subresources: &v1.CustomResourceSubresources{
62
63 Status: &v1.CustomResourceSubresourceStatus{},
64 },
65 Schema: &v1.CustomResourceValidation{
66
67 OpenAPIV3Schema: &v1.JSONSchemaProps{},
68 },
69 },
70 },
71 Conversion: &v1.CustomResourceConversion{},
72 PreserveUnknownFields: false,
73 },
74 Status: v1.CustomResourceDefinitionStatus{
75 Conditions: []v1.CustomResourceDefinitionCondition{
76 {
77 Type: v1.Established,
78 Status: v1.ConditionTrue,
79 },
80 },
81 },
82 }
83
84 var coolBarCRD = &v1.CustomResourceDefinition{
85 TypeMeta: metav1.TypeMeta{
86 APIVersion: "apiextensions.k8s.io/v1",
87 Kind: "CustomResourceDefinition",
88 },
89 ObjectMeta: metav1.ObjectMeta{
90 Name: "coolbar.stable.example.com",
91 },
92 Spec: v1.CustomResourceDefinitionSpec{
93 Group: "stable.example.com",
94 Names: v1.CustomResourceDefinitionNames{
95 Plural: "coolbars",
96 Singular: "coolbar",
97 ShortNames: []string{"bar"},
98 Kind: "CoolBar",
99 ListKind: "CoolBarList",
100 Categories: []string{"cool"},
101 },
102 Scope: v1.ClusterScoped,
103 Versions: []v1.CustomResourceDefinitionVersion{
104 {
105 Name: "v1",
106 Served: true,
107 Storage: true,
108 Deprecated: false,
109 Schema: &v1.CustomResourceValidation{
110
111 OpenAPIV3Schema: &v1.JSONSchemaProps{},
112 },
113 },
114 },
115 Conversion: &v1.CustomResourceConversion{},
116 PreserveUnknownFields: false,
117 },
118 Status: v1.CustomResourceDefinitionStatus{
119 Conditions: []v1.CustomResourceDefinitionCondition{
120 {
121 Type: v1.Established,
122 Status: v1.ConditionTrue,
123 },
124 },
125 },
126 }
127
128 var coolFooDiscovery apidiscoveryv2.APIVersionDiscovery = apidiscoveryv2.APIVersionDiscovery{
129 Version: "v1",
130 Freshness: apidiscoveryv2.DiscoveryFreshnessCurrent,
131 Resources: []apidiscoveryv2.APIResourceDiscovery{
132 {
133 Resource: "coolfoos",
134 Scope: apidiscoveryv2.ScopeCluster,
135 SingularResource: "coolfoo",
136 Verbs: []string{"delete", "deletecollection", "get", "list", "patch", "create", "update", "watch"},
137 ShortNames: []string{"foo"},
138 Categories: []string{"cool"},
139 ResponseKind: &metav1.GroupVersionKind{
140 Group: "stable.example.com",
141 Version: "v1",
142 Kind: "CoolFoo",
143 },
144 Subresources: []apidiscoveryv2.APISubresourceDiscovery{
145 {
146 Subresource: "status",
147 Verbs: []string{"get", "patch", "update"},
148 AcceptedTypes: nil,
149 ResponseKind: &metav1.GroupVersionKind{
150 Group: "stable.example.com",
151 Version: "v1",
152 Kind: "CoolFoo",
153 },
154 },
155 },
156 },
157 },
158 }
159
160 var mergedDiscovery apidiscoveryv2.APIVersionDiscovery = apidiscoveryv2.APIVersionDiscovery{
161 Version: "v1",
162 Freshness: apidiscoveryv2.DiscoveryFreshnessCurrent,
163 Resources: []apidiscoveryv2.APIResourceDiscovery{
164 {
165 Resource: "coolbars",
166 Scope: apidiscoveryv2.ScopeCluster,
167 SingularResource: "coolbar",
168 Verbs: []string{"delete", "deletecollection", "get", "list", "patch", "create", "update", "watch"},
169 ShortNames: []string{"bar"},
170 Categories: []string{"cool"},
171 ResponseKind: &metav1.GroupVersionKind{
172 Group: "stable.example.com",
173 Version: "v1",
174 Kind: "CoolBar",
175 },
176 }, {
177 Resource: "coolfoos",
178 Scope: apidiscoveryv2.ScopeCluster,
179 SingularResource: "coolfoo",
180 Verbs: []string{"delete", "deletecollection", "get", "list", "patch", "create", "update", "watch"},
181 ShortNames: []string{"foo"},
182 Categories: []string{"cool"},
183 ResponseKind: &metav1.GroupVersionKind{
184 Group: "stable.example.com",
185 Version: "v1",
186 Kind: "CoolFoo",
187 },
188 Subresources: []apidiscoveryv2.APISubresourceDiscovery{
189 {
190 Subresource: "status",
191 Verbs: []string{"get", "patch", "update"},
192 AcceptedTypes: nil,
193 ResponseKind: &metav1.GroupVersionKind{
194 Group: "stable.example.com",
195 Version: "v1",
196 Kind: "CoolFoo",
197 },
198 },
199 },
200 },
201 },
202 }
203
204 func init() {
205
206 coolFooCRD.Status.AcceptedNames = coolFooCRD.Spec.Names
207 coolBarCRD.Status.AcceptedNames = coolBarCRD.Spec.Names
208 }
209
210
211 type testEnvironment struct {
212 clientset.Interface
213
214
215 versionDiscoveryHandler
216 groupDiscoveryHandler
217
218 aggregated.FakeResourceManager
219 }
220
221 func (env *testEnvironment) Start(ctx context.Context) {
222 discoverySyncedCh := make(chan struct{})
223
224 factory := externalversions.NewSharedInformerFactoryWithOptions(
225 env.Interface, 30*time.Second)
226
227 discoveryController := NewDiscoveryController(
228 factory.Apiextensions().V1().CustomResourceDefinitions(),
229 &env.versionDiscoveryHandler,
230 &env.groupDiscoveryHandler,
231 env.FakeResourceManager,
232 )
233
234 factory.Start(ctx.Done())
235 go discoveryController.Run(ctx.Done(), discoverySyncedCh)
236
237 select {
238 case <-discoverySyncedCh:
239 case <-ctx.Done():
240 }
241 }
242
243 func setup() *testEnvironment {
244 env := &testEnvironment{
245 Interface: fake.NewSimpleClientset(),
246 FakeResourceManager: aggregated.NewFakeResourceManager(),
247 versionDiscoveryHandler: versionDiscoveryHandler{
248 discovery: make(map[schema.GroupVersion]*discovery.APIVersionHandler),
249 },
250 groupDiscoveryHandler: groupDiscoveryHandler{
251 discovery: make(map[string]*discovery.APIGroupHandler),
252 },
253 }
254
255 return env
256 }
257
258 func TestResourceManagerExistingCRD(t *testing.T) {
259 ctx, cancel := context.WithCancel(context.Background())
260 defer cancel()
261
262 env := setup()
263 _, err := env.Interface.
264 ApiextensionsV1().
265 CustomResourceDefinitions().
266 Create(
267 ctx,
268 coolFooCRD,
269 metav1.CreateOptions{
270 FieldManager: "resource-manager-test",
271 },
272 )
273
274 require.NoError(t, err)
275
276 env.FakeResourceManager.Expect().
277 AddGroupVersion(coolFooCRD.Spec.Group, coolFooDiscovery)
278 for _, v := range coolFooCRD.Spec.Versions {
279 env.FakeResourceManager.Expect().
280 SetGroupVersionPriority(metav1.GroupVersion{Group: coolFooCRD.Spec.Group, Version: v.Name}, 1000, 100)
281 }
282
283 env.FakeResourceManager.Expect().
284 AddGroupVersion(coolFooCRD.Spec.Group, coolFooDiscovery)
285 for _, v := range coolFooCRD.Spec.Versions {
286 env.FakeResourceManager.Expect().
287 SetGroupVersionPriority(metav1.GroupVersion{Group: coolFooCRD.Spec.Group, Version: v.Name}, 1000, 100)
288 }
289
290 env.Start(ctx)
291 err = env.FakeResourceManager.WaitForActions(ctx, 1*time.Second)
292 require.NoError(t, err)
293 }
294
295
296
297 func TestResourceManagerAddedCRD(t *testing.T) {
298 ctx, cancel := context.WithCancel(context.Background())
299 defer cancel()
300
301 env := setup()
302 env.FakeResourceManager.Expect().
303 AddGroupVersion(coolFooCRD.Spec.Group, coolFooDiscovery)
304 for _, v := range coolFooCRD.Spec.Versions {
305 env.FakeResourceManager.Expect().
306 SetGroupVersionPriority(metav1.GroupVersion{Group: coolFooCRD.Spec.Group, Version: v.Name}, 1000, 100)
307 }
308
309 env.Start(ctx)
310
311
312 _, err := env.Interface.
313 ApiextensionsV1().
314 CustomResourceDefinitions().
315 Create(
316 ctx,
317 coolFooCRD,
318 metav1.CreateOptions{
319 FieldManager: "resource-manager-test",
320 },
321 )
322
323 require.NoError(t, err)
324
325 err = env.FakeResourceManager.WaitForActions(ctx, 1*time.Second)
326 require.NoError(t, err)
327 }
328
329
330
331 func TestMultipleCRDSameVersion(t *testing.T) {
332 ctx, cancel := context.WithCancel(context.Background())
333 defer cancel()
334
335 env := setup()
336 env.Start(ctx)
337
338 _, err := env.Interface.
339 ApiextensionsV1().
340 CustomResourceDefinitions().
341 Create(
342 ctx,
343 coolFooCRD,
344 metav1.CreateOptions{
345 FieldManager: "resource-manager-test",
346 },
347 )
348
349 require.NoError(t, err)
350 env.FakeResourceManager.Expect().
351 AddGroupVersion(coolFooCRD.Spec.Group, coolFooDiscovery)
352 for _, versionEntry := range coolFooCRD.Spec.Versions {
353 env.FakeResourceManager.Expect().SetGroupVersionPriority(metav1.GroupVersion{Group: coolFooCRD.Spec.Group, Version: versionEntry.Name}, 1000, 100)
354 }
355 err = env.FakeResourceManager.WaitForActions(ctx, 1*time.Second)
356 require.NoError(t, err)
357
358 _, err = env.Interface.
359 ApiextensionsV1().
360 CustomResourceDefinitions().
361 Create(
362 ctx,
363 coolBarCRD,
364 metav1.CreateOptions{
365 FieldManager: "resource-manager-test",
366 },
367 )
368 require.NoError(t, err)
369
370 env.FakeResourceManager.Expect().
371 AddGroupVersion(coolFooCRD.Spec.Group, mergedDiscovery)
372 for _, versionEntry := range coolFooCRD.Spec.Versions {
373 env.FakeResourceManager.Expect().SetGroupVersionPriority(metav1.GroupVersion{Group: coolFooCRD.Spec.Group, Version: versionEntry.Name}, 1000, 100)
374 }
375 err = env.FakeResourceManager.WaitForActions(ctx, 1*time.Second)
376 require.NoError(t, err)
377 }
378
379
380
381 func TestDiscoveryControllerResourceManagerRemovedCRD(t *testing.T) {
382 ctx, cancel := context.WithCancel(context.Background())
383 defer cancel()
384
385 env := setup()
386 env.Start(ctx)
387
388
389 _, err := env.Interface.
390 ApiextensionsV1().
391 CustomResourceDefinitions().
392 Create(
393 ctx,
394 coolFooCRD,
395 metav1.CreateOptions{},
396 )
397
398 require.NoError(t, err)
399
400
401
402 env.FakeResourceManager.Expect().
403 AddGroupVersion(coolFooCRD.Spec.Group, coolFooDiscovery)
404 for _, versionEntry := range coolFooCRD.Spec.Versions {
405 env.FakeResourceManager.Expect().SetGroupVersionPriority(metav1.GroupVersion{Group: coolFooCRD.Spec.Group, Version: versionEntry.Name}, 1000, 100)
406 }
407 err = env.FakeResourceManager.WaitForActions(ctx, 1*time.Second)
408 require.NoError(t, err)
409
410 err = env.Interface.
411 ApiextensionsV1().
412 CustomResourceDefinitions().
413 Delete(ctx, coolFooCRD.Name, metav1.DeleteOptions{})
414
415 require.NoError(t, err)
416
417
418
419 env.FakeResourceManager.Expect().RemoveGroup(coolFooCRD.Spec.Group)
420
421 err = env.FakeResourceManager.WaitForActions(ctx, 1*time.Second)
422 require.NoError(t, err)
423 }
424
View as plain text