1
16
17 package apimachinery
18
19 import (
20 "context"
21 "crypto/rand"
22 "encoding/json"
23 "fmt"
24 "math/big"
25 "net"
26 "strings"
27 "time"
28
29 appsv1 "k8s.io/api/apps/v1"
30 v1 "k8s.io/api/core/v1"
31 rbacv1 "k8s.io/api/rbac/v1"
32 apierrors "k8s.io/apimachinery/pkg/api/errors"
33 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
34 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
35 "k8s.io/apimachinery/pkg/labels"
36 "k8s.io/apimachinery/pkg/runtime/schema"
37 "k8s.io/apimachinery/pkg/types"
38 "k8s.io/apimachinery/pkg/util/intstr"
39 "k8s.io/apimachinery/pkg/util/wait"
40 "k8s.io/client-go/discovery"
41 clientset "k8s.io/client-go/kubernetes"
42 "k8s.io/client-go/util/retry"
43 apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
44 aggregatorclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
45 rbacv1helpers "k8s.io/kubernetes/pkg/apis/rbac/v1"
46 "k8s.io/kubernetes/test/e2e/framework"
47 e2eauth "k8s.io/kubernetes/test/e2e/framework/auth"
48 e2edeployment "k8s.io/kubernetes/test/e2e/framework/deployment"
49 e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
50 "k8s.io/kubernetes/test/utils/format"
51 imageutils "k8s.io/kubernetes/test/utils/image"
52 admissionapi "k8s.io/pod-security-admission/api"
53 samplev1alpha1 "k8s.io/sample-apiserver/pkg/apis/wardle/v1alpha1"
54 "k8s.io/utils/pointer"
55
56 "github.com/onsi/ginkgo/v2"
57 "github.com/onsi/gomega"
58 )
59
60 const (
61 aggregatorServicePort = 7443
62
63 apiServiceRetryPeriod = 1 * time.Second
64 apiServiceRetryTimeout = 2 * time.Minute
65
66 defaultApiServiceGroupName = samplev1alpha1.GroupName
67 defaultApiServiceVersion = "v1alpha1"
68 )
69
70 var _ = SIGDescribe("Aggregator", func() {
71 var aggrclient *aggregatorclient.Clientset
72
73 f := framework.NewDefaultFramework("aggregator")
74 f.NamespacePodSecurityLevel = admissionapi.LevelBaseline
75
76
77
78
79 ginkgo.BeforeEach(func() {
80 config, err := framework.LoadConfig()
81 if err != nil {
82 framework.Failf("could not load config: %v", err)
83 }
84 aggrclient, err = aggregatorclient.NewForConfig(config)
85 if err != nil {
86 framework.Failf("could not create aggregator client: %v", err)
87 }
88 apiServiceName := defaultApiServiceVersion + "." + defaultApiServiceGroupName
89 ginkgo.DeferCleanup(cleanupSampleAPIServer, f.ClientSet, aggrclient, generateSampleAPIServerObjectNames(f.Namespace.Name), apiServiceName)
90 })
91
92
98 framework.ConformanceIt("Should be able to support the 1.17 Sample API Server using the current Aggregator", func(ctx context.Context) {
99
100 TestSampleAPIServer(ctx, f, aggrclient, imageutils.GetE2EImage(imageutils.APIServer), defaultApiServiceGroupName, defaultApiServiceVersion)
101 })
102 })
103
104 func cleanupSampleAPIServer(ctx context.Context, client clientset.Interface, aggrclient *aggregatorclient.Clientset, n sampleAPIServerObjectNames, apiServiceName string) {
105
106 _ = aggrclient.ApiregistrationV1().APIServices().Delete(ctx, apiServiceName, metav1.DeleteOptions{})
107
108 _ = client.AppsV1().Deployments(n.namespace).Delete(ctx, "sample-apiserver-deployment", metav1.DeleteOptions{})
109 _ = client.CoreV1().Secrets(n.namespace).Delete(ctx, "sample-apiserver-secret", metav1.DeleteOptions{})
110 _ = client.CoreV1().Services(n.namespace).Delete(ctx, "sample-api", metav1.DeleteOptions{})
111 _ = client.CoreV1().ServiceAccounts(n.namespace).Delete(ctx, "sample-apiserver", metav1.DeleteOptions{})
112 _ = client.RbacV1().RoleBindings("kube-system").Delete(ctx, n.roleBinding, metav1.DeleteOptions{})
113 _ = client.RbacV1().ClusterRoleBindings().Delete(ctx, "wardler:"+n.namespace+":auth-delegator", metav1.DeleteOptions{})
114 _ = client.RbacV1().ClusterRoles().Delete(ctx, n.clusterRole, metav1.DeleteOptions{})
115 _ = client.RbacV1().ClusterRoleBindings().Delete(ctx, n.clusterRoleBinding, metav1.DeleteOptions{})
116 }
117
118 type sampleAPIServerObjectNames struct {
119 namespace string
120 roleBinding string
121 clusterRole string
122 clusterRoleBinding string
123 }
124
125 func generateSampleAPIServerObjectNames(namespace string) sampleAPIServerObjectNames {
126 return sampleAPIServerObjectNames{
127 namespace: namespace,
128 roleBinding: "wardler-auth-reader-" + namespace,
129 clusterRole: "sample-apiserver-reader-" + namespace,
130 clusterRoleBinding: "wardler:" + namespace + "sample-apiserver-reader-" + namespace,
131 }
132 }
133
134 func SetUpSampleAPIServer(ctx context.Context, f *framework.Framework, aggrclient *aggregatorclient.Clientset, image string, n sampleAPIServerObjectNames, apiServiceGroupName, apiServiceVersion string) {
135 ginkgo.By("Registering the sample API server.")
136 client := f.ClientSet
137 restClient := client.Discovery().RESTClient()
138 certCtx := setupServerCert(n.namespace, "sample-api")
139 apiServiceName := apiServiceVersion + "." + apiServiceGroupName
140
141
142
143
144
145 secretName := "sample-apiserver-secret"
146 secret := &v1.Secret{
147 ObjectMeta: metav1.ObjectMeta{
148 Name: secretName,
149 },
150 Type: v1.SecretTypeOpaque,
151 Data: map[string][]byte{
152 "tls.crt": certCtx.cert,
153 "tls.key": certCtx.key,
154 },
155 }
156 _, err := client.CoreV1().Secrets(n.namespace).Create(ctx, secret, metav1.CreateOptions{})
157 framework.ExpectNoError(err, "creating secret %s in.namespace %s", secretName, n.namespace)
158
159 if e2eauth.IsRBACEnabled(ctx, client.RbacV1()) {
160
161 _, err = client.RbacV1().ClusterRoles().Create(ctx, &rbacv1.ClusterRole{
162
163 ObjectMeta: metav1.ObjectMeta{Name: n.clusterRole},
164 Rules: []rbacv1.PolicyRule{
165 rbacv1helpers.NewRule("get", "list", "watch").Groups("").Resources("namespaces").RuleOrDie(),
166 rbacv1helpers.NewRule("get", "list", "watch").Groups("admissionregistration.k8s.io").Resources("*").RuleOrDie(),
167 rbacv1helpers.NewRule("get", "list", "watch").Groups("flowcontrol.apiserver.k8s.io").Resources("prioritylevelconfigurations", "flowschemas").RuleOrDie(),
168 },
169 }, metav1.CreateOptions{})
170 framework.ExpectNoError(err, "creating cluster role %s", n.clusterRole)
171
172 _, err = client.RbacV1().ClusterRoleBindings().Create(ctx, &rbacv1.ClusterRoleBinding{
173 ObjectMeta: metav1.ObjectMeta{
174 Name: n.clusterRoleBinding,
175 },
176 RoleRef: rbacv1.RoleRef{
177 APIGroup: "rbac.authorization.k8s.io",
178 Kind: "ClusterRole",
179 Name: n.clusterRole,
180 },
181 Subjects: []rbacv1.Subject{
182 {
183 APIGroup: "",
184 Kind: "ServiceAccount",
185 Name: "default",
186 Namespace: n.namespace,
187 },
188 },
189 }, metav1.CreateOptions{})
190 framework.ExpectNoError(err, "creating cluster role binding %s", n.clusterRoleBinding)
191
192
193 _, err = client.RbacV1().ClusterRoleBindings().Create(ctx, &rbacv1.ClusterRoleBinding{
194 ObjectMeta: metav1.ObjectMeta{
195 Name: "wardler:" + n.namespace + ":auth-delegator",
196 },
197 RoleRef: rbacv1.RoleRef{
198 APIGroup: "rbac.authorization.k8s.io",
199 Kind: "ClusterRole",
200 Name: "system:auth-delegator",
201 },
202 Subjects: []rbacv1.Subject{
203 {
204 APIGroup: "",
205 Kind: "ServiceAccount",
206 Name: "default",
207 Namespace: n.namespace,
208 },
209 },
210 }, metav1.CreateOptions{})
211 framework.ExpectNoError(err, "creating cluster role binding %s", "wardler:"+n.namespace+":auth-delegator")
212 }
213
214
215 deploymentName := "sample-apiserver-deployment"
216 etcdImage := imageutils.GetE2EImage(imageutils.Etcd)
217 podLabels := map[string]string{"app": "sample-apiserver", "apiserver": "true"}
218 replicas := int32(1)
219 etcdLocalhostAddress := "127.0.0.1"
220 if framework.TestContext.ClusterIsIPv6() {
221 etcdLocalhostAddress = "::1"
222 }
223 etcdURL := fmt.Sprintf("http://%s", net.JoinHostPort(etcdLocalhostAddress, "2379"))
224
225 mounts := []v1.VolumeMount{
226 {
227 Name: "apiserver-certs",
228 ReadOnly: true,
229 MountPath: "/apiserver.local.config/certificates",
230 },
231 }
232 volumes := []v1.Volume{
233 {
234 Name: "apiserver-certs",
235 VolumeSource: v1.VolumeSource{
236 Secret: &v1.SecretVolumeSource{SecretName: secretName},
237 },
238 },
239 }
240 containers := []v1.Container{
241 {
242 Name: "sample-apiserver",
243 VolumeMounts: mounts,
244 Args: []string{
245 fmt.Sprintf("--etcd-servers=%s", etcdURL),
246 "--tls-cert-file=/apiserver.local.config/certificates/tls.crt",
247 "--tls-private-key-file=/apiserver.local.config/certificates/tls.key",
248 "--audit-log-path=-",
249 "--audit-log-maxage=0",
250 "--audit-log-maxbackup=0",
251 },
252 Image: image,
253 ReadinessProbe: &v1.Probe{
254 ProbeHandler: v1.ProbeHandler{
255 HTTPGet: &v1.HTTPGetAction{
256 Scheme: v1.URISchemeHTTPS,
257 Port: intstr.FromInt32(443),
258 Path: "/readyz",
259 },
260 },
261 InitialDelaySeconds: 20,
262 PeriodSeconds: 1,
263 SuccessThreshold: 1,
264 FailureThreshold: 3,
265 },
266 },
267 {
268 Name: "etcd",
269 Image: etcdImage,
270 Command: []string{
271 "/usr/local/bin/etcd",
272 "--listen-client-urls",
273 etcdURL,
274 "--advertise-client-urls",
275 etcdURL,
276 },
277 },
278 }
279 d := e2edeployment.NewDeployment(deploymentName, replicas, podLabels, "", "", appsv1.RollingUpdateDeploymentStrategyType)
280 d.Spec.Template.Spec.Containers = containers
281 d.Spec.Template.Spec.Volumes = volumes
282
283 deployment, err := client.AppsV1().Deployments(n.namespace).Create(ctx, d, metav1.CreateOptions{})
284 framework.ExpectNoError(err, "creating deployment %s in namespace %s", deploymentName, n.namespace)
285
286 err = e2edeployment.WaitForDeploymentRevisionAndImage(client, n.namespace, deploymentName, "1", image)
287 framework.ExpectNoError(err, "waiting for the deployment of image %s in %s in %s to complete", image, deploymentName, n.namespace)
288
289 err = e2edeployment.WaitForDeploymentRevisionAndImage(client, n.namespace, deploymentName, "1", etcdImage)
290 framework.ExpectNoError(err, "waiting for the deployment of image %s in %s in %s to complete", etcdImage, deploymentName, n.namespace)
291
292
293 serviceLabels := map[string]string{"apiserver": "true"}
294 service := &v1.Service{
295 ObjectMeta: metav1.ObjectMeta{
296 Namespace: n.namespace,
297 Name: "sample-api",
298 Labels: map[string]string{"test": "aggregator"},
299 },
300 Spec: v1.ServiceSpec{
301 Selector: serviceLabels,
302 Ports: []v1.ServicePort{
303 {
304 Protocol: v1.ProtocolTCP,
305 Port: aggregatorServicePort,
306 TargetPort: intstr.FromInt32(443),
307 },
308 },
309 },
310 }
311 _, err = client.CoreV1().Services(n.namespace).Create(ctx, service, metav1.CreateOptions{})
312 framework.ExpectNoError(err, "creating service %s in namespace %s", "sample-api", n.namespace)
313
314
315 sa := &v1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: "sample-apiserver"}}
316 _, err = client.CoreV1().ServiceAccounts(n.namespace).Create(ctx, sa, metav1.CreateOptions{})
317 framework.ExpectNoError(err, "creating service account %s in namespace %s", "sample-apiserver", n.namespace)
318
319 if e2eauth.IsRBACEnabled(ctx, client.RbacV1()) {
320
321 _, err = client.RbacV1().RoleBindings("kube-system").Create(ctx, &rbacv1.RoleBinding{
322 ObjectMeta: metav1.ObjectMeta{
323 Name: n.roleBinding,
324 Annotations: map[string]string{
325 rbacv1.AutoUpdateAnnotationKey: "true",
326 },
327 },
328 RoleRef: rbacv1.RoleRef{
329 APIGroup: "",
330 Kind: "Role",
331 Name: "extension-apiserver-authentication-reader",
332 },
333 Subjects: []rbacv1.Subject{
334 {
335 Kind: "ServiceAccount",
336 Name: "default",
337 Namespace: n.namespace,
338 },
339 },
340 }, metav1.CreateOptions{})
341 framework.ExpectNoError(err, "creating role binding %s in namespace %s", n.roleBinding, "kube-system")
342 }
343
344
345
346
347
348 err = e2edeployment.WaitForDeploymentComplete(client, deployment)
349 framework.ExpectNoError(err, "deploying extension apiserver in namespace %s", n.namespace)
350
351
352 _, err = aggrclient.ApiregistrationV1().APIServices().Create(ctx, &apiregistrationv1.APIService{
353 ObjectMeta: metav1.ObjectMeta{Name: apiServiceName},
354 Spec: apiregistrationv1.APIServiceSpec{
355 Service: &apiregistrationv1.ServiceReference{
356 Namespace: n.namespace,
357 Name: "sample-api",
358 Port: pointer.Int32(aggregatorServicePort),
359 },
360 Group: apiServiceGroupName,
361 Version: apiServiceVersion,
362 CABundle: certCtx.signingCert,
363 GroupPriorityMinimum: 2000,
364 VersionPriority: 200,
365 },
366 }, metav1.CreateOptions{})
367 framework.ExpectNoError(err, "creating apiservice %s", apiServiceName)
368
369 var (
370 currentAPIService *apiregistrationv1.APIService
371 currentPods *v1.PodList
372 )
373
374 err = pollTimed(ctx, 100*time.Millisecond, 60*time.Second, func(ctx context.Context) (bool, error) {
375
376 currentAPIService, _ = aggrclient.ApiregistrationV1().APIServices().Get(ctx, apiServiceName, metav1.GetOptions{})
377 currentPods, _ = client.CoreV1().Pods(n.namespace).List(ctx, metav1.ListOptions{})
378
379 request := restClient.Get().AbsPath("/apis/" + apiServiceGroupName + "/" + apiServiceVersion + "/namespaces/default/flunders")
380 request.SetHeader("Accept", "application/json")
381 _, err := request.DoRaw(ctx)
382 if err != nil {
383 status, ok := err.(*apierrors.StatusError)
384 if !ok {
385 return false, err
386 }
387 if status.Status().Code == 403 || status.Status().Code == 503 {
388 return false, nil
389 }
390 if status.Status().Code == 404 && strings.HasPrefix(err.Error(), "the server could not find the requested resource") {
391 return false, nil
392 }
393 return false, err
394 }
395 return true, nil
396 }, "Waited %s for the sample-apiserver to be ready to handle requests.")
397 if err != nil {
398 currentAPIServiceJSON, _ := json.Marshal(currentAPIService)
399 framework.Logf("current APIService: %s", string(currentAPIServiceJSON))
400
401 currentPodsJSON, _ := json.Marshal(currentPods)
402 framework.Logf("current pods: %s", string(currentPodsJSON))
403
404 if currentPods != nil {
405 for _, pod := range currentPods.Items {
406 for _, container := range pod.Spec.Containers {
407 logs, err := e2epod.GetPodLogs(ctx, client, n.namespace, pod.Name, container.Name)
408 framework.Logf("logs of %s/%s (error: %v): %s", pod.Name, container.Name, err, logs)
409 }
410 }
411 }
412 }
413 framework.ExpectNoError(err, "gave up waiting for apiservice wardle to come up successfully")
414 }
415
416
417
418 func TestSampleAPIServer(ctx context.Context, f *framework.Framework, aggrclient *aggregatorclient.Clientset, image, apiServiceGroupName, apiServiceVersion string) {
419 n := generateSampleAPIServerObjectNames(f.Namespace.Name)
420 SetUpSampleAPIServer(ctx, f, aggrclient, image, n, apiServiceGroupName, apiServiceVersion)
421 client := f.ClientSet
422 restClient := client.Discovery().RESTClient()
423
424 flunderName := generateFlunderName("rest-flunder")
425 apiServiceName := apiServiceVersion + "." + apiServiceGroupName
426
427
428
429
430 flunder := `{"apiVersion":"` + apiServiceGroupName + `/` + apiServiceVersion + `","kind":"Flunder","metadata":{"labels":{"sample-label":"true"},"name":"` + flunderName + `","namespace":"default"}}`
431 result := restClient.Post().AbsPath("/apis/"+apiServiceGroupName+"/"+apiServiceVersion+"/namespaces/default/flunders").Body([]byte(flunder)).SetHeader("Accept", "application/json").Do(ctx)
432 framework.ExpectNoError(result.Error(), "creating a new flunders resource")
433 var statusCode int
434 result.StatusCode(&statusCode)
435 if statusCode != 201 {
436 framework.Failf("Flunders client creation response was status %d, not 201", statusCode)
437 }
438 u := &unstructured.Unstructured{}
439 if err := result.Into(u); err != nil {
440 framework.ExpectNoError(err, "reading created response")
441 }
442
443 gomega.Expect(u.GetAPIVersion()).To(gomega.Equal(apiServiceGroupName + "/" + apiServiceVersion))
444 gomega.Expect(u.GetKind()).To(gomega.Equal("Flunder"))
445 gomega.Expect(u.GetName()).To(gomega.Equal(flunderName))
446
447 pods, err := client.CoreV1().Pods(n.namespace).List(ctx, metav1.ListOptions{})
448 framework.ExpectNoError(err, "getting pods for flunders service")
449
450
451
452 contents, err := restClient.Get().AbsPath("/apis/"+apiServiceGroupName+"/"+apiServiceVersion+"/namespaces/default/flunders").SetHeader("Accept", "application/json").DoRaw(ctx)
453 framework.ExpectNoError(err, "attempting to get a newly created flunders resource")
454 var flundersList samplev1alpha1.FlunderList
455 err = json.Unmarshal(contents, &flundersList)
456 validateErrorWithDebugInfo(ctx, f, err, pods, "Error in unmarshalling %T response from server %s", contents, "/apis/"+apiServiceGroupName+"/"+apiServiceVersion)
457 if len(flundersList.Items) != 1 {
458 framework.Failf("failed to get back the correct flunders list %v", flundersList)
459 }
460
461
462
463 _, err = restClient.Delete().AbsPath("/apis/" + apiServiceGroupName + "/" + apiServiceVersion + "/namespaces/default/flunders/" + flunderName).DoRaw(ctx)
464 validateErrorWithDebugInfo(ctx, f, err, pods, "attempting to delete a newly created flunders(%v) resource", flundersList.Items)
465
466
467
468 contents, err = restClient.Get().AbsPath("/apis/"+apiServiceGroupName+"/"+apiServiceVersion+"/namespaces/default/flunders").SetHeader("Accept", "application/json").DoRaw(ctx)
469 framework.ExpectNoError(err, "confirming delete of a newly created flunders resource")
470 err = json.Unmarshal(contents, &flundersList)
471 validateErrorWithDebugInfo(ctx, f, err, pods, "Error in unmarshalling %T response from server %s", contents, "/apis/"+apiServiceGroupName+"/"+apiServiceVersion)
472 if len(flundersList.Items) != 0 {
473 framework.Failf("failed to get back the correct deleted flunders list %v", flundersList)
474 }
475
476 flunderName = generateFlunderName("dynamic-flunder")
477
478
479 resources, discoveryErr := client.Discovery().ServerPreferredNamespacedResources()
480 groupVersionResources, err := discovery.GroupVersionResources(resources)
481 framework.ExpectNoError(err, "getting group version resources for dynamic client")
482 gvr := schema.GroupVersionResource{Group: apiServiceGroupName, Version: apiServiceVersion, Resource: "flunders"}
483 _, ok := groupVersionResources[gvr]
484 if !ok {
485 framework.Failf("could not find group version resource for dynamic client and wardle/flunders (discovery error: %v, discovery results: %#v)", discoveryErr, groupVersionResources)
486 }
487 dynamicClient := f.DynamicClient.Resource(gvr).Namespace(n.namespace)
488
489
490
491 testFlunder := samplev1alpha1.Flunder{
492 TypeMeta: metav1.TypeMeta{
493 Kind: "Flunder",
494 APIVersion: apiServiceGroupName + "/" + apiServiceVersion,
495 },
496 ObjectMeta: metav1.ObjectMeta{Name: flunderName},
497 Spec: samplev1alpha1.FlunderSpec{},
498 }
499 jsonFlunder, err := json.Marshal(testFlunder)
500 framework.ExpectNoError(err, "marshalling test-flunder for create using dynamic client")
501 unstruct := &unstructured.Unstructured{}
502 err = unstruct.UnmarshalJSON(jsonFlunder)
503 framework.ExpectNoError(err, "unmarshalling test-flunder as unstructured for create using dynamic client")
504 _, err = dynamicClient.Create(ctx, unstruct, metav1.CreateOptions{})
505 framework.ExpectNoError(err, "listing flunders using dynamic client")
506
507
508 unstructuredList, err := dynamicClient.List(ctx, metav1.ListOptions{})
509 framework.ExpectNoError(err, "listing flunders using dynamic client")
510 if len(unstructuredList.Items) != 1 {
511 framework.Failf("failed to get back the correct flunders list %v from the dynamic client", unstructuredList)
512 }
513
514 ginkgo.By("Read Status for " + apiServiceName)
515 statusContent, err := restClient.Get().
516 AbsPath("/apis/apiregistration.k8s.io/v1/apiservices/"+apiServiceName+"/status").
517 SetHeader("Accept", "application/json").DoRaw(ctx)
518 framework.ExpectNoError(err, "No response for .../apiservices/"+apiServiceName+"/status. Error: %v", err)
519
520 var jr *apiregistrationv1.APIService
521 err = json.Unmarshal([]byte(statusContent), &jr)
522 framework.ExpectNoError(err, "Failed to process statusContent: %v | err: %v ", string(statusContent), err)
523 gomega.Expect(jr.Status.Conditions[0].Message).To(gomega.Equal("all checks passed"), "The Message returned was %v", jr.Status.Conditions[0].Message)
524
525 ginkgo.By("kubectl patch apiservice " + apiServiceName + " -p '{\"spec\":{\"versionPriority\": 400}}'")
526 patchContent, err := restClient.Patch(types.MergePatchType).
527 AbsPath("/apis/apiregistration.k8s.io/v1/apiservices/"+apiServiceName).
528 SetHeader("Accept", "application/json").
529 Body([]byte(`{"spec":{"versionPriority": 400}}`)).DoRaw(ctx)
530
531 framework.ExpectNoError(err, "Patch failed for .../apiservices/"+apiServiceName+". Error: %v", err)
532 err = json.Unmarshal([]byte(patchContent), &jr)
533 framework.ExpectNoError(err, "Failed to process patchContent: %v | err: %v ", string(patchContent), err)
534 gomega.Expect(jr.Spec.VersionPriority).To(gomega.Equal(int32(400)), "The VersionPriority returned was %d", jr.Spec.VersionPriority)
535
536 ginkgo.By("List APIServices")
537 listApiservices, err := restClient.Get().
538 AbsPath("/apis/apiregistration.k8s.io/v1/apiservices").
539 SetHeader("Accept", "application/json").DoRaw(ctx)
540
541 framework.ExpectNoError(err, "No response for /apis/apiregistration.k8s.io/v1/apiservices Error: %v", err)
542
543 var list *apiregistrationv1.APIServiceList
544 err = json.Unmarshal([]byte(listApiservices), &list)
545 framework.ExpectNoError(err, "Failed to process APIServiceList: %v | err: %v ", list, err)
546
547 locatedWardle := false
548 for _, item := range list.Items {
549 if item.Name == apiServiceName {
550 framework.Logf("Found " + apiServiceName + " in APIServiceList")
551 locatedWardle = true
552 break
553 }
554 }
555 if !locatedWardle {
556 framework.Failf("Unable to find " + apiServiceName + " in APIServiceList")
557 }
558
559
560
561 ginkgo.By("Adding a label to the APIService")
562 apiServiceClient := aggrclient.ApiregistrationV1().APIServices()
563 apiServiceLabel := map[string]string{"e2e-apiservice": "patched"}
564 apiServicePatch, err := json.Marshal(map[string]interface{}{
565 "metadata": map[string]interface{}{
566 "labels": apiServiceLabel,
567 },
568 })
569 framework.ExpectNoError(err, "failed to Marshal APIService JSON patch")
570 _, err = apiServiceClient.Patch(ctx, apiServiceName, types.StrategicMergePatchType, []byte(apiServicePatch), metav1.PatchOptions{})
571 framework.ExpectNoError(err, "failed to patch APIService")
572
573 patchedApiService, err := apiServiceClient.Get(ctx, apiServiceName, metav1.GetOptions{})
574 framework.ExpectNoError(err, "Unable to retrieve api service %s", apiServiceName)
575 framework.Logf("APIService labels: %v", patchedApiService.Labels)
576
577 ginkgo.By("Updating APIService Status")
578 var updatedStatus, wardle *apiregistrationv1.APIService
579
580 err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
581 var statusToUpdate *apiregistrationv1.APIService
582 statusContent, err = restClient.Get().
583 AbsPath("/apis/apiregistration.k8s.io/v1/apiservices/"+apiServiceName+"/status").
584 SetHeader("Accept", "application/json").DoRaw(ctx)
585 framework.ExpectNoError(err, "No response for .../apiservices/"+apiServiceName+"/status. Error: %v", err)
586
587 err = json.Unmarshal([]byte(statusContent), &statusToUpdate)
588 framework.ExpectNoError(err, "Failed to process statusContent: %v | err: %v ", string(statusContent), err)
589
590 statusToUpdate.Status.Conditions = append(statusToUpdate.Status.Conditions, apiregistrationv1.APIServiceCondition{
591 Type: "StatusUpdated",
592 Status: "True",
593 Reason: "E2E",
594 Message: "Set from e2e test",
595 })
596
597 updatedStatus, err = apiServiceClient.UpdateStatus(ctx, statusToUpdate, metav1.UpdateOptions{})
598 return err
599 })
600 framework.ExpectNoError(err, "Failed to update status. %v", err)
601 framework.Logf("updatedStatus.Conditions: %#v", updatedStatus.Status.Conditions)
602
603 ginkgo.By("Confirm that " + apiServiceName + " /status was updated")
604 statusContent, err = restClient.Get().
605 AbsPath("/apis/apiregistration.k8s.io/v1/apiservices/"+apiServiceName+"/status").
606 SetHeader("Accept", "application/json").DoRaw(ctx)
607 framework.ExpectNoError(err, "No response for .../apiservices/"+apiServiceName+"/status. Error: %v", err)
608
609 err = json.Unmarshal([]byte(statusContent), &wardle)
610 framework.ExpectNoError(err, "Failed to process statusContent: %v | err: %v ", string(statusContent), err)
611
612 foundUpdatedStatusCondition := false
613 for _, cond := range wardle.Status.Conditions {
614 if cond.Type == "StatusUpdated" && cond.Reason == "E2E" && cond.Message == "Set from e2e test" {
615 framework.Logf("Found APIService %v with Labels: %v & Condition: %v", wardle.ObjectMeta.Name, wardle.Labels, cond)
616 foundUpdatedStatusCondition = true
617 break
618 } else {
619 framework.Logf("Observed APIService %v with Labels: %v & Condition: %v", wardle.ObjectMeta.Name, wardle.Labels, cond)
620 }
621 }
622 if !foundUpdatedStatusCondition {
623 framework.Failf("The updated status condition was not found in:\n%s", format.Object(wardle.Status.Conditions, 1))
624 }
625 framework.Logf("Found updated status condition for %s", wardle.ObjectMeta.Name)
626
627 ginkgo.By(fmt.Sprintf("Replace APIService %s", apiServiceName))
628 var updatedApiService *apiregistrationv1.APIService
629
630 err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
631 currentApiService, err := apiServiceClient.Get(ctx, apiServiceName, metav1.GetOptions{})
632 framework.ExpectNoError(err, "Unable to get APIService %s", apiServiceName)
633 currentApiService.Labels = map[string]string{
634 apiServiceName: "updated",
635 }
636 updatedApiService, err = apiServiceClient.Update(ctx, currentApiService, metav1.UpdateOptions{})
637 return err
638 })
639 framework.ExpectNoError(err)
640 gomega.Expect(updatedApiService.Labels).To(gomega.HaveKeyWithValue(apiServiceName, "updated"), "should have the updated label but have %q", updatedApiService.Labels[apiServiceName])
641 framework.Logf("Found updated apiService label for %q", apiServiceName)
642
643
644 ginkgo.By(fmt.Sprintf("Delete flunders resource %q", flunderName))
645 err = dynamicClient.Delete(ctx, flunderName, metav1.DeleteOptions{})
646 validateErrorWithDebugInfo(ctx, f, err, pods, "deleting flunders(%v) using dynamic client", unstructuredList.Items)
647
648
649 unstructuredList, err = dynamicClient.List(ctx, metav1.ListOptions{})
650 framework.ExpectNoError(err, "listing flunders using dynamic client")
651 if len(unstructuredList.Items) != 0 {
652 framework.Failf("failed to get back the correct deleted flunders list %v from the dynamic client", unstructuredList)
653 }
654
655 ginkgo.By("Recreating test-flunder before removing endpoint via deleteCollection")
656 jsonFlunder, err = json.Marshal(testFlunder)
657 framework.ExpectNoError(err, "marshalling test-flunder for create using dynamic client")
658 unstruct = &unstructured.Unstructured{}
659 err = unstruct.UnmarshalJSON(jsonFlunder)
660 framework.ExpectNoError(err, "unmarshalling test-flunder as unstructured for create using dynamic client")
661 _, err = dynamicClient.Create(ctx, unstruct, metav1.CreateOptions{})
662 framework.ExpectNoError(err, "listing flunders using dynamic client")
663
664
665 unstructuredList, err = dynamicClient.List(ctx, metav1.ListOptions{})
666 framework.ExpectNoError(err, "listing flunders using dynamic client")
667 if len(unstructuredList.Items) != 1 {
668 framework.Failf("failed to get back the correct flunders list %v from the dynamic client", unstructuredList)
669 }
670
671 ginkgo.By("Read " + apiServiceName + " /status before patching it")
672 statusContent, err = restClient.Get().
673 AbsPath("/apis/apiregistration.k8s.io/v1/apiservices/"+apiServiceName+"/status").
674 SetHeader("Accept", "application/json").DoRaw(ctx)
675 framework.ExpectNoError(err, "No response for .../apiservices/"+apiServiceName+"/status. Error: %v", err)
676
677 wardle.Reset()
678 err = json.Unmarshal([]byte(statusContent), &wardle)
679 framework.ExpectNoError(err, "Failed to process statusContent: %v | err: %v ", string(statusContent), err)
680
681 ginkgo.By("Patch APIService Status")
682 patch := map[string]interface{}{
683 "status": map[string]interface{}{
684 "conditions": append(wardle.Status.Conditions, apiregistrationv1.APIServiceCondition{
685 Type: "StatusPatched",
686 Status: "True",
687 Reason: "E2E",
688 Message: "Set by e2e test",
689 }),
690 },
691 }
692 payload, err := json.Marshal(patch)
693 framework.ExpectNoError(err, "Failed to marshal JSON. %v", err)
694
695 _, err = restClient.Patch(types.MergePatchType).
696 AbsPath("/apis/apiregistration.k8s.io/v1/apiservices/"+apiServiceName+"/status").
697 SetHeader("Accept", "application/json").
698 Body([]byte(payload)).
699 DoRaw(ctx)
700 framework.ExpectNoError(err, "Patch failed for .../apiservices/"+apiServiceName+"/status. Error: %v", err)
701
702 ginkgo.By("Confirm that " + apiServiceName + " /status was patched")
703 statusContent, err = restClient.Get().
704 AbsPath("/apis/apiregistration.k8s.io/v1/apiservices/"+apiServiceName+"/status").
705 SetHeader("Accept", "application/json").DoRaw(ctx)
706 framework.ExpectNoError(err, "No response for .../apiservices/"+apiServiceName+"/status. Error: %v", err)
707
708 wardle.Reset()
709 err = json.Unmarshal([]byte(statusContent), &wardle)
710 framework.ExpectNoError(err, "Failed to process statusContent: %v | err: %v ", string(statusContent), err)
711
712 foundPatchedStatusCondition := false
713 for _, cond := range wardle.Status.Conditions {
714 if cond.Type == "StatusPatched" && cond.Reason == "E2E" && cond.Message == "Set by e2e test" {
715 framework.Logf("Found APIService %v with Labels: %v & Conditions: %v", wardle.ObjectMeta.Name, wardle.Labels, cond)
716 foundPatchedStatusCondition = true
717 break
718 } else {
719 framework.Logf("Observed APIService %v with Labels: %v & Conditions: %v", wardle.ObjectMeta.Name, wardle.Labels, cond)
720 }
721 }
722 if !foundPatchedStatusCondition {
723 framework.Failf("The patched status condition was not found in:\n%s", format.Object(wardle.Status.Conditions, 1))
724 }
725 framework.Logf("Found patched status condition for %s", wardle.ObjectMeta.Name)
726
727 apiServiceLabelSelector := labels.SelectorFromSet(updatedApiService.Labels).String()
728 ginkgo.By(fmt.Sprintf("APIService deleteCollection with labelSelector: %q", apiServiceLabelSelector))
729
730 err = aggrclient.ApiregistrationV1().APIServices().DeleteCollection(ctx,
731 metav1.DeleteOptions{},
732 metav1.ListOptions{LabelSelector: apiServiceLabelSelector})
733 framework.ExpectNoError(err, "Unable to delete apiservice %s", apiServiceName)
734
735 ginkgo.By("Confirm that the generated APIService has been deleted")
736 err = wait.PollImmediate(apiServiceRetryPeriod, apiServiceRetryTimeout, checkApiServiceListQuantity(ctx, aggrclient, apiServiceLabelSelector, 0))
737 framework.ExpectNoError(err, "failed to count the required APIServices")
738 framework.Logf("APIService %s has been deleted.", apiServiceName)
739
740 cleanupSampleAPIServer(ctx, client, aggrclient, n, apiServiceName)
741 }
742
743
744
745
746 func pollTimed(ctx context.Context, interval, timeout time.Duration, condition wait.ConditionWithContextFunc, msg string) error {
747 defer func(start time.Time, msg string) {
748 elapsed := time.Since(start)
749 framework.Logf(msg, elapsed)
750 }(time.Now(), msg)
751 return wait.PollWithContext(ctx, interval, timeout, condition)
752 }
753
754 func validateErrorWithDebugInfo(ctx context.Context, f *framework.Framework, err error, pods *v1.PodList, msg string, fields ...interface{}) {
755 if err != nil {
756 namespace := f.Namespace.Name
757 msg := fmt.Sprintf(msg, fields...)
758 msg += fmt.Sprintf(" but received unexpected error:\n%v", err)
759 client := f.ClientSet
760 ep, err := client.CoreV1().Endpoints(namespace).Get(ctx, "sample-api", metav1.GetOptions{})
761 if err == nil {
762 msg += fmt.Sprintf("\nFound endpoints for sample-api:\n%v", ep)
763 }
764 pds, err := client.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{})
765 if err == nil {
766 msg += fmt.Sprintf("\nFound pods in %s:\n%v", namespace, pds)
767 msg += fmt.Sprintf("\nOriginal pods in %s:\n%v", namespace, pods)
768 }
769
770 framework.Failf(msg)
771 }
772 }
773
774 func generateFlunderName(base string) string {
775 id, err := rand.Int(rand.Reader, big.NewInt(2147483647))
776 if err != nil {
777 return base
778 }
779 return fmt.Sprintf("%s-%d", base, id)
780 }
781
782 func checkApiServiceListQuantity(ctx context.Context, aggrclient *aggregatorclient.Clientset, label string, quantity int) func() (bool, error) {
783 return func() (bool, error) {
784 var err error
785
786 framework.Logf("Requesting list of APIServices to confirm quantity")
787
788 list, err := aggrclient.ApiregistrationV1().APIServices().List(ctx, metav1.ListOptions{LabelSelector: label})
789 if err != nil {
790 return false, err
791 }
792
793 if len(list.Items) != quantity {
794 return false, err
795 }
796 framework.Logf("Found %d APIService with label %q", quantity, label)
797 return true, nil
798 }
799 }
800
View as plain text