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  }