istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tests/integration/README.md (about)

     1  # Istio Integration Tests
     2  
     3  This folder contains Istio integration tests that use the test framework checked in at
     4  [istio.io/istio/pkg/test/framework](https://github.com/istio/istio/tree/master/pkg/test/framework).
     5  
     6  ## Table of Contents
     7  
     8  1. [Overview](#overview)
     9  1. [Writing Tests](#writing-tests)
    10      1. [Adding a Test Suite](#adding-a-test-suite)
    11      1. [Sub-Tests](#sub-tests)
    12      1. [Parallel Tests](#parallel-tests)
    13      1. [Using Components](#using-components)
    14      1. [Writing Components](#writing-components)
    15  1. [Running Tests](#running-tests)
    16      1. [Test Parallelism and Kubernetes](#test-parellelism-and-kubernetes)
    17      1. [Test Selection](#test-selection)
    18      1. [Running Tests on CI](#running-tests-on-ci)
    19          1. [Step 1: Add a Test Script](#step-1-add-a-test-script)
    20          1. [Step 2: Add a Prow Job](#step-2-add-a-prow-job)
    21          1. [Step 3: Update TestGrid](#step-3-update-testgrid)
    22  1. [Environments](#environments)
    23  1. [Diagnosing Failures](#diagnosing-failures)
    24      1. [Working Directory](#working-directory)
    25      1. [Enabling CI Mode](#enabling-ci-mode)
    26      1. [Preserving State (No Cleanup)](#preserving-state-no-cleanup)
    27      1. [Additional Logging](#additional-logging)
    28      1. [Running Tests Under Debugger](#running-tests-under-debugger-goland)
    29  1. [Reference](#reference)
    30      1. [Helm Values Overrides](#helm-values-overrides)
    31      1. [Commandline Flags](#command-line-flags)
    32  1. [Notes](#notes)
    33      1. [Running on a Mac](#running-on-a-mac)
    34  
    35  ## Overview
    36  
    37  The goal of the framework is to make it as easy as possible to author and run tests. In its simplest
    38  case, just typing ```go test ./...``` should be sufficient to run tests.
    39  
    40  This guide walks through the basics of writing tests with the Istio test framework. For best
    41  practices, see [Writing Good Integration Tests](https://github.com/istio/istio/wiki/Writing-Good-Integration-Tests).
    42  
    43  ## Writing Tests
    44  
    45  The test framework is designed to work with standard go tooling and allows developers
    46  to write environment-agnostics tests in a high-level fashion.
    47  
    48  ### Adding a Test Suite
    49  
    50  All tests that use the framework, must run as part of a *suite*. Only a single suite can be defined per package, since
    51  it is bootstrapped by a Go `TestMain`, which has the same restriction.
    52  
    53  To begin, create a new folder for your suite under
    54  [tests/integration](https://github.com/istio/istio/tree/master/tests/integration).
    55  
    56  ```console
    57  $ cd ${ISTIO}/tests/integration
    58  $ mkdir mysuite
    59  ```
    60  
    61  Within that package, create a `TestMain` to bootstrap the test suite:
    62  
    63  ```go
    64  func TestMain(m *testing.M) {
    65      framework.
    66          NewSuite("mysuite", m).
    67          Run()
    68  }
    69  ```
    70  
    71  Next, define your tests in the same package:
    72  
    73  ```go
    74  func TestMyLogic(t *testing.T) {
    75      framework.
    76          NewTest(t).
    77          Run(func(ctx framework.TestContext) {
    78              // Create a component
    79              p := pilot.NewOrFail(ctx, ctx, cfg)
    80  
    81              // Use the component.
    82              // Apply Kubernetes Config
    83              ctx.ApplyConfigOrFail(ctx, nil, mycfg)
    84  
    85              // Do more stuff here.
    86          }
    87  }
    88  ```
    89  
    90  The `framework.TestContext` is a wrapper around the underlying `testing.T` and implements the same interface. Test code
    91  should generally not interact with the `testing.T` directly.
    92  
    93  In the `TestMain`, you can also restrict the test to particular environment, apply labels, or do test-wide setup, such as
    94   deploying Istio.
    95  
    96  ```go
    97  func TestMain(m *testing.M) {
    98      framework.
    99          NewSuite("mysuite", m).
   100          // Deploy Istio on the cluster
   101          Setup(istio.Setup(nil, nil)).
   102          // Run your own custom setup
   103          Setup(mySetup).
   104          Run()
   105  }
   106  
   107  func mySetup(ctx resource.Context) error {
   108      // Your own setup code
   109      return nil
   110  }
   111  ```
   112  
   113  ### Sub-Tests
   114  
   115  Go allows you to run sub-tests with `t.Run()`. Similarly, this framework supports nesting tests with `ctx.NewSubTest()`:
   116  
   117  ```go
   118  func TestMyLogic(t *testing.T) {
   119      framework.
   120          NewTest(t).
   121          Run(func(ctx framework.TestContext) {
   122  
   123              // Create a component
   124              g := galley.NewOrFail(ctx, ctx, cfg)
   125  
   126              configs := []struct{
   127                  name: string
   128                  yaml: string
   129              } {
   130                  // Some array of YAML
   131              }
   132  
   133              for _, cfg := range configs {
   134                  ctx.NewSubTest(cfg.name).
   135                      Run(func(ctx framework.TestContext) {
   136                          ctx.ApplyConfigOrFail(ctx, nil, mycfg)
   137                          // Do more stuff here.
   138                      })
   139              }
   140          })
   141  }
   142  ```
   143  
   144  Under the hood, calling `subtest.Run()` delegates to `t.Run()` in order to create a child `testing.T`.
   145  
   146  ### Parallel Tests
   147  
   148  Many tests can take a while to start up for a variety of reasons, such as waiting for pods to start or waiting
   149  for a particular piece of configuration to propagate throughout the system. Where possible, it may be desirable
   150  to run these sorts of tests in parallel:
   151  
   152  ```go
   153  func TestMyLogic(t *testing.T) {
   154      framework.
   155          NewTest(t).
   156          RunParallel(func(ctx framework.TestContext) {
   157              // ...
   158          }
   159  }
   160  ```
   161  
   162  Under the hood, this relies on Go's `t.Parallel()` and will, therefore, have the same behavior.
   163  
   164  A parallel test will run in parallel with siblings that share the same parent test. The parent test function
   165  will exit before the parallel children are executed. It should be noted that if the parent test is prevented
   166  from exiting (e.g. parent test is waiting for something to occur within the child test), the test will
   167  deadlock.
   168  
   169  Consider the following example:
   170  
   171  ```go
   172  func TestMyLogic(t *testing.T) {
   173      framework.NewTest(t).
   174          Run(func(ctx framework.TestContext) {
   175              ctx.NewSubTest("T1").
   176                  Run(func(ctx framework.TestContext) {
   177                      ctx.NewSubTest("T1a").
   178                          RunParallel(func(ctx framework.TestContext) {
   179                              // Run in parallel with T1b
   180                          })
   181                      ctx.NewSubTest("T1b").
   182                          RunParallel(func(ctx framework.TestContext) {
   183                              // Run in parallel with T1a
   184                          })
   185                      // Exits before T1a and T1b are run.
   186                  })
   187  
   188              ctx.NewSubTest("T2").
   189                  Run(func(ctx framework.TestContext) {
   190                      ctx.NewSubTest("T2a").
   191                          RunParallel(func(ctx framework.TestContext) {
   192                              // Run in parallel with T2b
   193                          })
   194                      ctx.NewSubTest("T2b").
   195                          RunParallel(func(ctx framework.TestContext) {
   196                              // Run in parallel with T2a
   197                          })
   198                      // Exits before T2a and T2b are run.
   199                  })
   200          })
   201  }
   202  ```
   203  
   204  In the example above, non-parallel parents T1 and T2 contain parallel children T1a, T1b, T2a, T2b.
   205  
   206  Since both T1 and T2 are non-parallel, they are run synchronously: T1 followed by T2. After T1 exits,
   207  T1a and T1b are run asynchronously with each other. After T1a and T1b complete, T2 is then run in the
   208  same way: T2 exits, then T2a and T2b are run asynchronously to completion.
   209  
   210  ### Using Components
   211  
   212  The framework itself is just a platform for running tests and tracking resources. Without these `resources`, there
   213  isn't much added value. Enter: components.
   214  
   215  Components are utilities that provide abstractions for Istio resources. They are maintained in the
   216  [components package](https://github.com/istio/istio/tree/master/pkg/test/framework/components), which defines
   217  various Istio components such as galley, pilot, and namespaces.
   218  
   219  Each component defines their own API which simplifies their use from test code, abstracting away the
   220  environment-specific details. This means that the test code can (and should, where possible) be written in an
   221  environment-agnostic manner, so that they can be run against any Istio implementation.
   222  
   223  For example, the following code creates and then interacts with a Galley and Pilot component:
   224  
   225  ```go
   226  func TestMyLogic(t *testing.T) {
   227      framework.
   228          NewTest(t).
   229          Run(func(ctx framework.TestContext) {
   230              // Create the components.
   231              g := galley.NewOrFail(ctx, ctx, galley.Config{})
   232              p := pilot.NewOrFail(ctx, ctx, pilot.Config {})
   233  
   234              // Apply configuration via Galley.
   235              ctx.ApplyConfigOrFail(ctx, nil, mycfg)
   236  
   237              // Wait until Pilot has received the configuration update.
   238              p.StartDiscoveryOrFail(t, discoveryRequest)
   239              p.WatchDiscoveryOrFail(t, timeout,
   240                  func(response *xdsapi.DiscoveryResponse) (b bool, e error) {
   241                      // Validate that the discovery response has the configuration applied.
   242                  })
   243              // Do more stuff...
   244          }
   245  }
   246  ```
   247  
   248  When a component is created, the framework tracks its lifecycle. When the test exits, any components that were
   249  created during the test are automatically closed.
   250  
   251  ### Writing Components
   252  
   253  To add a new component, you'll first need to create a top-level folder for your component under the
   254  [components folder](https://github.com/istio/istio/tree/master/pkg/test/framework/components).
   255  
   256  ```console
   257  $ cd ${ISTIO}/pkg/test/framework/components
   258  $ mkdir mycomponent
   259  ```
   260  
   261  You'll then need to define your component's API.
   262  
   263  ```go
   264  package mycomponent
   265  
   266  type Instance interface {
   267      resource.Resource
   268  
   269      DoStuff() error
   270      DoStuffOrFail(t test.Failer)
   271  }
   272  ```
   273  
   274  | NOTE: A common pattern is to provide two versions of many methods: one that returns an error as well as an `OrFail` version that fails the test upon encountering an error. This provides options to the calling test and helps to simplify the calling logic. |
   275  | --- |
   276  
   277  Next you need to implement your component for one or more environments. If possible, create both a native and Kubernetes version.
   278  
   279  ```go
   280  package mycomponent
   281  
   282  type nativeComponent struct {
   283      id resource.ID
   284      // ...
   285  }
   286  
   287  func newNative(ctx resource.Context) (Instance, error) {
   288      if config.Galley == nil {
   289          return nil, errors.New("galley must be provided")
   290      }
   291  
   292      instance := &nativeComponent{}
   293      instance.id = ctx.TrackResource(instance)
   294  
   295      //...
   296      return instance, nil
   297  }
   298  
   299  func (c *nativeComponent) ID() resource.ID {
   300      return c.id
   301  }
   302  ```
   303  
   304  Each implementation of the component must implement `resource.Resource`, which just exposes a unique identifier for your
   305  component instances used for resource tracking by the framework. To get the ID, the component must call `ctx.TrackResource`
   306  during construction.
   307  
   308  Finally, you'll need to provide an environment-agnostic constructor for your component:
   309  
   310  ```go
   311  package mycomponent
   312  
   313  func New(ctx resource.Context) (i Instance, err error){
   314      err = resource.UnsupportedEnvironment(ctx.Environment())
   315      ctx.Environment().Case(environment.Native, func() {
   316          i, err = newNative(ctx)
   317      })
   318      ctx.Environment().Case(environment.Kube, func() {
   319          i, err = newKube(ctx)
   320      })
   321      return
   322  }
   323  
   324  func NewOrFail(t test.Failer, ctx resource.Context) Instance {
   325      i, err := New(ctx)
   326      if err != nil {
   327          t.Fatal(err)
   328      }
   329      return i
   330  }
   331  ```
   332  
   333  Now that everything is in place, you can begin using your component:
   334  
   335  ```go
   336  func TestMyLogic(t *testing.T) {
   337      framework.
   338          NewTest(t).
   339          Run(func(ctx framework.TestContext) {
   340              // Create the components.
   341              g := myComponent.NewOrFail(ctx, ctx)
   342  
   343              // Do more stuff...
   344          }
   345  }
   346  ```
   347  
   348  ## Running Tests
   349  
   350  The test framework builds on top of the Go testing infrastructure, and is therefore compatible with
   351  the standard `go test` command-line.  For example, to run the tests under the `/tests/integration/mycomponent`
   352  using the default (native) environment, you can simply type:
   353  
   354  ```console
   355  $ go test -tags=integ ./tests/integration/mycomponent/...
   356  ```
   357  
   358  Note that samples below invoking variations of ```go test ./...``` are intended to be run from the ```tests/integration``` directory.
   359  
   360  Tests are tagged with the `integ` build target to avoid accidental invocation. If this is not set, no tests will be run.
   361  
   362  ### Test Parellelism and Kubernetes
   363  
   364  By default, Go will run tests within the same package (i.e. suite) synchronously. However, tests in other packages
   365  may be run concurrently.
   366  
   367  When running in the Kubernetes environment this can be problematic for suites that deploy Istio. The Istio deployment,
   368  as it stands is a singleton per cluster. If multiple suites attempt to deploy/configure Istio,
   369  they can corrupt each other and/or simply fail.  To avoid this issue, you have a couple of options:
   370  
   371  1. Run one suite per command (e.g. `go test ./tests/integration/mysuite/...`)
   372  1. Disable parallelism with `-p 1` (e.g. `go test -p 1 ./...`). A major disadvantage to doing this is that it will also disable
   373  parallelism within the suite, even when explicitly specified via [RunParallel](#parallel-tests).
   374  
   375  ### Test Selection
   376  
   377  When no flags are specified, the test framework will run all applicable tests. It is possible to filter in/out specific
   378  tests using 2 mechanisms:
   379  
   380  1. The standard ```-run <regexp>``` flag, as exposed by Go's own test framework.
   381  1. ```--istio.test.select <filter-expr>``` flag to select/skip framework-aware tests that use labels.
   382  
   383  For example, if a test, or test suite uses labels in this fashion:
   384  
   385  ```go
   386  func TestMain(m *testing.M) {
   387      framework.
   388          NewSuite("galley_conversion", m).
   389          // Test is tagged with "CustomSetup" label
   390          Label(label.CustomSetup).
   391          Run()
   392  ```
   393  
   394  Then you can explicitly select execution of such tests using label based selection. For example, the following expression
   395  will select only the tests that have the ```label.CustomSetup``` label.
   396  
   397  ```console
   398  $ go test ./... --istio.test.select +customsetup
   399  ```
   400  
   401  Similarly, you can exclude tests that use ```label.CustomSetup``` label by:
   402  
   403  ```console
   404  $ go test ./... --istio.test.select -customsetup
   405  ```
   406  
   407  You can "and" the predicates by separating with commas:
   408  
   409  ```console
   410  $ go test ./... --istio.test.select +customsetup,-postsubmit
   411  ```
   412  
   413  This will select tests that have ```label.CustomSetup``` only. It will **not** select tests that have both ```label.CustomSetup```
   414  and ```label.Postsubmit```.
   415  
   416  ### Running Tests on CI
   417  
   418  Istio's CI/CD system is composed of 2 parts:
   419  
   420  Tool | Description |
   421  ---|---
   422  [Prow](https://github.com/kubernetes/test-infra/tree/master/prow) | Kubernetes-based CI/CD system developed by the Kubernetes community and is deployed in Google Kubernetes Engine (GKE).
   423  [TestGrid](https://k8s-testgrid.appspot.com/istio-release) | A Kubernetes dashboard used for visualizing the status of the Prow jobs.
   424  
   425  Test suites are defined for each toplevel directory (such as `pilot` and `telemetry`), so any tests added to these directories will automatically be run in CI.
   426  
   427  If you need to add a new test suite, it can be added to the [job configuration](https://github.com/istio/test-infra/blob/master/prow/config/jobs/istio.yaml).
   428  
   429  ## Environments
   430  
   431  The test binaries run in a Kubernetes cluster, but the test logic runs in the test binary.
   432  
   433  ```console
   434  $ go test ./... -p 1
   435  ```
   436  
   437  | WARNING: ```-p 1``` is required when running directly in the ```tests/integration/``` folder. |
   438  | --- |
   439  
   440  You will need to provide a K8s cluster to run the tests against.
   441  (See [here](https://github.com/istio/istio/blob/master/tests/integration/GKE.md)
   442  for info about how to set up a suitable GKE cluster.)
   443  You can specify the kube config file that should be used to use for connecting to the cluster, through
   444  command-line:
   445  
   446  ```console
   447  $ go test ./... -p 1 --istio.test.kube.config ~/.kube/config
   448  ```
   449  
   450  If not specified, `~/.kube/config` will be used by default.
   451  
   452  **Be aware that any existing content will be altered and/or removed from the cluster**.
   453  
   454  Note that the HUB and TAG environment variables **must** be set when running tests in the Kubernetes environment.
   455  
   456  ## Diagnosing Failures
   457  
   458  ### Working Directory
   459  
   460  The test framework will generate additional diagnostic output in its work directory. Typically, this is
   461  created under the host operating system's temporary folder (which can be overridden using
   462  the `--istio.test.work_dir` flag). The name of the work dir will be based on the test id that is supplied in
   463  a tests TestMain method. These files typically contain some of the logging & diagnostic output that components
   464  spew out as part of test execution
   465  
   466  ```console
   467  $ go test galley/... --istio.test.work_dir /foo
   468    ...
   469  
   470  $ ls /foo
   471    galley-test-4ef25d910d2746f9b38/
   472  
   473  $ ls /foo/galley-test-4ef25d910d2746f9b38/
   474    istio-system-1537332205890088657.yaml
   475    ...
   476  ```
   477  
   478  ### Enabling CI Mode
   479  
   480  When executing in the CI systems, the makefiles use the ```--istio.test.ci``` flag. This flag causes a few changes in
   481  behavior. Specifically, more verbose logging output will be displayed, some of the timeout values will be more relaxed, and
   482  additional diagnostic data will be dumped into the working directory at the end of the test execution.
   483  
   484  The flag is not enabled by default to provide a better U/X when running tests locally (i.e. additional logging can clutter
   485  test output and error dumping can take quite a while). However, if you see a behavior difference between local and CI runs,
   486  you can enable the flag to make the tests work in a similar fashion.
   487  
   488  ### Preserving State (No Cleanup)
   489  
   490  By default, the test framework will cleanup all deployed artifacts after the test run, especially on the Kubernetes
   491  environment. You can specify the ```--istio.test.nocleanup``` flag to stop the framework from cleaning up the state
   492  for investigation.
   493  
   494  ### Additional Logging
   495  
   496  The framework accepts standard istio logging flags. You can use these flags to enable additional logging for both the
   497  framework, as well as some of the components that are used in-line in the native environment:
   498  
   499  ```console
   500  $ go test ./... --log_output_level=tf:debug
   501  ```
   502  
   503  The above example will enable debugging logging for the test framework (```tf```) and the MCP protocol stack (```mcp```).
   504  
   505  ### Running Tests Under Debugger (GoLand)
   506  
   507  The tests authored in the new test framework can be debugged directly under GoLand using the debugger. If you want to
   508  pass command-line flags to the test while running under the debugger, you can use the
   509  [Run/Debug configurations dialog](https://i.stack.imgur.com/C6y0L.png) to specify these flags as program arguments.
   510  
   511  ## Reference
   512  
   513  ### Command-Line Flags
   514  
   515  The test framework supports the following command-line flags:
   516  
   517  | Name | Type | Description |
   518  |------|------|-------------|
   519  | -istio.test.work_dir | string | Local working directory for creating logs/temp files. If left empty, os.TempDir() is used. |
   520  | -istio.test.ci | bool | Enable CI Mode. Additional logging and state dumping will be enabled. |
   521  | -istio.test.nocleanup | bool | Do not cleanup resources after test completion. |
   522  | -istio.test.select | string | Comma separated list of labels for selecting tests to run (e.g. 'foo,+bar-baz'). |
   523  | -istio.test.hub | string | Container registry hub to use (default HUB environment variable). |
   524  | -istio.test.tag | string | Common Container tag to use when deploying container images (default TAG environment variable). |
   525  | -istio.test.pullpolicy | string | Common image pull policy to use when deploying container images. |
   526  | -istio.test.kube.config | string | A comma-separated list of paths to kube config files for cluster environments. (default ~/.kube/config). |
   527  | -istio.test.kube.deploy | bool | Deploy Istio into the target Kubernetes environment. (default true). |
   528  | -istio.test.kube.deployEastWestGW | bool | Deploy Istio east west gateway into the target Kubernetes environment. (default true). |
   529  | -istio.test.kube.systemNamespace | string | The namespace where the Istio components reside in a typical deployment. (default "istio-system"). |
   530  | -istio.test.kube.helm.values | string | Manual overrides for Helm values file. Only valid when deploying Istio. |
   531  | -istio.test.kube.helm.iopFile | string | IstioOperator spec file. This can be an absolute path or relative to the repository root. Defaults to "tests/integration/iop-integration-test-defaults.yaml". |
   532  | -istio.test.kube.loadbalancer | bool | Used to obtain the right IP address for ingress gateway. This should be false for any environment that doesn't support a LoadBalancer type. |
   533  | -istio.test.revision | string | Overwrite the default namespace label (istio-enabled=true) with revision lable (istio.io/rev=XXX). (default is no overwrite). |
   534  | -istio.test.skip | []string | Skip tests matching the regular expression. This follows the semantics of -test.run. |
   535  | -istio.test.skipVM | bool | Skip all the VM related parts in all the tests. (default is "false"). |
   536  | -istio.test.helmRepo | string | Overwrite the default helm Repo used for the tests. |
   537  | -istio.test.ambient | bool | Indicate the use of ambient mesh. |
   538  | -istio.test.openshift | bool | Set to `true` when running the tests in an OpenShift cluster, rather than in KinD. |
   539  
   540  ## Notes
   541  
   542  ### Running on a Mac
   543  
   544  * Currently some _native_ tests fail when being run on a Mac with an error like:
   545  
   546  ```plain
   547  unable to locate an Envoy binary
   548  ```
   549  
   550  This is documented in this [PR](https://github.com/istio/istio/issues/13677). Once the Envoy binary is available for the Mac,
   551  these tests will hopefully succeed.
   552  
   553  * If one uses Docker for Mac for the kubernetes environment be sure to specify the `-istio.test.kube.loadbalancer=false` parameter. This solves an error like:
   554  
   555  ```plain
   556  service ingress is not available yet
   557  ```