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 }