aggregator.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. /*
  2. Copyright 2017 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package apimachinery
  14. import (
  15. "context"
  16. "crypto/rand"
  17. "encoding/json"
  18. "fmt"
  19. "math/big"
  20. "net"
  21. "strings"
  22. "time"
  23. appsv1 "k8s.io/api/apps/v1"
  24. v1 "k8s.io/api/core/v1"
  25. rbacv1 "k8s.io/api/rbac/v1"
  26. apierrors "k8s.io/apimachinery/pkg/api/errors"
  27. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  28. "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
  29. unstructuredv1 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
  30. "k8s.io/apimachinery/pkg/runtime/schema"
  31. "k8s.io/apimachinery/pkg/util/intstr"
  32. "k8s.io/apimachinery/pkg/util/wait"
  33. "k8s.io/client-go/discovery"
  34. clientset "k8s.io/client-go/kubernetes"
  35. apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
  36. aggregatorclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
  37. rbacv1helpers "k8s.io/kubernetes/pkg/apis/rbac/v1"
  38. "k8s.io/kubernetes/test/e2e/framework"
  39. e2edeploy "k8s.io/kubernetes/test/e2e/framework/deployment"
  40. e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
  41. imageutils "k8s.io/kubernetes/test/utils/image"
  42. samplev1alpha1 "k8s.io/sample-apiserver/pkg/apis/wardle/v1alpha1"
  43. "k8s.io/utils/pointer"
  44. "github.com/onsi/ginkgo"
  45. )
  46. const (
  47. aggregatorServicePort = 7443
  48. )
  49. var _ = SIGDescribe("Aggregator", func() {
  50. var ns string
  51. var c clientset.Interface
  52. var aggrclient *aggregatorclient.Clientset
  53. // BeforeEachs run in LIFO order, AfterEachs run in FIFO order.
  54. // We want cleanTest to happen before the namespace cleanup AfterEach
  55. // inserted by NewDefaultFramework, so we put this AfterEach in front
  56. // of NewDefaultFramework.
  57. ginkgo.AfterEach(func() {
  58. cleanTest(c, aggrclient, ns)
  59. })
  60. f := framework.NewDefaultFramework("aggregator")
  61. // We want namespace initialization BeforeEach inserted by
  62. // NewDefaultFramework to happen before this, so we put this BeforeEach
  63. // after NewDefaultFramework.
  64. ginkgo.BeforeEach(func() {
  65. c = f.ClientSet
  66. ns = f.Namespace.Name
  67. if aggrclient == nil {
  68. config, err := framework.LoadConfig()
  69. if err != nil {
  70. framework.Failf("could not load config: %v", err)
  71. }
  72. aggrclient, err = aggregatorclient.NewForConfig(config)
  73. if err != nil {
  74. framework.Failf("could not create aggregator client: %v", err)
  75. }
  76. }
  77. })
  78. /*
  79. Testname: aggregator-supports-the-sample-apiserver
  80. Description: Ensure that the sample-apiserver code from 1.17 and compiled against 1.17
  81. will work on the current Aggregator/API-Server.
  82. */
  83. framework.ConformanceIt("Should be able to support the 1.17 Sample API Server using the current Aggregator", func() {
  84. // Testing a 1.17 version of the sample-apiserver
  85. TestSampleAPIServer(f, aggrclient, imageutils.GetE2EImage(imageutils.APIServer))
  86. })
  87. })
  88. func cleanTest(client clientset.Interface, aggrclient *aggregatorclient.Clientset, namespace string) {
  89. // delete the APIService first to avoid causing discovery errors
  90. _ = aggrclient.ApiregistrationV1().APIServices().Delete(context.TODO(), "v1alpha1.wardle.example.com", nil)
  91. _ = client.AppsV1().Deployments(namespace).Delete(context.TODO(), "sample-apiserver-deployment", nil)
  92. _ = client.CoreV1().Secrets(namespace).Delete(context.TODO(), "sample-apiserver-secret", nil)
  93. _ = client.CoreV1().Services(namespace).Delete(context.TODO(), "sample-api", nil)
  94. _ = client.CoreV1().ServiceAccounts(namespace).Delete(context.TODO(), "sample-apiserver", nil)
  95. _ = client.RbacV1().RoleBindings("kube-system").Delete(context.TODO(), "wardler-auth-reader", nil)
  96. _ = client.RbacV1().ClusterRoleBindings().Delete(context.TODO(), "wardler:"+namespace+":auth-delegator", nil)
  97. _ = client.RbacV1().ClusterRoles().Delete(context.TODO(), "sample-apiserver-reader", nil)
  98. _ = client.RbacV1().ClusterRoleBindings().Delete(context.TODO(), "wardler:"+namespace+":sample-apiserver-reader", nil)
  99. }
  100. // TestSampleAPIServer is a basic test if the sample-apiserver code from 1.10 and compiled against 1.10
  101. // will work on the current Aggregator/API-Server.
  102. func TestSampleAPIServer(f *framework.Framework, aggrclient *aggregatorclient.Clientset, image string) {
  103. ginkgo.By("Registering the sample API server.")
  104. client := f.ClientSet
  105. restClient := client.Discovery().RESTClient()
  106. namespace := f.Namespace.Name
  107. certCtx := setupServerCert(namespace, "sample-api")
  108. // kubectl create -f namespace.yaml
  109. // NOTE: aggregated apis should generally be set up in their own namespace. As the test framework is setting up a new namespace, we are just using that.
  110. // kubectl create -f secret.yaml
  111. secretName := "sample-apiserver-secret"
  112. secret := &v1.Secret{
  113. ObjectMeta: metav1.ObjectMeta{
  114. Name: secretName,
  115. },
  116. Type: v1.SecretTypeOpaque,
  117. Data: map[string][]byte{
  118. "tls.crt": certCtx.cert,
  119. "tls.key": certCtx.key,
  120. },
  121. }
  122. _, err := client.CoreV1().Secrets(namespace).Create(context.TODO(), secret, metav1.CreateOptions{})
  123. framework.ExpectNoError(err, "creating secret %q in namespace %q", secretName, namespace)
  124. // kubectl create -f clusterrole.yaml
  125. _, err = client.RbacV1().ClusterRoles().Create(context.TODO(), &rbacv1.ClusterRole{
  126. ObjectMeta: metav1.ObjectMeta{Name: "sample-apiserver-reader"},
  127. Rules: []rbacv1.PolicyRule{
  128. rbacv1helpers.NewRule("get", "list", "watch").Groups("").Resources("namespaces").RuleOrDie(),
  129. rbacv1helpers.NewRule("get", "list", "watch").Groups("admissionregistration.k8s.io").Resources("*").RuleOrDie(),
  130. },
  131. }, metav1.CreateOptions{})
  132. framework.ExpectNoError(err, "creating cluster role %s", "sample-apiserver-reader")
  133. _, err = client.RbacV1().ClusterRoleBindings().Create(context.TODO(), &rbacv1.ClusterRoleBinding{
  134. ObjectMeta: metav1.ObjectMeta{
  135. Name: "wardler:" + namespace + ":sample-apiserver-reader",
  136. },
  137. RoleRef: rbacv1.RoleRef{
  138. APIGroup: "rbac.authorization.k8s.io",
  139. Kind: "ClusterRole",
  140. Name: "sample-apiserver-reader",
  141. },
  142. Subjects: []rbacv1.Subject{
  143. {
  144. APIGroup: "",
  145. Kind: "ServiceAccount",
  146. Name: "default",
  147. Namespace: namespace,
  148. },
  149. },
  150. }, metav1.CreateOptions{})
  151. framework.ExpectNoError(err, "creating cluster role binding %s", "wardler:"+namespace+":sample-apiserver-reader")
  152. // kubectl create -f authDelegator.yaml
  153. _, err = client.RbacV1().ClusterRoleBindings().Create(context.TODO(), &rbacv1.ClusterRoleBinding{
  154. ObjectMeta: metav1.ObjectMeta{
  155. Name: "wardler:" + namespace + ":auth-delegator",
  156. },
  157. RoleRef: rbacv1.RoleRef{
  158. APIGroup: "rbac.authorization.k8s.io",
  159. Kind: "ClusterRole",
  160. Name: "system:auth-delegator",
  161. },
  162. Subjects: []rbacv1.Subject{
  163. {
  164. APIGroup: "",
  165. Kind: "ServiceAccount",
  166. Name: "default",
  167. Namespace: namespace,
  168. },
  169. },
  170. }, metav1.CreateOptions{})
  171. framework.ExpectNoError(err, "creating cluster role binding %s", "wardler:"+namespace+":auth-delegator")
  172. // kubectl create -f deploy.yaml
  173. deploymentName := "sample-apiserver-deployment"
  174. etcdImage := imageutils.GetE2EImage(imageutils.Etcd)
  175. podLabels := map[string]string{"app": "sample-apiserver", "apiserver": "true"}
  176. replicas := int32(1)
  177. zero := int64(0)
  178. etcdLocalhostAddress := "127.0.0.1"
  179. if framework.TestContext.ClusterIsIPv6() {
  180. etcdLocalhostAddress = "::1"
  181. }
  182. etcdURL := fmt.Sprintf("http://%s", net.JoinHostPort(etcdLocalhostAddress, "2379"))
  183. mounts := []v1.VolumeMount{
  184. {
  185. Name: "apiserver-certs",
  186. ReadOnly: true,
  187. MountPath: "/apiserver.local.config/certificates",
  188. },
  189. }
  190. volumes := []v1.Volume{
  191. {
  192. Name: "apiserver-certs",
  193. VolumeSource: v1.VolumeSource{
  194. Secret: &v1.SecretVolumeSource{SecretName: secretName},
  195. },
  196. },
  197. }
  198. containers := []v1.Container{
  199. {
  200. Name: "sample-apiserver",
  201. VolumeMounts: mounts,
  202. Args: []string{
  203. fmt.Sprintf("--etcd-servers=%s", etcdURL),
  204. "--tls-cert-file=/apiserver.local.config/certificates/tls.crt",
  205. "--tls-private-key-file=/apiserver.local.config/certificates/tls.key",
  206. "--audit-log-path=-",
  207. "--audit-log-maxage=0",
  208. "--audit-log-maxbackup=0",
  209. },
  210. Image: image,
  211. },
  212. {
  213. Name: "etcd",
  214. Image: etcdImage,
  215. Command: []string{
  216. "/usr/local/bin/etcd",
  217. "--listen-client-urls",
  218. etcdURL,
  219. "--advertise-client-urls",
  220. etcdURL,
  221. },
  222. },
  223. }
  224. d := &appsv1.Deployment{
  225. ObjectMeta: metav1.ObjectMeta{
  226. Name: deploymentName,
  227. Labels: podLabels,
  228. },
  229. Spec: appsv1.DeploymentSpec{
  230. Replicas: &replicas,
  231. Selector: &metav1.LabelSelector{
  232. MatchLabels: podLabels,
  233. },
  234. Strategy: appsv1.DeploymentStrategy{
  235. Type: appsv1.RollingUpdateDeploymentStrategyType,
  236. },
  237. Template: v1.PodTemplateSpec{
  238. ObjectMeta: metav1.ObjectMeta{
  239. Labels: podLabels,
  240. },
  241. Spec: v1.PodSpec{
  242. TerminationGracePeriodSeconds: &zero,
  243. Containers: containers,
  244. Volumes: volumes,
  245. },
  246. },
  247. },
  248. }
  249. deployment, err := client.AppsV1().Deployments(namespace).Create(context.TODO(), d, metav1.CreateOptions{})
  250. framework.ExpectNoError(err, "creating deployment %s in namespace %s", deploymentName, namespace)
  251. err = e2edeploy.WaitForDeploymentRevisionAndImage(client, namespace, deploymentName, "1", image)
  252. framework.ExpectNoError(err, "waiting for the deployment of image %s in %s in %s to complete", image, deploymentName, namespace)
  253. err = e2edeploy.WaitForDeploymentRevisionAndImage(client, namespace, deploymentName, "1", etcdImage)
  254. framework.ExpectNoError(err, "waiting for the deployment of image %s in %s to complete", etcdImage, deploymentName, namespace)
  255. // kubectl create -f service.yaml
  256. serviceLabels := map[string]string{"apiserver": "true"}
  257. service := &v1.Service{
  258. ObjectMeta: metav1.ObjectMeta{
  259. Namespace: namespace,
  260. Name: "sample-api",
  261. Labels: map[string]string{"test": "aggregator"},
  262. },
  263. Spec: v1.ServiceSpec{
  264. Selector: serviceLabels,
  265. Ports: []v1.ServicePort{
  266. {
  267. Protocol: "TCP",
  268. Port: aggregatorServicePort,
  269. TargetPort: intstr.FromInt(443),
  270. },
  271. },
  272. },
  273. }
  274. _, err = client.CoreV1().Services(namespace).Create(context.TODO(), service, metav1.CreateOptions{})
  275. framework.ExpectNoError(err, "creating service %s in namespace %s", "sample-apiserver", namespace)
  276. // kubectl create -f serviceAccount.yaml
  277. sa := &v1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: "sample-apiserver"}}
  278. _, err = client.CoreV1().ServiceAccounts(namespace).Create(context.TODO(), sa, metav1.CreateOptions{})
  279. framework.ExpectNoError(err, "creating service account %s in namespace %s", "sample-apiserver", namespace)
  280. // kubectl create -f auth-reader.yaml
  281. _, err = client.RbacV1().RoleBindings("kube-system").Create(context.TODO(), &rbacv1.RoleBinding{
  282. ObjectMeta: metav1.ObjectMeta{
  283. Name: "wardler-auth-reader",
  284. Annotations: map[string]string{
  285. rbacv1.AutoUpdateAnnotationKey: "true",
  286. },
  287. },
  288. RoleRef: rbacv1.RoleRef{
  289. APIGroup: "",
  290. Kind: "Role",
  291. Name: "extension-apiserver-authentication-reader",
  292. },
  293. Subjects: []rbacv1.Subject{
  294. {
  295. Kind: "ServiceAccount",
  296. Name: "default",
  297. Namespace: namespace,
  298. },
  299. },
  300. }, metav1.CreateOptions{})
  301. framework.ExpectNoError(err, "creating role binding %s:sample-apiserver to access configMap", namespace)
  302. // Wait for the extension apiserver to be up and healthy
  303. // kubectl get deployments -n <aggregated-api-namespace> && status == Running
  304. // NOTE: aggregated apis should generally be set up in their own namespace (<aggregated-api-namespace>). As the test framework
  305. // is setting up a new namespace, we are just using that.
  306. err = e2edeploy.WaitForDeploymentComplete(client, deployment)
  307. framework.ExpectNoError(err, "deploying extension apiserver in namespace %s", namespace)
  308. // kubectl create -f apiservice.yaml
  309. _, err = aggrclient.ApiregistrationV1().APIServices().Create(context.TODO(), &apiregistrationv1.APIService{
  310. ObjectMeta: metav1.ObjectMeta{Name: "v1alpha1.wardle.example.com"},
  311. Spec: apiregistrationv1.APIServiceSpec{
  312. Service: &apiregistrationv1.ServiceReference{
  313. Namespace: namespace,
  314. Name: "sample-api",
  315. Port: pointer.Int32Ptr(aggregatorServicePort),
  316. },
  317. Group: "wardle.example.com",
  318. Version: "v1alpha1",
  319. CABundle: certCtx.signingCert,
  320. GroupPriorityMinimum: 2000,
  321. VersionPriority: 200,
  322. },
  323. }, metav1.CreateOptions{})
  324. framework.ExpectNoError(err, "creating apiservice %s with namespace %s", "v1alpha1.wardle.example.com", namespace)
  325. var (
  326. currentAPIService *apiregistrationv1.APIService
  327. currentPods *v1.PodList
  328. )
  329. err = pollTimed(100*time.Millisecond, 60*time.Second, func() (bool, error) {
  330. currentAPIService, _ = aggrclient.ApiregistrationV1().APIServices().Get(context.TODO(), "v1alpha1.wardle.example.com", metav1.GetOptions{})
  331. currentPods, _ = client.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{})
  332. request := restClient.Get().AbsPath("/apis/wardle.example.com/v1alpha1/namespaces/default/flunders")
  333. request.SetHeader("Accept", "application/json")
  334. _, err := request.DoRaw(context.TODO())
  335. if err != nil {
  336. status, ok := err.(*apierrors.StatusError)
  337. if !ok {
  338. return false, err
  339. }
  340. if status.Status().Code == 503 {
  341. return false, nil
  342. }
  343. if status.Status().Code == 404 && strings.HasPrefix(err.Error(), "the server could not find the requested resource") {
  344. return false, nil
  345. }
  346. return false, err
  347. }
  348. return true, nil
  349. }, "Waited %s for the sample-apiserver to be ready to handle requests.")
  350. if err != nil {
  351. currentAPIServiceJSON, _ := json.Marshal(currentAPIService)
  352. framework.Logf("current APIService: %s", string(currentAPIServiceJSON))
  353. currentPodsJSON, _ := json.Marshal(currentPods)
  354. framework.Logf("current pods: %s", string(currentPodsJSON))
  355. if currentPods != nil {
  356. for _, pod := range currentPods.Items {
  357. for _, container := range pod.Spec.Containers {
  358. logs, err := e2epod.GetPodLogs(client, namespace, pod.Name, container.Name)
  359. framework.Logf("logs of %s/%s (error: %v): %s", pod.Name, container.Name, err, logs)
  360. }
  361. }
  362. }
  363. }
  364. framework.ExpectNoError(err, "gave up waiting for apiservice wardle to come up successfully")
  365. flunderName := generateFlunderName("rest-flunder")
  366. // kubectl create -f flunders-1.yaml -v 9
  367. // curl -k -v -XPOST https://localhost/apis/wardle.example.com/v1alpha1/namespaces/default/flunders
  368. // Request Body: {"apiVersion":"wardle.example.com/v1alpha1","kind":"Flunder","metadata":{"labels":{"sample-label":"true"},"name":"test-flunder","namespace":"default"}}
  369. flunder := `{"apiVersion":"wardle.example.com/v1alpha1","kind":"Flunder","metadata":{"labels":{"sample-label":"true"},"name":"` + flunderName + `","namespace":"default"}}`
  370. result := restClient.Post().AbsPath("/apis/wardle.example.com/v1alpha1/namespaces/default/flunders").Body([]byte(flunder)).SetHeader("Accept", "application/json").Do(context.TODO())
  371. framework.ExpectNoError(result.Error(), "creating a new flunders resource")
  372. var statusCode int
  373. result.StatusCode(&statusCode)
  374. if statusCode != 201 {
  375. framework.Failf("Flunders client creation response was status %d, not 201", statusCode)
  376. }
  377. u := &unstructured.Unstructured{}
  378. if err := result.Into(u); err != nil {
  379. framework.ExpectNoError(err, "reading created response")
  380. }
  381. framework.ExpectEqual(u.GetAPIVersion(), "wardle.example.com/v1alpha1")
  382. framework.ExpectEqual(u.GetKind(), "Flunder")
  383. framework.ExpectEqual(u.GetName(), flunderName)
  384. pods, err := client.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{})
  385. framework.ExpectNoError(err, "getting pods for flunders service")
  386. // kubectl get flunders -v 9
  387. // curl -k -v -XGET https://localhost/apis/wardle.example.com/v1alpha1/namespaces/default/flunders
  388. contents, err := restClient.Get().AbsPath("/apis/wardle.example.com/v1alpha1/namespaces/default/flunders").SetHeader("Accept", "application/json").DoRaw(context.TODO())
  389. framework.ExpectNoError(err, "attempting to get a newly created flunders resource")
  390. var flundersList samplev1alpha1.FlunderList
  391. err = json.Unmarshal(contents, &flundersList)
  392. validateErrorWithDebugInfo(f, err, pods, "Error in unmarshalling %T response from server %s", contents, "/apis/wardle.example.com/v1alpha1")
  393. if len(flundersList.Items) != 1 {
  394. framework.Failf("failed to get back the correct flunders list %v", flundersList)
  395. }
  396. // kubectl delete flunder test-flunder -v 9
  397. // curl -k -v -XDELETE https://35.193.112.40/apis/wardle.example.com/v1alpha1/namespaces/default/flunders/test-flunder
  398. _, err = restClient.Delete().AbsPath("/apis/wardle.example.com/v1alpha1/namespaces/default/flunders/" + flunderName).DoRaw(context.TODO())
  399. validateErrorWithDebugInfo(f, err, pods, "attempting to delete a newly created flunders(%v) resource", flundersList.Items)
  400. // kubectl get flunders -v 9
  401. // curl -k -v -XGET https://localhost/apis/wardle.example.com/v1alpha1/namespaces/default/flunders
  402. contents, err = restClient.Get().AbsPath("/apis/wardle.example.com/v1alpha1/namespaces/default/flunders").SetHeader("Accept", "application/json").DoRaw(context.TODO())
  403. framework.ExpectNoError(err, "confirming delete of a newly created flunders resource")
  404. err = json.Unmarshal(contents, &flundersList)
  405. validateErrorWithDebugInfo(f, err, pods, "Error in unmarshalling %T response from server %s", contents, "/apis/wardle.example.com/v1alpha1")
  406. if len(flundersList.Items) != 0 {
  407. framework.Failf("failed to get back the correct deleted flunders list %v", flundersList)
  408. }
  409. flunderName = generateFlunderName("dynamic-flunder")
  410. // Rerun the Create/List/Delete tests using the Dynamic client.
  411. resources, discoveryErr := client.Discovery().ServerPreferredNamespacedResources()
  412. groupVersionResources, err := discovery.GroupVersionResources(resources)
  413. framework.ExpectNoError(err, "getting group version resources for dynamic client")
  414. gvr := schema.GroupVersionResource{Group: "wardle.example.com", Version: "v1alpha1", Resource: "flunders"}
  415. _, ok := groupVersionResources[gvr]
  416. if !ok {
  417. framework.Failf("could not find group version resource for dynamic client and wardle/flunders (discovery error: %v, discovery results: %#v)", discoveryErr, groupVersionResources)
  418. }
  419. dynamicClient := f.DynamicClient.Resource(gvr).Namespace(namespace)
  420. // kubectl create -f flunders-1.yaml
  421. // Request Body: {"apiVersion":"wardle.example.com/v1alpha1","kind":"Flunder","metadata":{"labels":{"sample-label":"true"},"name":"test-flunder","namespace":"default"}}
  422. testFlunder := samplev1alpha1.Flunder{
  423. TypeMeta: metav1.TypeMeta{
  424. Kind: "Flunder",
  425. APIVersion: "wardle.example.com/v1alpha1",
  426. },
  427. ObjectMeta: metav1.ObjectMeta{Name: flunderName},
  428. Spec: samplev1alpha1.FlunderSpec{},
  429. }
  430. jsonFlunder, err := json.Marshal(testFlunder)
  431. framework.ExpectNoError(err, "marshalling test-flunder for create using dynamic client")
  432. unstruct := &unstructuredv1.Unstructured{}
  433. err = unstruct.UnmarshalJSON(jsonFlunder)
  434. framework.ExpectNoError(err, "unmarshalling test-flunder as unstructured for create using dynamic client")
  435. _, err = dynamicClient.Create(unstruct, metav1.CreateOptions{})
  436. framework.ExpectNoError(err, "listing flunders using dynamic client")
  437. // kubectl get flunders
  438. unstructuredList, err := dynamicClient.List(metav1.ListOptions{})
  439. framework.ExpectNoError(err, "listing flunders using dynamic client")
  440. if len(unstructuredList.Items) != 1 {
  441. framework.Failf("failed to get back the correct flunders list %v from the dynamic client", unstructuredList)
  442. }
  443. // kubectl delete flunder test-flunder
  444. err = dynamicClient.Delete(flunderName, &metav1.DeleteOptions{})
  445. validateErrorWithDebugInfo(f, err, pods, "deleting flunders(%v) using dynamic client", unstructuredList.Items)
  446. // kubectl get flunders
  447. unstructuredList, err = dynamicClient.List(metav1.ListOptions{})
  448. framework.ExpectNoError(err, "listing flunders using dynamic client")
  449. if len(unstructuredList.Items) != 0 {
  450. framework.Failf("failed to get back the correct deleted flunders list %v from the dynamic client", unstructuredList)
  451. }
  452. cleanTest(client, aggrclient, namespace)
  453. }
  454. // pollTimed will call Poll but time how long Poll actually took.
  455. // It will then framework.Logf the msg with the duration of the Poll.
  456. // It is assumed that msg will contain one %s for the elapsed time.
  457. func pollTimed(interval, timeout time.Duration, condition wait.ConditionFunc, msg string) error {
  458. defer func(start time.Time, msg string) {
  459. elapsed := time.Since(start)
  460. framework.Logf(msg, elapsed)
  461. }(time.Now(), msg)
  462. return wait.Poll(interval, timeout, condition)
  463. }
  464. func validateErrorWithDebugInfo(f *framework.Framework, err error, pods *v1.PodList, msg string, fields ...interface{}) {
  465. if err != nil {
  466. namespace := f.Namespace.Name
  467. msg := fmt.Sprintf(msg, fields...)
  468. msg += fmt.Sprintf(" but received unexpected error:\n%v", err)
  469. client := f.ClientSet
  470. ep, err := client.CoreV1().Endpoints(namespace).Get(context.TODO(), "sample-api", metav1.GetOptions{})
  471. if err == nil {
  472. msg += fmt.Sprintf("\nFound endpoints for sample-api:\n%v", ep)
  473. }
  474. pds, err := client.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{})
  475. if err == nil {
  476. msg += fmt.Sprintf("\nFound pods in %s:\n%v", namespace, pds)
  477. msg += fmt.Sprintf("\nOriginal pods in %s:\n%v", namespace, pods)
  478. }
  479. framework.Failf(msg)
  480. }
  481. }
  482. func generateFlunderName(base string) string {
  483. id, err := rand.Int(rand.Reader, big.NewInt(2147483647))
  484. if err != nil {
  485. return base
  486. }
  487. return fmt.Sprintf("%s-%d", base, id)
  488. }