istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tests/integration/pilot/multicluster_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  	"context"
    22  	"fmt"
    23  	"testing"
    24  	"time"
    25  
    26  	corev1 "k8s.io/api/core/v1"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"sigs.k8s.io/yaml"
    29  
    30  	"istio.io/istio/pkg/test/framework"
    31  	"istio.io/istio/pkg/test/framework/components/cluster"
    32  	"istio.io/istio/pkg/test/framework/components/echo"
    33  	"istio.io/istio/pkg/test/framework/components/echo/check"
    34  	"istio.io/istio/pkg/test/util/retry"
    35  	"istio.io/istio/pkg/test/util/tmpl"
    36  )
    37  
    38  var (
    39  	multiclusterRetryTimeout = retry.Timeout(1 * time.Minute)
    40  	multiclusterRetryDelay   = retry.Delay(500 * time.Millisecond)
    41  )
    42  
    43  func TestClusterLocal(t *testing.T) {
    44  	// nolint: staticcheck
    45  	framework.NewTest(t).
    46  		RequiresMinClusters(2).
    47  		RequireIstioVersion("1.11").
    48  		Run(func(t framework.TestContext) {
    49  			// TODO use echotest to dynamically pick 2 simple pods from apps.All
    50  			sources := apps.A
    51  			to := apps.B
    52  
    53  			tests := []struct {
    54  				name  string
    55  				setup func(t framework.TestContext)
    56  			}{
    57  				{
    58  					"MeshConfig.serviceSettings",
    59  					func(t framework.TestContext) {
    60  						i.PatchMeshConfigOrFail(t, t, fmt.Sprintf(`
    61  serviceSettings: 
    62  - settings:
    63      clusterLocal: true
    64    hosts:
    65    - "%s"
    66  `, apps.B.Config().ClusterLocalFQDN()))
    67  					},
    68  				},
    69  				{
    70  					"subsets",
    71  					func(t framework.TestContext) {
    72  						cfg := tmpl.EvaluateOrFail(t, `
    73  apiVersion: networking.istio.io/v1beta1
    74  kind: DestinationRule
    75  metadata:
    76    name: mysvc-dr
    77  spec:
    78    host: {{.host}}
    79    subsets:
    80  {{- range .dst }}
    81    - name: {{ .Config.Cluster.Name }}
    82      labels:
    83        topology.istio.io/cluster: {{ .Config.Cluster.Name }}
    84  {{- end }}
    85  ---
    86  apiVersion: networking.istio.io/v1beta1
    87  kind: VirtualService
    88  metadata:
    89    name: mysvc-vs
    90  spec:
    91    hosts:
    92    - {{.host}}
    93    http:
    94  {{- range .dst }}
    95    - name: "{{ .Config.Cluster.Name }}-local"
    96      match:
    97      - sourceLabels:
    98          topology.istio.io/cluster: {{ .Config.Cluster.Name }}
    99      route:
   100      - destination:
   101          host: {{$.host}}
   102          subset: {{ .Config.Cluster.Name }}
   103  {{- end }}
   104  `, map[string]any{"src": sources, "dst": to, "host": to.Config().ClusterLocalFQDN()})
   105  						t.ConfigIstio().YAML(sources.Config().Namespace.Name(), cfg).ApplyOrFail(t)
   106  					},
   107  				},
   108  			}
   109  
   110  			for _, test := range tests {
   111  				test := test
   112  				t.NewSubTest(test.name).Run(func(t framework.TestContext) {
   113  					test.setup(t)
   114  					for _, source := range sources {
   115  						source := source
   116  						t.NewSubTest(source.Config().Cluster.StableName()).RunParallel(func(t framework.TestContext) {
   117  							source.CallOrFail(t, echo.CallOptions{
   118  								To: to,
   119  								Port: echo.Port{
   120  									Name: "http",
   121  								},
   122  								Check: check.And(
   123  									check.OK(),
   124  									check.ReachedClusters(t.AllClusters(), cluster.Clusters{source.Config().Cluster}),
   125  								),
   126  								Retry: echo.Retry{
   127  									Options: []retry.Option{multiclusterRetryDelay, multiclusterRetryTimeout},
   128  								},
   129  							})
   130  						})
   131  					}
   132  				})
   133  			}
   134  
   135  			// this runs in a separate test context - confirms the cluster local config was cleaned up
   136  			t.NewSubTest("cross cluster").Run(func(t framework.TestContext) {
   137  				for _, source := range sources {
   138  					source := source
   139  					t.NewSubTest(source.Config().Cluster.StableName()).Run(func(t framework.TestContext) {
   140  						source.CallOrFail(t, echo.CallOptions{
   141  							To: to,
   142  							Port: echo.Port{
   143  								Name: "http",
   144  							},
   145  							Check: check.And(
   146  								check.OK(),
   147  								check.ReachedTargetClusters(t),
   148  							),
   149  							Retry: echo.Retry{
   150  								Options: []retry.Option{multiclusterRetryDelay, multiclusterRetryTimeout},
   151  							},
   152  						})
   153  					})
   154  				}
   155  			})
   156  		})
   157  }
   158  
   159  func TestBadRemoteSecret(t *testing.T) {
   160  	// nolint: staticcheck
   161  	framework.NewTest(t).
   162  		RequiresMinClusters(2).
   163  		Run(func(t framework.TestContext) {
   164  			if len(t.Clusters().Primaries()) == 0 {
   165  				t.Skip("no primary cluster in framework (most likely only remote-config)")
   166  			}
   167  
   168  			// we don't need to test this per-cluster
   169  			primary := t.Clusters().Primaries()[0]
   170  			// it doesn't matter if the other cluster is a primary/remote/etc.
   171  			remote := t.Clusters().Exclude(primary)[0]
   172  
   173  			var (
   174  				ns  = i.Settings().SystemNamespace
   175  				sa  = "istio-reader-no-perms"
   176  				pod = "istiod-bad-secrets-test"
   177  			)
   178  			t.Logf("creating service account %s/%s", ns, sa)
   179  			if _, err := remote.Kube().CoreV1().ServiceAccounts(ns).Create(context.TODO(), &corev1.ServiceAccount{
   180  				ObjectMeta: metav1.ObjectMeta{Name: sa},
   181  			}, metav1.CreateOptions{}); err != nil {
   182  				t.Fatal(err)
   183  			}
   184  
   185  			// intentionally not doing this with subtests since it would be pretty slow
   186  			for _, opts := range [][]string{
   187  				{"--name", "unreachable", "--server", "https://255.255.255.255"},
   188  				{"--name", "no-permissions", "--service-account", "istio-reader-no-perms"},
   189  			} {
   190  				var secret string
   191  				retry.UntilSuccessOrFail(t, func() error {
   192  					var err error
   193  					secret, err = i.CreateRemoteSecret(t, remote, opts...)
   194  					return err
   195  				}, retry.Timeout(15*time.Second))
   196  
   197  				t.ConfigKube().YAML(ns, secret).ApplyOrFail(t)
   198  			}
   199  			// Test exec auth
   200  			// CreateRemoteSecret can never generate this, so create it manually
   201  			t.ConfigIstio().YAML(ns, `apiVersion: v1
   202  kind: Secret
   203  metadata:
   204    annotations:
   205      networking.istio.io/cluster: bad
   206    creationTimestamp: null
   207    labels:
   208      istio/multiCluster: "true"
   209    name: istio-remote-secret-bad
   210  stringData:
   211    bad: |
   212      apiVersion: v1
   213      kind: Config
   214      clusters:
   215      - cluster:
   216          server: https://127.0.0.1
   217        name: bad
   218      contexts:
   219      - context:
   220          cluster: bad
   221          user: bad
   222        name: bad
   223      current-context: bad
   224      users:
   225      - name: bad
   226        user:
   227          exec:
   228            command: /bin/sh
   229            args: ["-c", "hello world!"]
   230  ---
   231  `).ApplyOrFail(t)
   232  
   233  			// create a new istiod pod using the template from the deployment, but not managed by the deployment
   234  			t.Logf("creating pod %s/%s", ns, pod)
   235  			deps, err := primary.Kube().AppsV1().
   236  				Deployments(ns).List(context.TODO(), metav1.ListOptions{LabelSelector: "app=istiod"})
   237  			if err != nil {
   238  				t.Fatal(err)
   239  			}
   240  			if len(deps.Items) == 0 {
   241  				t.Skip("no deployments with label app=istiod")
   242  			}
   243  			pods := primary.Kube().CoreV1().Pods(ns)
   244  			podMeta := deps.Items[0].Spec.Template.ObjectMeta
   245  			podMeta.Name = pod
   246  			template := deps.Items[0].Spec.Template.Spec
   247  			for _, container := range template.Containers {
   248  				if container.Name != "discovery" {
   249  					continue
   250  				}
   251  				container.Env = append(container.Env, corev1.EnvVar{
   252  					Name:  "PILOT_REMOTE_CLUSTER_TIMEOUT",
   253  					Value: "15s",
   254  				})
   255  			}
   256  			_, err = pods.Create(context.TODO(), &corev1.Pod{
   257  				ObjectMeta: podMeta,
   258  				Spec:       deps.Items[0].Spec.Template.Spec,
   259  			}, metav1.CreateOptions{})
   260  			if err != nil {
   261  				t.Fatal(err)
   262  			}
   263  			t.Cleanup(func() {
   264  				if err := pods.Delete(context.TODO(), pod, metav1.DeleteOptions{}); err != nil {
   265  					t.Logf("error cleaning up %s: %v", pod, err)
   266  				}
   267  			})
   268  
   269  			retry.UntilSuccessOrFail(t, func() error {
   270  				pod, err := pods.Get(context.TODO(), pod, metav1.GetOptions{})
   271  				if err != nil {
   272  					return err
   273  				}
   274  				for _, status := range pod.Status.ContainerStatuses {
   275  					if status.Started != nil && !*status.Started {
   276  						return fmt.Errorf("container %s in %s is not started", status.Name, pod)
   277  					}
   278  				}
   279  				return nil
   280  			}, retry.Timeout(5*time.Minute), retry.Delay(time.Second))
   281  
   282  			// make sure the pod comes up healthy
   283  			retry.UntilSuccessOrFail(t, func() error {
   284  				pod, err := pods.Get(context.TODO(), pod, metav1.GetOptions{})
   285  				if err != nil {
   286  					return err
   287  				}
   288  				status := pod.Status.ContainerStatuses
   289  				if len(status) < 1 || !status[0].Ready {
   290  					conditions, _ := yaml.Marshal(pod.Status.ContainerStatuses)
   291  					return fmt.Errorf("%s not ready: %s", pod.Name, conditions)
   292  				}
   293  				return nil
   294  			}, retry.Timeout(time.Minute), retry.Delay(time.Second))
   295  		})
   296  }