github.com/abayer/test-infra@v0.0.5/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  }