istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tests/integration/pilot/multi_version_revision_test.go (about)

     1  //go:build integ
     2  // +build integ
     3  
     4  // Copyright Istio Authors
     5  //
     6  // Licensed under the Apache License, Version 2.0 (the "License");
     7  // you may not use this file except in compliance with the License.
     8  // You may obtain a copy of the License at
     9  //
    10  //     http://www.apache.org/licenses/LICENSE-2.0
    11  //
    12  // Unless required by applicable law or agreed to in writing, software
    13  // distributed under the License is distributed on an "AS IS" BASIS,
    14  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15  // See the License for the specific language governing permissions and
    16  // limitations under the License.
    17  
    18  package pilot
    19  
    20  import (
    21  	"fmt"
    22  	"path/filepath"
    23  	"strings"
    24  	"testing"
    25  	"time"
    26  
    27  	"istio.io/istio/pkg/config/protocol"
    28  	"istio.io/istio/pkg/test/env"
    29  	"istio.io/istio/pkg/test/framework"
    30  	"istio.io/istio/pkg/test/framework/components/echo"
    31  	"istio.io/istio/pkg/test/framework/components/echo/check"
    32  	"istio.io/istio/pkg/test/framework/components/echo/deployment"
    33  	"istio.io/istio/pkg/test/framework/components/namespace"
    34  	"istio.io/istio/pkg/test/framework/label"
    35  	"istio.io/istio/pkg/test/framework/resource/config/apply"
    36  	"istio.io/istio/pkg/test/util/file"
    37  	"istio.io/istio/pkg/test/util/retry"
    38  )
    39  
    40  const (
    41  	NMinusOne   = "1.10.0"
    42  	NMinusTwo   = "1.9.5"
    43  	NMinusThree = "1.8.6"
    44  	NMinusFour  = "1.7.6"
    45  	NMinusFive  = "1.6.11"
    46  )
    47  
    48  var versions = []string{NMinusOne, NMinusTwo, NMinusThree, NMinusFour, NMinusFive}
    49  
    50  type revisionedNamespace struct {
    51  	revision  string
    52  	namespace namespace.Instance
    53  }
    54  
    55  // TestMultiVersionRevision tests traffic between data planes running under differently versioned revisions
    56  // should test all possible revisioned namespace pairings to test traffic between all versions
    57  func TestMultiVersionRevision(t *testing.T) {
    58  	// nolint: staticcheck
    59  	framework.NewTest(t).
    60  		RequiresSingleCluster().
    61  		RequiresLocalControlPlane().
    62  		// Requires installation of CPs from manifests, won't succeed
    63  		// if existing CPs have different root cert
    64  		Label(label.CustomSetup).
    65  		Run(func(t framework.TestContext) {
    66  			t.Skip("https://github.com/istio/istio/pull/46213")
    67  			skipIfK8sVersionUnsupported(t)
    68  
    69  			// keep track of applied configurations and clean up after the test
    70  			configs := make(map[string]string)
    71  			t.CleanupConditionally(func() {
    72  				for _, config := range configs {
    73  					_ = t.ConfigIstio().YAML("istio-system", config).Delete()
    74  				}
    75  			})
    76  
    77  			revisionedNamespaces := []revisionedNamespace{}
    78  			for _, v := range versions {
    79  				installRevisionOrFail(t, v, configs)
    80  
    81  				// create a namespace pointed to the revisioned control plane we just installed
    82  				rev := strings.ReplaceAll(v, ".", "-")
    83  				ns, err := namespace.New(t, namespace.Config{
    84  					Prefix:   fmt.Sprintf("revision-%s", rev),
    85  					Inject:   true,
    86  					Revision: rev,
    87  				})
    88  				if err != nil {
    89  					t.Fatalf("failed to created revisioned namespace: %v", err)
    90  				}
    91  				revisionedNamespaces = append(revisionedNamespaces, revisionedNamespace{
    92  					revision:  rev,
    93  					namespace: ns,
    94  				})
    95  			}
    96  
    97  			// create an echo instance in each revisioned namespace, all these echo
    98  			// instances will be injected with proxies from their respective versions
    99  			builder := deployment.New(t)
   100  
   101  			for _, ns := range revisionedNamespaces {
   102  				builder = builder.WithConfig(echo.Config{
   103  					Service:   fmt.Sprintf("revision-%s", ns.revision),
   104  					Namespace: ns.namespace,
   105  					Ports: []echo.Port{
   106  						{
   107  							Name:         "http",
   108  							Protocol:     protocol.HTTP,
   109  							WorkloadPort: 8000,
   110  						},
   111  						{
   112  							Name:         "tcp",
   113  							Protocol:     protocol.TCP,
   114  							WorkloadPort: 9000,
   115  						},
   116  						{
   117  							Name:         "grpc",
   118  							Protocol:     protocol.GRPC,
   119  							WorkloadPort: 9090,
   120  						},
   121  					},
   122  				})
   123  			}
   124  			instances := builder.BuildOrFail(t)
   125  			// add an existing pod from apps to the rotation to avoid an extra deployment
   126  			instances = append(instances, apps.A[0])
   127  
   128  			testAllEchoCalls(t, instances)
   129  		})
   130  }
   131  
   132  // testAllEchoCalls takes list of revisioned namespaces and generates list of echo calls covering
   133  // communication between every pair of namespaces
   134  func testAllEchoCalls(t framework.TestContext, echoInstances []echo.Instance) {
   135  	trafficTypes := []string{"http", "tcp", "grpc"}
   136  	for _, from := range echoInstances {
   137  		for _, to := range echoInstances {
   138  			if from == to {
   139  				continue
   140  			}
   141  			for _, trafficType := range trafficTypes {
   142  				t.NewSubTest(fmt.Sprintf("%s-%s->%s", trafficType, from.Config().Service, to.Config().Service)).
   143  					Run(func(t framework.TestContext) {
   144  						retry.UntilSuccessOrFail(t, func() error {
   145  							result, err := from.Call(echo.CallOptions{
   146  								To:    to,
   147  								Count: 1,
   148  								Port: echo.Port{
   149  									Name: trafficType,
   150  								},
   151  								Retry: echo.Retry{
   152  									NoRetry: true,
   153  								},
   154  							})
   155  							return check.And(
   156  								check.NoError(),
   157  								check.OK()).Check(result, err)
   158  						}, retry.Delay(time.Millisecond*150))
   159  					})
   160  			}
   161  		}
   162  	}
   163  }
   164  
   165  // installRevisionOrFail takes an Istio version and installs a revisioned control plane running that version
   166  // provided istio version must be present in tests/integration/pilot/testdata/upgrade for the installation to succeed
   167  func installRevisionOrFail(t framework.TestContext, version string, configs map[string]string) {
   168  	config, err := file.ReadTarFile(filepath.Join(env.IstioSrc, "tests/integration/pilot/testdata/upgrade",
   169  		fmt.Sprintf("%s-install.yaml.tar", version)))
   170  	if err != nil {
   171  		t.Fatalf("could not read installation config: %v", err)
   172  	}
   173  	configs[version] = config
   174  	if err := t.ConfigIstio().YAML(i.Settings().SystemNamespace, config).Apply(apply.NoCleanup); err != nil {
   175  		t.Fatal(err)
   176  	}
   177  }
   178  
   179  // skipIfK8sVersionUnsupported skips the test if we're running on a k8s version that is not expected to work
   180  // with any of the revision versions included in the test (i.e. istio 1.7 not supported on k8s 1.15)
   181  func skipIfK8sVersionUnsupported(t framework.TestContext) {
   182  	if !t.Clusters().Default().MinKubeVersion(16) {
   183  		t.Skipf("k8s version not supported for %s (<%s)", t.Name(), "1.16")
   184  	}
   185  	// Kubernetes 1.22 drops support for a number of legacy resources, so we cannot install the old versions
   186  	if !t.Clusters().Default().MaxKubeVersion(21) {
   187  		t.Skipf("k8s version not supported for %s (>%s)", t.Name(), "1.21")
   188  	}
   189  }