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 }