github.com/cilium/cilium@v1.16.2/test/test_suite_test.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package ciliumTest
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"os/exec"
    10  	"path/filepath"
    11  	"strconv"
    12  	"testing"
    13  	"time"
    14  
    15  	gops "github.com/google/gops/agent"
    16  	"github.com/onsi/ginkgo"
    17  	ginkgoconfig "github.com/onsi/ginkgo/config"
    18  	. "github.com/onsi/gomega"
    19  	"github.com/onsi/gomega/format"
    20  	"github.com/sirupsen/logrus"
    21  
    22  	"github.com/cilium/cilium/pkg/logging"
    23  	"github.com/cilium/cilium/test/config"
    24  	. "github.com/cilium/cilium/test/ginkgo-ext"
    25  	"github.com/cilium/cilium/test/helpers"
    26  	"github.com/cilium/cilium/test/logger"
    27  
    28  	// These packages are where Ginkgo test specs live. They are declared as blank
    29  	// (_) global variables and are pulled in using package import side effects.
    30  	_ "github.com/cilium/cilium/test/k8s"
    31  	_ "github.com/cilium/cilium/test/runtime"
    32  )
    33  
    34  var (
    35  	log             = logging.DefaultLogger
    36  	DefaultSettings = map[string]string{
    37  		"K8S_VERSION": "1.30",
    38  	}
    39  	k8sNodesEnv         = "K8S_NODES"
    40  	commandsLogFileName = "cmds.log"
    41  )
    42  
    43  func init() {
    44  	// Open socket for using gops to get stacktraces in case the tests deadlock.
    45  	if err := gops.Listen(gops.Options{ShutdownCleanup: true}); err != nil {
    46  		fmt.Fprintf(os.Stderr, "unable to start gops: %s", err)
    47  		os.Exit(1)
    48  	}
    49  
    50  	for k, v := range DefaultSettings {
    51  		getOrSetEnvVar(k, v)
    52  	}
    53  	os.RemoveAll(helpers.TestResultsPath)
    54  
    55  	format.UseStringerRepresentation = true
    56  }
    57  
    58  func configLogsOutput() {
    59  	log.SetLevel(logrus.DebugLevel)
    60  	log.Out = &logger.TestLogWriter
    61  	logrus.SetFormatter(&logger.Formatter)
    62  	log.Formatter = &logger.Formatter
    63  	log.Hooks.Add(&logger.LogHook{})
    64  
    65  	GinkgoWriter = NewWriter(log.Out)
    66  }
    67  
    68  func showCommands() {
    69  	if !config.CiliumTestConfig.ShowCommands {
    70  		return
    71  	}
    72  
    73  	helpers.SSHMetaLogs = NewWriter(os.Stdout)
    74  }
    75  
    76  func Test(t *testing.T) {
    77  	if config.CiliumTestConfig.TestScope != "" {
    78  		helpers.UserDefinedScope = config.CiliumTestConfig.TestScope
    79  		fmt.Printf("User specified the scope:  %q\n", config.CiliumTestConfig.TestScope)
    80  	}
    81  
    82  	// Skip the ginkgo test suite if 'go test ./...' is run on the repository.
    83  	// Require passing a scope or focus to pull in the ginkgo suite.
    84  	if _, err := helpers.GetScope(); err != nil {
    85  		fmt.Println("No Ginkgo test scope defined, skipping test suite of package test/")
    86  		t.Skip("Run this package through Ginkgo with the --focus or -cilium.testScope options")
    87  	}
    88  
    89  	if integration := helpers.GetCurrentIntegration(); integration != "" {
    90  		fmt.Printf("Using CNI_INTEGRATION=%q\n", integration)
    91  
    92  		switch integration {
    93  		case helpers.CIIntegrationMicrok8s:
    94  			fallthrough
    95  		case helpers.CIIntegrationMinikube:
    96  			fmt.Printf("Disabling multinode testing")
    97  			config.CiliumTestConfig.Multinode = false
    98  		default:
    99  		}
   100  	}
   101  
   102  	configLogsOutput()
   103  	showCommands()
   104  
   105  	if config.CiliumTestConfig.HoldEnvironment {
   106  		RegisterFailHandler(helpers.Fail)
   107  	} else {
   108  		RegisterFailHandler(Fail)
   109  	}
   110  	junitReporter := NewJUnitReporter(fmt.Sprintf(
   111  		"%s.xml", helpers.GetScopeWithVersion()))
   112  	RunSpecsWithDefaultAndCustomReporters(
   113  		t, fmt.Sprintf("Suite-%s", helpers.GetScopeWithVersion()),
   114  		[]ginkgo.Reporter{junitReporter})
   115  }
   116  
   117  func goReportSetupStatus() chan bool {
   118  	if ginkgoconfig.DefaultReporterConfig.Verbose ||
   119  		ginkgoconfig.DefaultReporterConfig.Succinct {
   120  		// Dev told us they want more/less information than default. Skip.
   121  		return nil
   122  	}
   123  
   124  	exit := make(chan bool)
   125  	go func() {
   126  		done := false
   127  		iter := 0
   128  		for {
   129  			var out string
   130  			select {
   131  			case ok := <-exit:
   132  				if ok {
   133  					out = "●\n"
   134  				} else {
   135  					out = "◌\n"
   136  				}
   137  				done = true
   138  			default:
   139  				out = string(rune(int('◜') + iter%4))
   140  			}
   141  			fmt.Printf("\rSetting up test suite... %s", out)
   142  			if done {
   143  				return
   144  			}
   145  			time.Sleep(250 * time.Millisecond)
   146  			iter++
   147  		}
   148  	}()
   149  	return exit
   150  }
   151  
   152  func reportCreateVMFailure(vm string, err error) {
   153  	failmsg := fmt.Sprintf(`
   154          ===================== ERROR - VM PROVISION FAILED =====================
   155  
   156          Unable to provision and start VM %q: %s", vm, err
   157  
   158          =======================================================================
   159          `, vm, err)
   160  	GinkgoPrint(failmsg)
   161  	Fail(failmsg)
   162  }
   163  
   164  var _ = BeforeAll(func() {
   165  	helpers.Init()
   166  	By("Starting tests: command line parameters: %+v environment variables: %v", config.CiliumTestConfig, os.Environ())
   167  	go func() {
   168  		defer GinkgoRecover()
   169  		time.Sleep(config.CiliumTestConfig.Timeout)
   170  		msg := fmt.Sprintf("Test suite timed out after %s", config.CiliumTestConfig.Timeout)
   171  		By(msg)
   172  		Fail(msg)
   173  	}()
   174  
   175  	var err error
   176  
   177  	logger := log.WithFields(logrus.Fields{"testName": "BeforeAll"})
   178  	scope, err := helpers.GetScope()
   179  	if err != nil {
   180  		Fail(fmt.Sprintf(
   181  			"Cannot get the scope for running test, please use --cilium.testScope option: %s",
   182  			err))
   183  	}
   184  
   185  	if config.CiliumTestConfig.SSHConfig != "" {
   186  		// If we set a different VM that it's not in our test environment
   187  		// ginkgo cannot provision it, so skip setup below.
   188  		return
   189  	}
   190  
   191  	if progressChan := goReportSetupStatus(); progressChan != nil {
   192  		defer func() { progressChan <- err == nil }()
   193  	}
   194  
   195  	switch scope {
   196  	case helpers.Runtime:
   197  		// Boot / provision VMs if specified by configuration.
   198  		if config.CiliumTestConfig.Reprovision {
   199  			err = helpers.CreateVM(helpers.Runtime)
   200  			if err != nil {
   201  				log.WithError(err).Error("Error starting VM")
   202  				reportCreateVMFailure(helpers.Runtime, err)
   203  			}
   204  		}
   205  
   206  		vm := helpers.InitRuntimeHelper(helpers.Runtime, logger)
   207  		err = vm.SetUpCilium()
   208  
   209  		if err != nil {
   210  			// AfterFailed function is not defined in this scope, fired the
   211  			// ReportFailed manually for this assert to gather cilium logs Fix
   212  			// #3428
   213  			vm.ReportFailed()
   214  			log.WithError(err).Error("Cilium was unable to be set up correctly")
   215  			reportCreateVMFailure(helpers.Runtime, err)
   216  		}
   217  		go vm.PprofReport()
   218  
   219  	case helpers.K8s:
   220  		// FIXME: This should be:
   221  		// Start k8s1 and provision kubernetes.
   222  		// When finish, start to build cilium in background
   223  		// Start k8s2
   224  		// Wait until compilation finished, and pull cilium image on k8s2
   225  
   226  		// Name for K8s VMs depends on K8s version that is running.
   227  
   228  		// Boot / provision VMs if specified by configuration.
   229  		if config.CiliumTestConfig.Reprovision {
   230  			var nodesInt int
   231  			nodes := os.Getenv(k8sNodesEnv)
   232  			if nodes != "" {
   233  				nodesInt, err = strconv.Atoi(nodes)
   234  				if err != nil {
   235  					Fail(fmt.Sprintf("%s value is not a number %q", k8sNodesEnv, nodes))
   236  				}
   237  			}
   238  
   239  			err = helpers.CreateVM(helpers.K8s1VMName())
   240  			if err != nil {
   241  				reportCreateVMFailure(helpers.K8s1VMName(), err)
   242  			}
   243  
   244  			if nodesInt != 1 {
   245  				err = helpers.CreateVM(helpers.K8s2VMName())
   246  				if err != nil {
   247  					reportCreateVMFailure(helpers.K8s2VMName(), err)
   248  				}
   249  			}
   250  
   251  			// For Nightly test we need to have more than two kubernetes nodes. If
   252  			// the env variable K8S_NODES is present, more nodes will be created.
   253  			if nodesInt > 2 {
   254  				for i := 3; i <= nodesInt; i++ {
   255  					vmName := fmt.Sprintf("%s%d-%s", helpers.K8s, i, helpers.GetCurrentK8SEnv())
   256  					err = helpers.CreateVM(vmName)
   257  					if err != nil {
   258  						reportCreateVMFailure(vmName, err)
   259  					}
   260  				}
   261  			}
   262  		}
   263  		kubectl := helpers.CreateKubectl(helpers.K8s1VMName(), logger)
   264  		kubectl.PrepareCluster()
   265  
   266  		// Cleanup all cilium components if there are any leftovers from previous
   267  		// run, like when running tests locally.
   268  		kubectl.CleanupCiliumComponents()
   269  
   270  		kubectl.ApplyDefault(kubectl.GetFilePath("../examples/kubernetes/addons/prometheus/monitoring-example.yaml"))
   271  
   272  		go kubectl.PprofReport()
   273  	}
   274  })
   275  
   276  var _ = AfterSuite(func() {
   277  	if !helpers.IsRunningOnJenkins() {
   278  		GinkgoPrint("AfterSuite: not running on Jenkins; leaving VMs running for debugging")
   279  		return
   280  	}
   281  	// Errors are not checked here because it should fail on BeforeAll
   282  	scope, _ := helpers.GetScope()
   283  	GinkgoPrint("cleaning up VMs started for %s tests", scope)
   284  	switch scope {
   285  	case helpers.Runtime:
   286  		helpers.DestroyVM(helpers.Runtime)
   287  	case helpers.K8s:
   288  		helpers.DestroyVM(helpers.K8s1VMName())
   289  		helpers.DestroyVM(helpers.K8s2VMName())
   290  	}
   291  })
   292  
   293  func getOrSetEnvVar(key, value string) {
   294  	if val := os.Getenv(key); val == "" {
   295  		log.Infof("environment variable %q was not set; setting to default value %q", key, value)
   296  		os.Setenv(key, value)
   297  	}
   298  }
   299  
   300  var _ = AfterEach(func() {
   301  
   302  	// Send the Checks output to Junit report to be render on Jenkins.
   303  	defer helpers.CheckLogs.Reset()
   304  	GinkgoPrint("<Checks>\n%s\n</Checks>\n", helpers.CheckLogs.Buffer.String())
   305  
   306  	defer logger.TestLogWriterReset()
   307  	err := helpers.CreateLogFile(logger.TestLogFileName, logger.TestLogWriter.Bytes())
   308  	if err != nil {
   309  		log.WithError(err).Errorf("cannot create log file '%s'", logger.TestLogFileName)
   310  		return
   311  	}
   312  
   313  	defer helpers.SSHMetaLogs.Reset()
   314  	err = helpers.CreateLogFile(commandsLogFileName, helpers.SSHMetaLogs.Bytes())
   315  	if err != nil {
   316  		log.WithError(err).Errorf("cannot create log file '%s'", commandsLogFileName)
   317  		return
   318  	}
   319  
   320  	// This piece of code is to enable zip attachments on Junit Output.
   321  	if TestFailed() && helpers.IsRunningOnJenkins() {
   322  		// ReportDirectory is already created. No check the error
   323  		path, _ := helpers.CreateReportDirectory()
   324  		zipFileName := fmt.Sprintf("%s_%s.zip", helpers.MakeUID(), GetTestName())
   325  		zipFilePath := filepath.Join(helpers.TestResultsPath, zipFileName)
   326  
   327  		_, err := exec.Command(
   328  			"/usr/bin/env", "bash", "-c",
   329  			fmt.Sprintf("zip -qr \"%s\" \"%s\"", zipFilePath, path)).CombinedOutput()
   330  		if err != nil {
   331  			log.WithError(err).Errorf("cannot create zip file '%s'", zipFilePath)
   332  		}
   333  
   334  		GinkgoPrint("[[ATTACHMENT|%s]]", zipFileName)
   335  	}
   336  
   337  	if !TestFailed() && helpers.IsRunningOnJenkins() {
   338  		// If the test success delete the monitor.log filename to not store all
   339  		// the data in Jenkins
   340  		testPath, err := helpers.CreateReportDirectory()
   341  		if err != nil {
   342  			log.WithError(err).Error("cannot retrieve test result path")
   343  			return
   344  		}
   345  		_ = os.Remove(filepath.Join(testPath, helpers.MonitorLogFileName))
   346  	}
   347  })