k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/e2e_node/remote/run_remote_suite.go (about)

     1  /*
     2  Copyright 2016 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 remote
    18  
    19  import (
    20  	"flag"
    21  	"fmt"
    22  	"log"
    23  	"os"
    24  	"os/exec"
    25  	"os/signal"
    26  	"strings"
    27  	"sync"
    28  
    29  	"k8s.io/klog/v2"
    30  )
    31  
    32  var mode = flag.String("mode", "gce", "Mode to operate in. One of gce|ssh. Defaults to gce")
    33  var testArgs = flag.String("test_args", "", "Space-separated list of arguments to pass to Ginkgo test runner.")
    34  var instanceNamePrefix = flag.String("instance-name-prefix", "", "prefix for instance names")
    35  var imageConfigFile = flag.String("image-config-file", "", "yaml file describing images to run")
    36  var imageConfigDir = flag.String("image-config-dir", "", "(optional) path to image config files")
    37  var images = flag.String("images", "", "images to test")
    38  var hosts = flag.String("hosts", "", "hosts to test")
    39  var cleanup = flag.Bool("cleanup", true, "If true remove files from remote hosts and delete temporary instances")
    40  var deleteInstances = flag.Bool("delete-instances", true, "If true, delete any instances created")
    41  var buildOnly = flag.Bool("build-only", false, "If true, build e2e_node_test.tar.gz and exit.")
    42  var gubernator = flag.Bool("gubernator", false, "If true, output Gubernator link to view logs")
    43  var ginkgoFlags = flag.String("ginkgo-flags", "", "Passed to ginkgo to specify additional flags such as --skip=.")
    44  var (
    45  	arc Archive
    46  )
    47  
    48  // Archive contains path info in the archive.
    49  type Archive struct {
    50  	sync.Once
    51  	path string
    52  	err  error
    53  }
    54  
    55  func getFlag(name string) string {
    56  	lookup := flag.Lookup(name)
    57  	if lookup == nil {
    58  		return ""
    59  	}
    60  	return lookup.Value.String()
    61  }
    62  
    63  func RunRemoteTestSuite(testSuite TestSuite) {
    64  	// Listen for SIGINT and ignore the first one. In case SIGINT is sent to this
    65  	// process and all its children, we ignore it here, while our children ssh connections
    66  	// are stopped. This allows us to gather artifacts and print out test state before
    67  	// being killed.
    68  	c := make(chan os.Signal, 2)
    69  	signal.Notify(c, os.Interrupt)
    70  	go func() {
    71  		<-c
    72  		fmt.Printf("Received SIGINT. Will exit on next SIGINT.\n")
    73  		<-c
    74  		fmt.Printf("Received another SIGINT. Will exit.\n")
    75  		os.Exit(1)
    76  	}()
    77  
    78  	if *buildOnly {
    79  		// Build the archive and exit
    80  		CreateTestArchive(testSuite,
    81  			getFlag("system-spec-name"),
    82  			getFlag("kubelet-config-file"))
    83  		return
    84  	}
    85  
    86  	// Append some default ginkgo flags. We use similar defaults here as hack/ginkgo-e2e.sh
    87  	allGinkgoFlags := fmt.Sprintf("%s --no-color -v", *ginkgoFlags)
    88  	fmt.Printf("Will use ginkgo flags as: %s", allGinkgoFlags)
    89  
    90  	var runner Runner
    91  	cfg := Config{
    92  		InstanceNamePrefix: *instanceNamePrefix,
    93  		ImageConfigFile:    *imageConfigFile,
    94  		ImageConfigDir:     *imageConfigDir,
    95  		Images:             splitCommaList(*images),
    96  		Hosts:              parseHostsList(*hosts),
    97  		GinkgoFlags:        allGinkgoFlags,
    98  		DeleteInstances:    *deleteInstances,
    99  		Cleanup:            *cleanup,
   100  		TestArgs:           *testArgs,
   101  		ExtraEnvs:          getFlag("extra-envs"),
   102  		RuntimeConfig:      getFlag("runtime-config"),
   103  		SystemSpecName:     getFlag("system-spec-name"),
   104  	}
   105  
   106  	var sshRunner Runner
   107  
   108  	if *mode == "ssh" {
   109  		runner = NewSSHRunner(cfg)
   110  	} else {
   111  		getRunner, err := GetRunner(*mode)
   112  		if err != nil {
   113  			klog.Fatalf("getting runner mode %q : %v", *mode, err)
   114  		}
   115  		runner = getRunner(cfg)
   116  		sshRunner = NewSSHRunner(cfg)
   117  	}
   118  
   119  	if err := runner.Validate(); err != nil {
   120  		klog.Fatalf("validating remote config, %s", err)
   121  	}
   122  
   123  	// Setup coloring
   124  	stat, _ := os.Stdout.Stat()
   125  	useColor := (stat.Mode() & os.ModeCharDevice) != 0
   126  	blue := ""
   127  	noColour := ""
   128  	if useColor {
   129  		blue = "\033[0;34m"
   130  		noColour = "\033[0m"
   131  	}
   132  
   133  	results := make(chan *TestResult)
   134  
   135  	path, err := arc.getArchive(testSuite)
   136  	if err != nil {
   137  		log.Fatalf("unable to create test archive: %s", err)
   138  	}
   139  	defer arc.deleteArchive()
   140  
   141  	running := runner.StartTests(testSuite, path, results)
   142  	// You can potentially run SSH based tests while running image based test as well.  The GCE provider does this, see
   143  	// test-e2e-node.sh.
   144  	if sshRunner != nil && len(cfg.Hosts) > 0 {
   145  		running += sshRunner.StartTests(testSuite, path, results)
   146  	}
   147  
   148  	// Wait for all tests to complete and emit the results
   149  	errCount := 0
   150  	exitOk := true
   151  	for i := 0; i < running; i++ {
   152  		tr := <-results
   153  		host := tr.Host
   154  		fmt.Println() // Print an empty line
   155  		fmt.Printf("%s>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>%s\n", blue, noColour)
   156  		fmt.Printf("%s>                              START TEST                                >%s\n", blue, noColour)
   157  		fmt.Printf("%s>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>%s\n", blue, noColour)
   158  		fmt.Printf("Start Test Suite on Host %s\n", host)
   159  		fmt.Printf("%s\n", tr.Output)
   160  		if tr.Err != nil {
   161  			errCount++
   162  			fmt.Printf("Failure Finished Test Suite on Host %s. Refer to artifacts directory for ginkgo log for this host.\n%v\n", host, tr.Err)
   163  		} else {
   164  			fmt.Printf("Success Finished Test Suite on Host %s. Refer to artifacts directory for ginkgo log for this host.\n", host)
   165  		}
   166  		exitOk = exitOk && tr.ExitOK
   167  		fmt.Printf("%s<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<%s\n", blue, noColour)
   168  		fmt.Printf("%s<                              FINISH TEST                               <%s\n", blue, noColour)
   169  		fmt.Printf("%s<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<%s\n", blue, noColour)
   170  		fmt.Println() // Print an empty line
   171  	}
   172  	// Set the exit code if there were failures
   173  	if !exitOk {
   174  		fmt.Printf("Failure: %d errors encountered.\n", errCount)
   175  		callGubernator(*gubernator)
   176  		arc.deleteArchive()
   177  		os.Exit(1)
   178  	}
   179  	callGubernator(*gubernator)
   180  }
   181  
   182  func splitCommaList(s string) []string {
   183  	if len(s) == 0 {
   184  		return nil
   185  	}
   186  	return strings.Split(s, ",")
   187  }
   188  
   189  func callGubernator(gubernator bool) {
   190  	if gubernator {
   191  		fmt.Println("Running gubernator.sh")
   192  		output, err := exec.Command("./test/e2e_node/gubernator.sh", "y").Output()
   193  
   194  		if err != nil {
   195  			fmt.Println("gubernator.sh Failed")
   196  			fmt.Println(err)
   197  			return
   198  		}
   199  		fmt.Printf("%s", output)
   200  	}
   201  	return
   202  }
   203  
   204  func (a *Archive) getArchive(suite TestSuite) (string, error) {
   205  	a.Do(func() {
   206  		a.path, a.err = CreateTestArchive(suite,
   207  			getFlag("system-spec-name"),
   208  			getFlag("kubelet-config-file"))
   209  	})
   210  	return a.path, a.err
   211  }
   212  
   213  func (a *Archive) deleteArchive() {
   214  	path, err := a.getArchive(nil)
   215  	if err != nil {
   216  		return
   217  	}
   218  	os.Remove(path)
   219  }
   220  
   221  // parseHostsList splits a host list of the form a=1.2.3.4,b=5.6.7.8 into the list of hosts [a,b] while registering the
   222  // given addresses
   223  func parseHostsList(hostList string) []string {
   224  	if len(hostList) == 0 {
   225  		return nil
   226  	}
   227  	hosts := strings.Split(hostList, ",")
   228  	var hostsOnly []string
   229  	for _, host := range hosts {
   230  		segs := strings.Split(host, "=")
   231  		if len(segs) == 2 {
   232  			AddHostnameIP(segs[0], segs[1])
   233  		} else if len(segs) > 2 {
   234  			klog.Fatalf("invalid format of host %q", hostList)
   235  		}
   236  		hostsOnly = append(hostsOnly, segs[0])
   237  	}
   238  	return hostsOnly
   239  }