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 }