github.com/interconnectedcloud/qdr-operator@v0.0.0-20210826174505-576d2b33dac7/test/e2e/framework/framework.go (about) 1 // Copyright 2019 The Interconnectedcloud Authors 2 // 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 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package framework 16 17 import ( 18 "fmt" 19 "strings" 20 "time" 21 22 routev1 "github.com/openshift/client-go/route/clientset/versioned" 23 "k8s.io/client-go/dynamic" 24 25 appsv1 "k8s.io/api/apps/v1" 26 corev1 "k8s.io/api/core/v1" 27 rbacv1 "k8s.io/api/rbac/v1" 28 apierrors "k8s.io/apimachinery/pkg/api/errors" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 31 qdrclient "github.com/interconnectedcloud/qdr-operator/pkg/client/clientset/versioned" 32 e2elog "github.com/interconnectedcloud/qdr-operator/test/e2e/framework/log" 33 apiextension "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" 34 clientset "k8s.io/client-go/kubernetes" 35 36 "k8s.io/client-go/tools/clientcmd" 37 38 "github.com/onsi/ginkgo" 39 "github.com/onsi/gomega" 40 "k8s.io/apimachinery/pkg/util/uuid" 41 ) 42 43 const ( 44 qdrOperatorName = "qdr-operator" 45 crdName = "interconnects.interconnectedcloud.github.io" 46 groupName = "interconnectedcloud.github.io" 47 apiVersion = "v1alpha1" 48 ) 49 50 var ( 51 RetryInterval = time.Second * 5 52 Timeout = time.Second * 180 53 TimeoutSuite = time.Second * 1200 54 CleanupRetryInterval = time.Second * 1 55 CleanupTimeout = time.Second * 5 56 GVR = groupName + "/" + apiVersion 57 ) 58 59 type ocpClient struct { 60 RoutesClient *routev1.Clientset 61 } 62 63 type Framework struct { 64 BaseName string 65 66 // Set together with creating the ClientSet and the namespace. 67 // Guaranteed to be unique in the cluster even when running the same 68 // test multiple times in parallel. 69 UniqueName string 70 71 KubeClient clientset.Interface 72 ExtClient apiextension.Interface 73 QdrClient qdrclient.Interface 74 DynClient dynamic.Interface 75 OcpClient ocpClient 76 77 CertManagerPresent bool // if crd is detected 78 SkipNamespaceCreation bool // Whether to skip creating a namespace 79 KeepCRD bool // Whether to preserve CRD on cleanup 80 Namespace string 81 namespacesToDelete []*corev1.Namespace // Some tests have more than one 82 cleanupHandle CleanupActionHandle 83 isOpenShift *bool 84 } 85 86 // NewFramework creates a test framework 87 func NewFramework(baseName string, client clientset.Interface) *Framework { 88 89 f := &Framework{ 90 BaseName: baseName, 91 KubeClient: client, 92 } 93 ginkgo.BeforeEach(f.BeforeEach) 94 ginkgo.AfterEach(f.AfterEach) 95 96 return f 97 } 98 99 // BeforeEach gets clients and makes a namespace 100 func (f *Framework) BeforeEach() { 101 102 f.cleanupHandle = AddCleanupAction(f.AfterEach) 103 104 if f.KubeClient == nil { 105 ginkgo.By("Creating kubernetes clients") 106 config, err := clientcmd.BuildConfigFromFlags("", TestContext.KubeConfig) 107 f.KubeClient, err = clientset.NewForConfig(config) 108 gomega.Expect(err).NotTo(gomega.HaveOccurred()) 109 f.ExtClient, err = apiextension.NewForConfig(config) 110 gomega.Expect(err).NotTo(gomega.HaveOccurred()) 111 f.QdrClient, err = qdrclient.NewForConfig(config) 112 gomega.Expect(err).NotTo(gomega.HaveOccurred()) 113 f.DynClient, err = dynamic.NewForConfig(config) 114 gomega.Expect(err).NotTo(gomega.HaveOccurred()) 115 116 if f.IsOpenShift() { 117 f.OcpClient.RoutesClient, err = routev1.NewForConfig(config) 118 gomega.Expect(err).NotTo(gomega.HaveOccurred()) 119 } 120 } 121 122 if !f.SkipNamespaceCreation { 123 ginkgo.By(fmt.Sprintf("Building namespace api objects, basename %s", f.BaseName)) 124 125 namespaceLabels := map[string]string{ 126 "e2e-framework": f.BaseName, 127 } 128 129 namespace := generateNamespace(f.KubeClient, f.BaseName, namespaceLabels) 130 131 f.AddNamespacesToDelete(namespace) 132 f.Namespace = namespace.GetName() 133 f.UniqueName = namespace.GetName() 134 135 } else { 136 f.UniqueName = string(uuid.NewUUID()) 137 } 138 139 _, err := f.ExtClient.ApiextensionsV1beta1().CustomResourceDefinitions().Get("issuers.certmanager.k8s.io", metav1.GetOptions{}) 140 if err == nil { 141 f.CertManagerPresent = true 142 } 143 144 // setup the operator 145 err = f.Setup() 146 gomega.Expect(err).NotTo(gomega.HaveOccurred()) 147 } 148 149 // AfterEach deletes the namespace, after reading its events. 150 func (f *Framework) AfterEach() { 151 RemoveCleanupAction(f.cleanupHandle) 152 153 // teardown the operator 154 err := f.Teardown() 155 gomega.Expect(err).NotTo(gomega.HaveOccurred()) 156 157 // DeleteNamespace at the very end in defer, to avoid any 158 // expectation failures preventing deleting the namespace. 159 defer func() { 160 nsDeletionErrors := map[string][]error{} 161 // Whether to delete namespace is determined by 3 factors: delete-namespace flag, delete-namespace-on-failure flag and the test result 162 // if delete-namespace set to false, namespace will always be preserved. 163 // if delete-namespace is true and delete-namespace-on-failure is false, namespace will be preserved if test failed. 164 for _, ns := range f.namespacesToDelete { 165 ginkgo.By(fmt.Sprintf("Destroying namespace %q for this suite on all clusters.", ns.Name)) 166 if errors := f.DeleteNamespace(ns); errors != nil { 167 nsDeletionErrors[ns.Name] = errors 168 } 169 } 170 171 // Paranoia-- prevent reuse! 172 f.Namespace = "" 173 f.KubeClient = nil 174 f.namespacesToDelete = nil 175 176 // if we had errors deleting, report them now. 177 if len(nsDeletionErrors) != 0 { 178 messages := []string{} 179 for namespaceKey, namespaceErrors := range nsDeletionErrors { 180 for clusterIdx, namespaceErr := range namespaceErrors { 181 messages = append(messages, fmt.Sprintf("Couldn't delete ns: %q (@cluster %d): %s (%#v)", 182 namespaceKey, clusterIdx, namespaceErr, namespaceErr)) 183 } 184 } 185 e2elog.Failf(strings.Join(messages, ",")) 186 } 187 }() 188 189 } 190 191 func (f *Framework) Teardown() error { 192 193 // Skip the qdr-operator teardown if the operator image was not specified 194 if len(TestContext.OperatorImage) == 0 { 195 return nil 196 } 197 198 err := f.KubeClient.CoreV1().ServiceAccounts(f.Namespace).Delete(qdrOperatorName, metav1.NewDeleteOptions(1)) 199 if err != nil && !apierrors.IsNotFound(err) { 200 return fmt.Errorf("failed to delete qdr-operator service account: %v", err) 201 } 202 err = f.KubeClient.RbacV1().Roles(f.Namespace).Delete(qdrOperatorName, metav1.NewDeleteOptions(1)) 203 if err != nil && !apierrors.IsNotFound(err) { 204 return fmt.Errorf("failed to delete qdr-operator role: %v", err) 205 } 206 err = f.KubeClient.RbacV1().ClusterRoles().Delete(qdrOperatorName, metav1.NewDeleteOptions(1)) 207 if err != nil && !apierrors.IsNotFound(err) { 208 return fmt.Errorf("failed to delete qdr-operator cluster role: %v", err) 209 } 210 err = f.KubeClient.RbacV1().RoleBindings(f.Namespace).Delete(qdrOperatorName, metav1.NewDeleteOptions(1)) 211 if err != nil && !apierrors.IsNotFound(err) { 212 return fmt.Errorf("failed to delete qdr-operator role binding: %v", err) 213 } 214 err = f.KubeClient.RbacV1().ClusterRoleBindings().Delete(qdrOperatorName, metav1.NewDeleteOptions(1)) 215 if err != nil && !apierrors.IsNotFound(err) { 216 return fmt.Errorf("failed to delete qdr-operator cluster role binding: %v", err) 217 } 218 // In cases when CRD was already present, it must be kept 219 if !f.KeepCRD { 220 err = f.ExtClient.ApiextensionsV1beta1().CustomResourceDefinitions().Delete(crdName, metav1.NewDeleteOptions(1)) 221 if err != nil && !apierrors.IsNotFound(err) { 222 return fmt.Errorf("failed to delete qdr-operator crd: %v", err) 223 } 224 } 225 err = f.KubeClient.AppsV1().Deployments(f.Namespace).Delete(qdrOperatorName, metav1.NewDeleteOptions(1)) 226 if err != nil && !apierrors.IsNotFound(err) { 227 return fmt.Errorf("failed to delete qdr-operator deployment: %v", err) 228 } 229 230 e2elog.Logf("e2e teardown succesful") 231 return nil 232 } 233 234 func (f *Framework) Setup() error { 235 236 err := f.setupQdrServiceAccount() 237 if err != nil { 238 return fmt.Errorf("failed to setup qdr operator: %v", err) 239 } 240 err = f.setupQdrRole() 241 if err != nil { 242 return fmt.Errorf("failed to setup qdr operator: %v", err) 243 } 244 err = f.setupQdrClusterRole() 245 if err != nil && !HasAlreadyExistsSuffix(err) { 246 return fmt.Errorf("failed to setup qdr operator: %v", err) 247 } 248 err = f.setupQdrRoleBinding() 249 if err != nil { 250 return fmt.Errorf("failed to setup qdr operator: %v", err) 251 } 252 err = f.setupQdrClusterRoleBinding() 253 if err != nil && !HasAlreadyExistsSuffix(err) { 254 return fmt.Errorf("failed to setup qdr operator: %v", err) 255 } 256 err = f.setupQdrCrd() 257 if err != nil && !HasAlreadyExistsSuffix(err) { 258 return fmt.Errorf("failed to setup qdr operator: %v", err) 259 } else if err != nil { 260 // In case CRD already exists, do not remove on clean up (to preserve original state) 261 f.KeepCRD = true 262 } 263 err = f.setupQdrDeployment() 264 if err != nil { 265 return fmt.Errorf("failed to setup qdr operator: %v", err) 266 } 267 err = WaitForDeployment(f.KubeClient, f.Namespace, "qdr-operator", 1, RetryInterval, Timeout) 268 if err != nil { 269 return fmt.Errorf("Failed to wait for qdr operator: %v", err) 270 } 271 return nil 272 } 273 274 // HasAlreadyExistsSuffix returns true if the string representation of the error 275 // ends with "already exists". 276 func HasAlreadyExistsSuffix(err error) bool { 277 return strings.HasSuffix(strings.ToLower(err.Error()), "already exists") 278 } 279 280 func (f *Framework) setupQdrServiceAccount() error { 281 sa := &corev1.ServiceAccount{ 282 ObjectMeta: metav1.ObjectMeta{ 283 Name: qdrOperatorName, 284 }, 285 } 286 _, err := f.KubeClient.CoreV1().ServiceAccounts(f.Namespace).Create(sa) 287 if err != nil { 288 return fmt.Errorf("create qdr-operator service account failed: %v", err) 289 } 290 return nil 291 } 292 293 func (f *Framework) setupQdrRole() error { 294 role := &rbacv1.Role{ 295 ObjectMeta: metav1.ObjectMeta{ 296 Name: qdrOperatorName, 297 }, 298 Rules: []rbacv1.PolicyRule{ 299 { 300 APIGroups: []string{""}, 301 Resources: []string{"pods", "services", "serviceaccounts", "endpoints", "persistentvolumeclaims", "events", "configmaps", "secrets"}, 302 Verbs: []string{"*"}, 303 }, 304 { 305 APIGroups: []string{"rbac.authorization.k8s.io"}, 306 Resources: []string{"rolebindings", "roles"}, 307 Verbs: []string{"get", "list", "watch", "create", "delete"}, 308 }, 309 { 310 APIGroups: []string{"extensions"}, 311 Resources: []string{"ingresses"}, 312 Verbs: []string{"get", "list", "watch", "create", "delete"}, 313 }, 314 { 315 APIGroups: []string{""}, 316 Resources: []string{"namespaces"}, 317 Verbs: []string{"get"}, 318 }, 319 { 320 APIGroups: []string{"apps"}, 321 Resources: []string{"deployments", "daemonsets", "replicasets", "statefulsets"}, 322 Verbs: []string{"*"}, 323 }, 324 { 325 APIGroups: []string{"certmanager.k8s.io"}, 326 Resources: []string{"issuers", "certificates"}, 327 Verbs: []string{"get", "list", "watch", "create", "delete"}, 328 }, 329 { 330 APIGroups: []string{"monitoring.coreos.com"}, 331 Resources: []string{"servicemonitors"}, 332 Verbs: []string{"get", "create"}, 333 }, 334 { 335 APIGroups: []string{"route.openshift.io"}, 336 Resources: []string{"routes", "routes/custom-host", "routes/status"}, 337 Verbs: []string{"get", "list", "watch", "create", "delete"}, 338 }, 339 { 340 APIGroups: []string{"interconnectedcloud.github.io"}, 341 Resources: []string{"*"}, 342 Verbs: []string{"*"}, 343 }, 344 }, 345 } 346 _, err := f.KubeClient.RbacV1().Roles(f.Namespace).Create(role) 347 if err != nil { 348 return fmt.Errorf("create qdr-operator role failed: %v", err) 349 } 350 return nil 351 } 352 353 func (f *Framework) setupQdrClusterRole() error { 354 crole := &rbacv1.ClusterRole{ 355 ObjectMeta: metav1.ObjectMeta{ 356 Name: qdrOperatorName, 357 }, 358 Rules: []rbacv1.PolicyRule{ 359 { 360 APIGroups: []string{"apiextensions.k8s.io"}, 361 Resources: []string{"customresourcedefinitions"}, 362 Verbs: []string{"get", "list"}, 363 }, 364 }, 365 } 366 _, err := f.KubeClient.RbacV1().ClusterRoles().Create(crole) 367 if err != nil { 368 return fmt.Errorf("create qdr-operator cluster role failed: %v", err) 369 } 370 return nil 371 } 372 373 func (f *Framework) setupQdrRoleBinding() error { 374 rb := &rbacv1.RoleBinding{ 375 ObjectMeta: metav1.ObjectMeta{ 376 Name: qdrOperatorName, 377 }, 378 RoleRef: rbacv1.RoleRef{ 379 APIGroup: "rbac.authorization.k8s.io", 380 Kind: "Role", 381 Name: qdrOperatorName, 382 }, 383 Subjects: []rbacv1.Subject{ 384 { 385 APIGroup: "", 386 Kind: "ServiceAccount", 387 Name: qdrOperatorName, 388 Namespace: f.Namespace, 389 }, 390 }, 391 } 392 _, err := f.KubeClient.RbacV1().RoleBindings(f.Namespace).Create(rb) 393 if err != nil { 394 return fmt.Errorf("create qdr-operator role binding failed: %v", err) 395 } 396 return nil 397 } 398 399 func (f *Framework) setupQdrClusterRoleBinding() error { 400 crb := &rbacv1.ClusterRoleBinding{ 401 ObjectMeta: metav1.ObjectMeta{ 402 Name: qdrOperatorName, 403 }, 404 RoleRef: rbacv1.RoleRef{ 405 APIGroup: "rbac.authorization.k8s.io", 406 Kind: "ClusterRole", 407 Name: qdrOperatorName, 408 }, 409 Subjects: []rbacv1.Subject{ 410 { 411 APIGroup: "", 412 Kind: "ServiceAccount", 413 Name: qdrOperatorName, 414 Namespace: f.Namespace, 415 }, 416 }, 417 } 418 _, err := f.KubeClient.RbacV1().ClusterRoleBindings().Create(crb) 419 if err != nil { 420 return fmt.Errorf("create qdr-operator cluster role binding failed: %v", err) 421 } 422 return nil 423 } 424 425 func (f *Framework) setupQdrCrd() error { 426 return CreateResourcesFromYAML(f.KubeClient, f.DynClient, f.Namespace, "https://raw.githubusercontent.com/interconnectedcloud/qdr-operator/master/deploy/crds/interconnectedcloud_v1alpha1_interconnect_crd.yaml") 427 } 428 429 func (f *Framework) setupQdrDeployment() error { 430 dep := &appsv1.Deployment{ 431 TypeMeta: metav1.TypeMeta{ 432 APIVersion: "apps/v1", 433 Kind: "Deployment", 434 }, 435 ObjectMeta: metav1.ObjectMeta{ 436 Name: qdrOperatorName, 437 }, 438 Spec: appsv1.DeploymentSpec{ 439 Replicas: int32Ptr(1), 440 Selector: &metav1.LabelSelector{ 441 MatchLabels: map[string]string{ 442 "name": qdrOperatorName, 443 }, 444 }, 445 Template: corev1.PodTemplateSpec{ 446 ObjectMeta: metav1.ObjectMeta{ 447 Labels: map[string]string{ 448 "name": qdrOperatorName, 449 }, 450 }, 451 Spec: corev1.PodSpec{ 452 ServiceAccountName: qdrOperatorName, 453 Containers: []corev1.Container{ 454 { 455 Command: []string{qdrOperatorName}, 456 Name: qdrOperatorName, 457 Image: TestContext.OperatorImage, 458 ImagePullPolicy: corev1.PullAlways, 459 Env: []corev1.EnvVar{ 460 { 461 Name: "WATCH_NAMESPACE", 462 ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}}, 463 }, 464 { 465 Name: "POD_NAME", 466 ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}}, 467 }, 468 { 469 Name: "OPERATOR_NAME", 470 Value: qdrOperatorName, 471 }, 472 }, 473 Ports: []corev1.ContainerPort{ 474 { 475 Name: "metrics", 476 ContainerPort: 60000, 477 }, 478 }, 479 }, 480 }, 481 }, 482 }, 483 }, 484 } 485 _, err := f.KubeClient.AppsV1().Deployments(f.Namespace).Create(dep) 486 if err != nil { 487 return fmt.Errorf("create qdr-operator deployment failed: %v", err) 488 } 489 return nil 490 } 491 492 func (f *Framework) IsOpenShift() bool { 493 if f.isOpenShift != nil { 494 return *f.isOpenShift 495 } 496 497 result := false 498 apiList, err := f.KubeClient.Discovery().ServerGroups() 499 if err != nil { 500 e2elog.Failf("Error in getting ServerGroups from discovery client, returning false") 501 result = false 502 f.isOpenShift = &result 503 return result 504 } 505 506 for _, v := range apiList.Groups { 507 if v.Name == "route.openshift.io" { 508 e2elog.Logf("OpenShift route detected in api groups, returning true") 509 result = true 510 f.isOpenShift = &result 511 return result 512 } 513 } 514 515 e2elog.Logf("OpenShift route not found in groups, returning false") 516 result = false 517 f.isOpenShift = &result 518 return result 519 } 520 521 func int32Ptr(i int32) *int32 { return &i }