github.com/fabianvf/ocp-release-operator-sdk@v0.0.0-20190426141702-57620ee2f090/cmd/operator-sdk/test/cluster.go (about)

     1  // Copyright 2018 The Operator-SDK 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 test
    16  
    17  import (
    18  	"bytes"
    19  	"fmt"
    20  	"strings"
    21  	"time"
    22  
    23  	"github.com/operator-framework/operator-sdk/internal/pkg/scaffold"
    24  	"github.com/operator-framework/operator-sdk/internal/pkg/scaffold/ansible"
    25  	"github.com/operator-framework/operator-sdk/internal/util/fileutil"
    26  	k8sInternal "github.com/operator-framework/operator-sdk/internal/util/k8sutil"
    27  	"github.com/operator-framework/operator-sdk/internal/util/projutil"
    28  	"github.com/operator-framework/operator-sdk/pkg/k8sutil"
    29  	"github.com/operator-framework/operator-sdk/pkg/test"
    30  
    31  	log "github.com/sirupsen/logrus"
    32  	"github.com/spf13/cobra"
    33  	v1 "k8s.io/api/core/v1"
    34  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    35  	"k8s.io/apimachinery/pkg/util/wait"
    36  	"k8s.io/client-go/kubernetes"
    37  )
    38  
    39  type testClusterConfig struct {
    40  	namespace       string
    41  	kubeconfig      string
    42  	imagePullPolicy string
    43  	serviceAccount  string
    44  	pendingTimeout  int
    45  }
    46  
    47  var tcConfig testClusterConfig
    48  
    49  func newTestClusterCmd() *cobra.Command {
    50  	testCmd := &cobra.Command{
    51  		Use:   "cluster <image name> [flags]",
    52  		Short: "Run End-To-End tests using image with embedded test binary",
    53  		RunE:  testClusterFunc,
    54  	}
    55  	testCmd.Flags().StringVar(&tcConfig.namespace, "namespace", "", "Namespace to run tests in")
    56  	testCmd.Flags().StringVar(&tcConfig.kubeconfig, "kubeconfig", "", "Kubeconfig path")
    57  	testCmd.Flags().StringVar(&tcConfig.imagePullPolicy, "image-pull-policy", "Always", "Set test pod image pull policy. Allowed values: Always, Never")
    58  	testCmd.Flags().StringVar(&tcConfig.serviceAccount, "service-account", "default", "Service account to run tests on")
    59  	testCmd.Flags().IntVar(&tcConfig.pendingTimeout, "pending-timeout", 60, "Timeout in seconds for testing pod to stay in pending state (default 60s)")
    60  
    61  	return testCmd
    62  }
    63  
    64  func testClusterFunc(cmd *cobra.Command, args []string) error {
    65  	if len(args) != 1 {
    66  		return fmt.Errorf("command %s requires exactly one argument", cmd.CommandPath())
    67  	}
    68  
    69  	log.Info("Testing operator in cluster.")
    70  
    71  	var pullPolicy v1.PullPolicy
    72  	if strings.ToLower(tcConfig.imagePullPolicy) == "always" {
    73  		pullPolicy = v1.PullAlways
    74  	} else if strings.ToLower(tcConfig.imagePullPolicy) == "never" {
    75  		pullPolicy = v1.PullNever
    76  	} else {
    77  		return fmt.Errorf("invalid imagePullPolicy '%v'", tcConfig.imagePullPolicy)
    78  	}
    79  
    80  	var testCmd []string
    81  	switch projutil.GetOperatorType() {
    82  	case projutil.OperatorTypeGo:
    83  		testCmd = []string{"/" + scaffold.GoTestScriptFile}
    84  	case projutil.OperatorTypeAnsible:
    85  		testCmd = []string{"/" + ansible.BuildTestFrameworkAnsibleTestScriptFile}
    86  	case projutil.OperatorTypeHelm:
    87  		log.Fatal("`test cluster` for Helm operators is not implemented")
    88  	default:
    89  		log.Fatal("Failed to determine operator type")
    90  	}
    91  
    92  	// cobra prints its help message on error; we silence that here because any errors below
    93  	// are due to the test failing, not incorrect user input
    94  	cmd.SilenceUsage = true
    95  	testPod := &v1.Pod{
    96  		ObjectMeta: metav1.ObjectMeta{
    97  			Name: "operator-test",
    98  		},
    99  		Spec: v1.PodSpec{
   100  			ServiceAccountName: tcConfig.serviceAccount,
   101  			RestartPolicy:      v1.RestartPolicyNever,
   102  			Containers: []v1.Container{{
   103  				Name:            "operator-test",
   104  				Image:           args[0],
   105  				ImagePullPolicy: pullPolicy,
   106  				Command:         testCmd,
   107  				Env: []v1.EnvVar{{
   108  					Name:      test.TestNamespaceEnv,
   109  					ValueFrom: &v1.EnvVarSource{FieldRef: &v1.ObjectFieldSelector{FieldPath: "metadata.namespace"}},
   110  				}, {
   111  					Name:  k8sutil.OperatorNameEnvVar,
   112  					Value: "test-operator",
   113  				}, {
   114  					Name:      k8sutil.PodNameEnvVar,
   115  					ValueFrom: &v1.EnvVarSource{FieldRef: &v1.ObjectFieldSelector{FieldPath: "metadata.name"}},
   116  				}},
   117  			}},
   118  		},
   119  	}
   120  	kubeconfig, defaultNamespace, err := k8sInternal.GetKubeconfigAndNamespace(tcConfig.kubeconfig)
   121  	if err != nil {
   122  		return fmt.Errorf("failed to get kubeconfig: %v", err)
   123  	}
   124  	if tcConfig.namespace == "" {
   125  		tcConfig.namespace = defaultNamespace
   126  	}
   127  	kubeclient, err := kubernetes.NewForConfig(kubeconfig)
   128  	if err != nil {
   129  		return fmt.Errorf("failed to create kubeclient: %v", err)
   130  	}
   131  	testPod, err = kubeclient.CoreV1().Pods(tcConfig.namespace).Create(testPod)
   132  	if err != nil {
   133  		return fmt.Errorf("failed to create test pod: %v", err)
   134  	}
   135  	defer func() {
   136  		rerr := kubeclient.CoreV1().Pods(tcConfig.namespace).Delete(testPod.Name, &metav1.DeleteOptions{})
   137  		if rerr != nil {
   138  			log.Warnf("Failed to delete test pod: %v", rerr)
   139  		}
   140  	}()
   141  	err = wait.Poll(time.Second*5, time.Second*time.Duration(tcConfig.pendingTimeout), func() (bool, error) {
   142  		testPod, err = kubeclient.CoreV1().Pods(tcConfig.namespace).Get(testPod.Name, metav1.GetOptions{})
   143  		if err != nil {
   144  			return false, fmt.Errorf("failed to get test pod: %v", err)
   145  		}
   146  		if testPod.Status.Phase == v1.PodPending {
   147  			return false, nil
   148  		}
   149  		return true, nil
   150  	})
   151  	if err != nil {
   152  		testPod, err = kubeclient.CoreV1().Pods(tcConfig.namespace).Get(testPod.Name, metav1.GetOptions{})
   153  		if err != nil {
   154  			return fmt.Errorf("failed to get test pod: %v", err)
   155  		}
   156  		waitingState := testPod.Status.ContainerStatuses[0].State.Waiting
   157  		return fmt.Errorf("test pod stuck in 'Pending' phase for longer than %d seconds.\nMessage: %s\nReason: %s", tcConfig.pendingTimeout, waitingState.Message, waitingState.Reason)
   158  	}
   159  	for {
   160  		testPod, err = kubeclient.CoreV1().Pods(tcConfig.namespace).Get(testPod.Name, metav1.GetOptions{})
   161  		if err != nil {
   162  			return fmt.Errorf("failed to get test pod: %v", err)
   163  		}
   164  		if testPod.Status.Phase != v1.PodSucceeded && testPod.Status.Phase != v1.PodFailed {
   165  			time.Sleep(time.Second * 5)
   166  			continue
   167  		} else if testPod.Status.Phase == v1.PodSucceeded {
   168  			log.Info("Cluster test successfully completed.")
   169  			return nil
   170  		} else if testPod.Status.Phase == v1.PodFailed {
   171  			req := kubeclient.CoreV1().Pods(tcConfig.namespace).GetLogs(testPod.Name, &v1.PodLogOptions{})
   172  			readCloser, err := req.Stream()
   173  			if err != nil {
   174  				return fmt.Errorf("test failed and failed to get error logs: %v", err)
   175  			}
   176  			defer func() {
   177  				if err := readCloser.Close(); err != nil && !fileutil.IsClosedError(err) {
   178  					log.Errorf("Failed to close pod log reader: (%v)", err)
   179  				}
   180  			}()
   181  			buf := new(bytes.Buffer)
   182  			_, err = buf.ReadFrom(readCloser)
   183  			if err != nil {
   184  				return fmt.Errorf("test failed and failed to read pod logs: %v", err)
   185  			}
   186  			return fmt.Errorf("test failed:\n%s", buf.String())
   187  		}
   188  	}
   189  }