github.com/maxgio92/test-infra@v0.1.0/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 "github.com/maxgio92/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 StorageTestDriver string 66 67 // Other ginkgo options 68 FocusRegex string 69 SkipRegex string 70 Seed int 71 SystemdServices []string 72 } 73 74 // NewGinkgoTester returns a new instance of GinkgoTester 75 func NewGinkgoTester(o *BuildTesterOptions) *GinkgoTester { 76 t := &GinkgoTester{ 77 FlakeAttempts: 1, 78 } 79 80 t.GinkgoParallel = o.Parallelism 81 t.FocusRegex = o.FocusRegex 82 t.SkipRegex = o.SkipRegex 83 84 return t 85 } 86 87 // args is a list of arguments, defining some helper functions 88 type args struct { 89 values []string 90 } 91 92 func (a *args) addIfNonEmpty(flagName, value string) { 93 if value != "" { 94 a.values = append(a.values, fmt.Sprintf("--%s=%s", flagName, value)) 95 } 96 } 97 98 func (a *args) addBool(flagName string, value bool) { 99 a.values = append(a.values, fmt.Sprintf("--%s=%t", flagName, value)) 100 } 101 102 func (a *args) addInt(flagName string, value int) { 103 a.values = append(a.values, fmt.Sprintf("--%s=%d", flagName, value)) 104 } 105 106 // validate checks that fields are set sanely 107 func (t *GinkgoTester) validate() error { 108 if t.Kubeconfig == "" { 109 return errors.New("Kubeconfig cannot be empty") 110 } 111 112 if t.Provider == "" { 113 return errors.New("Provider cannot be empty") 114 } 115 116 if t.KubeRoot == "" { 117 return errors.New("Kuberoot cannot be empty") 118 } 119 120 if t.GinkgoParallel <= 0 { 121 return errors.New("GinkgoParallel must be at least 1") 122 } 123 124 // Check that our files and folders exist. 125 if t.ReportDir != "" { 126 if _, err := os.Stat(t.ReportDir); err != nil { 127 return fmt.Errorf("ReportDir %s must exist before tests are run: %w", t.ReportDir, err) 128 } 129 } 130 131 return nil 132 } 133 134 // Run executes the test (calling ginkgo) 135 func (t *GinkgoTester) Run(control *process.Control, extraArgs []string) error { 136 if err := t.validate(); err != nil { 137 return fmt.Errorf("configuration error in GinkgoTester: %w", err) 138 } 139 140 ginkgoPath, err := t.findBinary("ginkgo") 141 if err != nil { 142 return err 143 } 144 e2eTest, err := t.findBinary("e2e.test") 145 if err != nil { 146 return err 147 } 148 149 a := &args{} 150 151 if t.Seed != 0 { 152 a.addInt("seed", t.Seed) 153 } 154 155 a.addIfNonEmpty("focus", t.FocusRegex) 156 a.addIfNonEmpty("skip", t.SkipRegex) 157 158 a.addInt("nodes", t.GinkgoParallel) 159 160 a.values = append(a.values, []string{ 161 e2eTest, 162 "--", 163 }...) 164 165 a.addIfNonEmpty("kubeconfig", t.Kubeconfig) 166 a.addInt("ginkgo.flakeAttempts", t.FlakeAttempts) 167 a.addIfNonEmpty("provider", t.Provider) 168 a.addIfNonEmpty("gce-project", t.GCEProject) 169 a.addIfNonEmpty("gce-zone", t.GCEZone) 170 a.addIfNonEmpty("gce-region", t.GCERegion) 171 a.addBool("gce-multizone", t.GCEMultizone) 172 173 a.addIfNonEmpty("gke-cluster", t.GKECluster) 174 a.addIfNonEmpty("host", t.KubeMasterURL) 175 a.addIfNonEmpty("kube-master", t.KubeMaster) 176 a.addIfNonEmpty("cluster-tag", t.ClusterID) 177 a.addIfNonEmpty("cloud-config-file", t.CloudConfig) 178 a.addIfNonEmpty("repo-root", t.KubeRoot) 179 a.addIfNonEmpty("node-instance-group", t.NodeInstanceGroup) 180 a.addIfNonEmpty("prefix", t.KubeGCEInstancePrefix) 181 a.addIfNonEmpty("network", t.Network) 182 a.addIfNonEmpty("node-tag", t.NodeTag) 183 a.addIfNonEmpty("master-tag", t.MasterTag) 184 a.addIfNonEmpty("cluster-monitoring-mode", t.ClusterMonitoringMode) 185 186 a.addIfNonEmpty("container-runtime", t.KubeContainerRuntime) 187 a.addIfNonEmpty("master-os-distro", t.MasterOSDistribution) 188 a.addIfNonEmpty("node-os-distro", t.NodeOSDistribution) 189 a.addInt("num-nodes", t.NumNodes) 190 a.addIfNonEmpty("report-dir", t.ReportDir) 191 a.addIfNonEmpty("report-prefix", t.ReportPrefix) 192 193 a.addIfNonEmpty("systemd-services", strings.Join(t.SystemdServices, ",")) 194 a.addIfNonEmpty("storage.testdriver", t.StorageTestDriver) 195 196 ginkgoArgs := append(a.values, extraArgs...) 197 198 cmd := exec.Command(ginkgoPath, ginkgoArgs...) 199 200 log.Printf("running ginkgo: %s %s", cmd.Path, strings.Join(cmd.Args, " ")) 201 202 return control.FinishRunning(cmd) 203 } 204 205 // findBinary finds a file by name, from a list of well-known output locations 206 // When multiple matches are found, the most recent will be returned 207 // Based on kube::util::find-binary from kubernetes/kubernetes 208 func (t *GinkgoTester) findBinary(name string) (string, error) { 209 kubeRoot := t.KubeRoot 210 211 locations := []string{ 212 filepath.Join(kubeRoot, "_output", "bin", name), 213 filepath.Join(kubeRoot, "_output", "dockerized", "bin", name), 214 filepath.Join(kubeRoot, "_output", "local", "bin", name), 215 filepath.Join(kubeRoot, "platforms", runtime.GOOS, runtime.GOARCH, name), 216 } 217 218 bazelBin := filepath.Join(kubeRoot, "bazel-bin") 219 bazelBinExists := true 220 if _, err := os.Stat(bazelBin); os.IsNotExist(err) { 221 bazelBinExists = false 222 log.Printf("bazel-bin not found at %s", bazelBin) 223 } 224 225 if bazelBinExists { 226 err := filepath.Walk(bazelBin, func(path string, info os.FileInfo, err error) error { 227 if err != nil { 228 return fmt.Errorf("error from walk: %w", err) 229 } 230 if info.Name() != name { 231 return nil 232 } 233 if !strings.Contains(path, runtime.GOOS+"_"+runtime.GOARCH) { 234 return nil 235 } 236 locations = append(locations, path) 237 return nil 238 }) 239 if err != nil { 240 return "", err 241 } 242 } 243 244 newestLocation := "" 245 var newestModTime time.Time 246 for _, loc := range locations { 247 stat, err := os.Stat(loc) 248 if err != nil { 249 if os.IsNotExist(err) { 250 continue 251 } 252 return "", fmt.Errorf("error from stat %s: %w", loc, err) 253 } 254 if newestLocation == "" || stat.ModTime().After(newestModTime) { 255 newestModTime = stat.ModTime() 256 newestLocation = loc 257 } 258 } 259 260 if newestLocation == "" { 261 log.Printf("could not find %s, looked in %s", name, locations) 262 return "", fmt.Errorf("could not find %s", name) 263 } 264 265 return newestLocation, nil 266 }