github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/kubetest/e2e/runner.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package e2e 18 19 import ( 20 "errors" 21 "fmt" 22 "log" 23 "os" 24 "os/exec" 25 "path/filepath" 26 "runtime" 27 "strings" 28 "time" 29 30 "k8s.io/test-infra/kubetest/process" 31 ) 32 33 // GinkgoTester runs e2e tests directly (by calling ginkgo) 34 type GinkgoTester struct { 35 // Required 36 Kubeconfig string 37 Provider string 38 KubeRoot string 39 40 GinkgoParallel int 41 42 // Other options defined in hack/ginkgo.sh 43 KubeMasterURL string 44 FlakeAttempts int 45 GCEProject string 46 GCEZone string 47 GCERegion string 48 GCEMultizone bool 49 GKECluster string 50 KubeMaster string 51 ClusterID string 52 CloudConfig string 53 NodeInstanceGroup string 54 KubeGCEInstancePrefix string 55 Network string 56 NodeTag string 57 MasterTag string 58 ClusterMonitoringMode string 59 KubeContainerRuntime string 60 MasterOSDistribution string 61 NodeOSDistribution string 62 NumNodes int 63 ReportDir string 64 ReportPrefix string 65 66 // Other ginkgo options 67 FocusRegex string 68 SkipRegex string 69 Seed int 70 SystemdServices []string 71 } 72 73 // NewGinkgoTester returns a new instance of GinkgoTester 74 func NewGinkgoTester(o *BuildTesterOptions) *GinkgoTester { 75 t := &GinkgoTester{ 76 FlakeAttempts: 1, 77 } 78 79 t.GinkgoParallel = o.Parallelism 80 t.FocusRegex = o.FocusRegex 81 t.SkipRegex = o.SkipRegex 82 83 return t 84 } 85 86 // args is a list of arguments, defining some helper functions 87 type args struct { 88 values []string 89 } 90 91 func (a *args) addIfNonEmpty(flagName, value string) { 92 if value != "" { 93 a.values = append(a.values, fmt.Sprintf("--%s=%s", flagName, value)) 94 } 95 } 96 97 func (a *args) addBool(flagName string, value bool) { 98 a.values = append(a.values, fmt.Sprintf("--%s=%t", flagName, value)) 99 } 100 101 func (a *args) addInt(flagName string, value int) { 102 a.values = append(a.values, fmt.Sprintf("--%s=%d", flagName, value)) 103 } 104 105 // validate checks that fields are set sanely 106 func (t *GinkgoTester) validate() error { 107 if t.Kubeconfig == "" { 108 return errors.New("Kubeconfig cannot be empty") 109 } 110 111 if t.Provider == "" { 112 return errors.New("Provider cannot be empty") 113 } 114 115 if t.KubeRoot == "" { 116 return errors.New("Kuberoot cannot be empty") 117 } 118 119 if t.GinkgoParallel <= 0 { 120 return errors.New("GinkgoParallel must be at least 1") 121 } 122 123 // Check that our files and folders exist. 124 if t.ReportDir != "" { 125 if _, err := os.Stat(t.ReportDir); err != nil { 126 return fmt.Errorf("ReportDir %s must exist before tests are run: %v", t.ReportDir, err) 127 } 128 } 129 130 return nil 131 } 132 133 // Run executes the test (calling ginkgo) 134 func (t *GinkgoTester) Run(control *process.Control, extraArgs []string) error { 135 if err := t.validate(); err != nil { 136 return fmt.Errorf("configuration error in GinkgoTester: %v", err) 137 } 138 139 ginkgoPath, err := t.findBinary("ginkgo") 140 if err != nil { 141 return err 142 } 143 e2eTest, err := t.findBinary("e2e.test") 144 if err != nil { 145 return err 146 } 147 148 a := &args{} 149 150 if t.Seed != 0 { 151 a.addInt("seed", t.Seed) 152 } 153 154 a.addIfNonEmpty("focus", t.FocusRegex) 155 a.addIfNonEmpty("skip", t.SkipRegex) 156 157 a.addInt("nodes", t.GinkgoParallel) 158 159 a.values = append(a.values, []string{ 160 e2eTest, 161 "--", 162 }...) 163 164 a.addIfNonEmpty("kubeconfig", t.Kubeconfig) 165 a.addInt("ginkgo.flakeAttempts", t.FlakeAttempts) 166 a.addIfNonEmpty("provider", t.Provider) 167 a.addIfNonEmpty("gce-project", t.GCEProject) 168 a.addIfNonEmpty("gce-zone", t.GCEZone) 169 a.addIfNonEmpty("gce-region", t.GCERegion) 170 a.addBool("gce-multizone", t.GCEMultizone) 171 172 a.addIfNonEmpty("gke-cluster", t.GKECluster) 173 a.addIfNonEmpty("host", t.KubeMasterURL) 174 a.addIfNonEmpty("kube-master", t.KubeMaster) 175 a.addIfNonEmpty("cluster-tag", t.ClusterID) 176 a.addIfNonEmpty("cloud-config-file", t.CloudConfig) 177 a.addIfNonEmpty("repo-root", t.KubeRoot) 178 a.addIfNonEmpty("node-instance-group", t.NodeInstanceGroup) 179 a.addIfNonEmpty("prefix", t.KubeGCEInstancePrefix) 180 a.addIfNonEmpty("network", t.Network) 181 a.addIfNonEmpty("node-tag", t.NodeTag) 182 a.addIfNonEmpty("master-tag", t.MasterTag) 183 a.addIfNonEmpty("cluster-monitoring-mode", t.ClusterMonitoringMode) 184 185 a.addIfNonEmpty("container-runtime", t.KubeContainerRuntime) 186 a.addIfNonEmpty("master-os-distro", t.MasterOSDistribution) 187 a.addIfNonEmpty("node-os-distro", t.NodeOSDistribution) 188 a.addInt("num-nodes", t.NumNodes) 189 a.addIfNonEmpty("report-dir", t.ReportDir) 190 a.addIfNonEmpty("report-prefix", t.ReportPrefix) 191 192 a.addIfNonEmpty("systemd-services", strings.Join(t.SystemdServices, ",")) 193 194 ginkgoArgs := append(a.values, extraArgs...) 195 196 cmd := exec.Command(ginkgoPath, ginkgoArgs...) 197 198 log.Printf("running ginkgo: %s %s", cmd.Path, strings.Join(cmd.Args, " ")) 199 200 return control.FinishRunning(cmd) 201 } 202 203 // findBinary finds a file by name, from a list of well-known output locations 204 // When multiple matches are found, the most recent will be returned 205 // Based on kube::util::find-binary from kubernetes/kubernetes 206 func (t *GinkgoTester) findBinary(name string) (string, error) { 207 kubeRoot := t.KubeRoot 208 209 locations := []string{ 210 filepath.Join(kubeRoot, "_output", "bin", name), 211 filepath.Join(kubeRoot, "_output", "dockerized", "bin", name), 212 filepath.Join(kubeRoot, "_output", "local", "bin", name), 213 filepath.Join(kubeRoot, "platforms", runtime.GOOS, runtime.GOARCH, name), 214 } 215 216 bazelBin := filepath.Join(kubeRoot, "bazel-bin") 217 bazelBinExists := true 218 if _, err := os.Stat(bazelBin); os.IsNotExist(err) { 219 bazelBinExists = false 220 log.Printf("bazel-bin not found at %s", bazelBin) 221 } 222 223 if bazelBinExists { 224 err := filepath.Walk(bazelBin, func(path string, info os.FileInfo, err error) error { 225 if err != nil { 226 return fmt.Errorf("error from walk: %v", err) 227 } 228 if info.Name() != name { 229 return nil 230 } 231 if !strings.Contains(path, runtime.GOOS+"_"+runtime.GOARCH) { 232 return nil 233 } 234 locations = append(locations, path) 235 return nil 236 }) 237 if err != nil { 238 return "", err 239 } 240 } 241 242 newestLocation := "" 243 var newestModTime time.Time 244 for _, loc := range locations { 245 stat, err := os.Stat(loc) 246 if err != nil { 247 if os.IsNotExist(err) { 248 continue 249 } 250 return "", fmt.Errorf("error from stat %s: %v", loc, err) 251 } 252 if newestLocation == "" || stat.ModTime().After(newestModTime) { 253 newestModTime = stat.ModTime() 254 newestLocation = loc 255 } 256 } 257 258 if newestLocation == "" { 259 log.Printf("could not find %s, looked in %s", name, locations) 260 return "", fmt.Errorf("could not find %s", name) 261 } 262 263 return newestLocation, nil 264 }