1// Copyright 2019 Istio Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package pilot 16 17import ( 18 "encoding/json" 19 "fmt" 20 "regexp" 21 "strings" 22 "testing" 23 24 "github.com/onsi/gomega" 25 26 "istio.io/istio/pkg/test/framework" 27 "istio.io/istio/pkg/test/framework/components/echo" 28 "istio.io/istio/pkg/test/framework/components/echo/echoboot" 29 "istio.io/istio/pkg/test/framework/components/galley" 30 "istio.io/istio/pkg/test/framework/components/istioctl" 31 "istio.io/istio/pkg/test/framework/components/namespace" 32 "istio.io/istio/pkg/test/framework/components/pilot" 33 "istio.io/istio/pkg/test/framework/resource/environment" 34 "istio.io/istio/pkg/test/util/file" 35) 36 37const ( 38 describeSvcAOutput = `Service: a\..* 39 Port: grpc 7070/GRPC targets pod port 7070 40 Port: http 80/HTTP targets pod port 8090 417070 DestinationRule: a\..* for "a" 42 Matching subsets: v1 43 No Traffic Policy 447070 VirtualService: a\..* 45 when headers are end-user=jason 467070 RBAC policies: ns\[.*\]-policy\[integ-test\]-rule\[0\] 4780 DestinationRule: a\..* for "a" 48 Matching subsets: v1 49 No Traffic Policy 5080 VirtualService: a\..* 51 when headers are end-user=jason 52` 53 54 describePodAOutput = `Pod: .* 55 Pod Ports: 7070 \(app\), 8090 \(app\), 8080 \(app\), 3333 \(app\), 15090 \(istio-proxy\) 56-------------------- 57Service: a\..* 58 Port: grpc 7070\/GRPC targets pod port 7070 59 Port: http 80\/HTTP targets pod port 8090 607070 DestinationRule: a\..* for "a" 61 Matching subsets: v1 62 No Traffic Policy 637070 VirtualService: a\..* 64 when headers are end-user=jason 657070 RBAC policies: ns\[.*\]-policy\[integ-test\]-rule\[0\] 6680 DestinationRule: a\..* for "a" 67 Matching subsets: v1 68 No Traffic Policy 6980 VirtualService: a\..* 70 when headers are end-user=jason 71` 72 73 addToMeshPodAOutput = `deployment .* updated successfully with Istio sidecar injected. 74Next Step: Add related labels to the deployment to align with Istio's requirement: https://istio.io/docs/setup/kubernetes/additional-setup/requirements/ 75` 76 removeFromMeshPodAOutput = `deployment .* updated successfully with Istio sidecar un-injected.` 77) 78 79func TestWait(t *testing.T) { 80 framework.NewTest(t). 81 RequiresEnvironment(environment.Kube). 82 Run(func(ctx framework.TestContext) { 83 ns := namespace.NewOrFail(t, ctx, namespace.Config{ 84 Prefix: "default", 85 Inject: true, 86 }) 87 g.ApplyConfigOrFail(t, ns, ` 88apiVersion: networking.istio.io/v1alpha3 89kind: VirtualService 90metadata: 91 name: reviews 92spec: 93 gateways: [missing-gw] 94 hosts: 95 - reviews 96 http: 97 - route: 98 - destination: 99 host: reviews 100`) 101 istioCtl := istioctl.NewOrFail(ctx, ctx, istioctl.Config{}) 102 istioCtl.InvokeOrFail(t, []string{"x", "wait", "VirtualService", "reviews." + ns.Name()}) 103 }) 104} 105 106// This test requires `--istio.test.env=kube` because it tests istioctl doing PodExec 107// TestVersion does "istioctl version --remote=true" to verify the CLI understands the data plane version data 108func TestVersion(t *testing.T) { 109 framework. 110 NewTest(t). 111 RequiresEnvironment(environment.Kube). 112 Run(func(ctx framework.TestContext) { 113 g := galley.NewOrFail(t, ctx, galley.Config{}) 114 _ = pilot.NewOrFail(t, ctx, pilot.Config{Galley: g}) 115 cfg := i.Settings() 116 117 istioCtl := istioctl.NewOrFail(ctx, ctx, istioctl.Config{}) 118 119 args := []string{"version", "--remote=true", fmt.Sprintf("--istioNamespace=%s", cfg.SystemNamespace)} 120 121 output, _ := istioCtl.InvokeOrFail(t, args) 122 123 // istioctl will return a single "control plane version" if all control plane versions match 124 controlPlaneRegex := regexp.MustCompile(`control plane version: [a-z0-9\-]*`) 125 if controlPlaneRegex.MatchString(output) { 126 return 127 } 128 129 ctx.Logf("Did not find control plane version. This may mean components have different versions.") 130 131 // At this point, we expect the version for each component 132 expectedRegexps := []*regexp.Regexp{ 133 regexp.MustCompile(`citadel version: [a-z0-9\-]*`), 134 regexp.MustCompile(`client version: [a-z0-9\-]*`), 135 regexp.MustCompile(`egressgateway version: [a-z0-9\-]*`), 136 regexp.MustCompile(`ingressgateway version: [a-z0-9\-]*`), 137 regexp.MustCompile(`istiod version: [a-z0-9\-]*`), 138 regexp.MustCompile(`galley version: [a-z0-9\-]*`), 139 regexp.MustCompile(`policy version: [a-z0-9\-]*`), 140 regexp.MustCompile(`sidecar-injector version: [a-z0-9\-]*`), 141 regexp.MustCompile(`telemetry version: [a-z0-9\-]*`), 142 } 143 for _, regexp := range expectedRegexps { 144 if !regexp.MatchString(output) { 145 ctx.Fatalf("Output didn't match for 'istioctl %s'\n got %v\nwant: %v", 146 strings.Join(args, " "), output, regexp) 147 } 148 } 149 }) 150} 151 152func TestDescribe(t *testing.T) { 153 framework.NewTest(t). 154 RequiresEnvironment(environment.Kube). 155 RunParallel(func(ctx framework.TestContext) { 156 ns := namespace.NewOrFail(ctx, ctx, namespace.Config{ 157 Prefix: "istioctl-describe", 158 Inject: true, 159 }) 160 161 deployment := file.AsStringOrFail(t, "../istioctl/testdata/a.yaml") 162 g.ApplyConfigOrFail(t, ns, deployment) 163 164 var a echo.Instance 165 echoboot.NewBuilderOrFail(ctx, ctx). 166 With(&a, echoConfig(ns, "a")). 167 BuildOrFail(ctx) 168 169 if err := a.WaitUntilCallable(a); err != nil { 170 t.Fatal(err) 171 } 172 istioCtl := istioctl.NewOrFail(ctx, ctx, istioctl.Config{}) 173 174 podID, err := getPodID(a) 175 if err != nil { 176 ctx.Fatalf("Could not get Pod ID: %v", err) 177 } 178 179 var output string 180 var args []string 181 g := gomega.NewGomegaWithT(t) 182 183 // When this test passed the namespace through --namespace it was flakey 184 // because istioctl uses a global variable for namespace, and this test may 185 // run in parallel. 186 args = []string{"--namespace=dummy", 187 "x", "describe", "pod", fmt.Sprintf("%s.%s", podID, ns.Name())} 188 output, _ = istioCtl.InvokeOrFail(t, args) 189 g.Expect(output).To(gomega.MatchRegexp(describePodAOutput)) 190 191 args = []string{"--namespace=dummy", 192 "x", "describe", "svc", fmt.Sprintf("a.%s", ns.Name())} 193 output, _ = istioCtl.InvokeOrFail(t, args) 194 g.Expect(output).To(gomega.MatchRegexp(describeSvcAOutput)) 195 }) 196} 197 198func getPodID(i echo.Instance) (string, error) { 199 wls, err := i.Workloads() 200 if err != nil { 201 return "", nil 202 } 203 204 for _, wl := range wls { 205 hostname := strings.Split(wl.Sidecar().NodeID(), "~")[2] 206 podID := strings.Split(hostname, ".")[0] 207 return podID, nil 208 } 209 210 return "", fmt.Errorf("no workloads") 211} 212 213func TestAddToAndRemoveFromMesh(t *testing.T) { 214 framework.NewTest(t). 215 RequiresEnvironment(environment.Kube). 216 RunParallel(func(ctx framework.TestContext) { 217 ns := namespace.NewOrFail(t, ctx, namespace.Config{ 218 Prefix: "istioctl-add-to-mesh", 219 Inject: true, 220 }) 221 222 var a echo.Instance 223 echoboot.NewBuilderOrFail(ctx, ctx). 224 With(&a, echoConfig(ns, "a")). 225 BuildOrFail(ctx) 226 227 istioCtl := istioctl.NewOrFail(ctx, ctx, istioctl.Config{}) 228 229 var output string 230 var args []string 231 g := gomega.NewGomegaWithT(t) 232 233 // able to remove from mesh when the deployment is auto injected 234 args = []string{fmt.Sprintf("--namespace=%s", ns.Name()), 235 "x", "remove-from-mesh", "service", "a"} 236 output, _ = istioCtl.InvokeOrFail(t, args) 237 g.Expect(output).To(gomega.MatchRegexp(removeFromMeshPodAOutput)) 238 239 // remove from mesh should be clean 240 // users can add it back to mesh successfully 241 if err := a.WaitUntilCallable(a); err != nil { 242 t.Fatal(err) 243 } 244 245 args = []string{fmt.Sprintf("--namespace=%s", ns.Name()), 246 "x", "add-to-mesh", "service", "a"} 247 output, _ = istioCtl.InvokeOrFail(t, args) 248 g.Expect(output).To(gomega.MatchRegexp(addToMeshPodAOutput)) 249 }) 250} 251 252func TestProxyConfig(t *testing.T) { 253 framework.NewTest(t). 254 RequiresEnvironment(environment.Kube). 255 Run(func(ctx framework.TestContext) { 256 ns := namespace.NewOrFail(ctx, ctx, namespace.Config{ 257 Prefix: "istioctl-pc", 258 Inject: true, 259 }) 260 261 var a echo.Instance 262 echoboot.NewBuilderOrFail(ctx, ctx). 263 With(&a, echoConfig(ns, "a")). 264 BuildOrFail(ctx) 265 266 istioCtl := istioctl.NewOrFail(ctx, ctx, istioctl.Config{}) 267 268 podID, err := getPodID(a) 269 if err != nil { 270 ctx.Fatalf("Could not get Pod ID: %v", err) 271 } 272 273 var output string 274 var args []string 275 g := gomega.NewGomegaWithT(t) 276 277 args = []string{"--namespace=dummy", 278 "pc", "bootstrap", fmt.Sprintf("%s.%s", podID, ns.Name())} 279 output, _ = istioCtl.InvokeOrFail(t, args) 280 jsonOutput := jsonUnmarshallOrFail(t, strings.Join(args, " "), output) 281 g.Expect(jsonOutput).To(gomega.HaveKey("bootstrap")) 282 283 args = []string{"--namespace=dummy", 284 "pc", "cluster", fmt.Sprintf("%s.%s", podID, ns.Name()), "-o", "json"} 285 output, _ = istioCtl.InvokeOrFail(t, args) 286 jsonOutput = jsonUnmarshallOrFail(t, strings.Join(args, " "), output) 287 g.Expect(jsonOutput).To(gomega.Not(gomega.BeEmpty())) 288 289 args = []string{"--namespace=dummy", 290 "pc", "endpoint", fmt.Sprintf("%s.%s", podID, ns.Name()), "-o", "json"} 291 output, _ = istioCtl.InvokeOrFail(t, args) 292 jsonOutput = jsonUnmarshallOrFail(t, strings.Join(args, " "), output) 293 g.Expect(jsonOutput).To(gomega.Not(gomega.BeEmpty())) 294 295 args = []string{"--namespace=dummy", 296 "pc", "listener", fmt.Sprintf("%s.%s", podID, ns.Name()), "-o", "json"} 297 output, _ = istioCtl.InvokeOrFail(t, args) 298 jsonOutput = jsonUnmarshallOrFail(t, strings.Join(args, " "), output) 299 g.Expect(jsonOutput).To(gomega.Not(gomega.BeEmpty())) 300 301 args = []string{"--namespace=dummy", 302 "pc", "route", fmt.Sprintf("%s.%s", podID, ns.Name()), "-o", "json"} 303 output, _ = istioCtl.InvokeOrFail(t, args) 304 jsonOutput = jsonUnmarshallOrFail(t, strings.Join(args, " "), output) 305 g.Expect(jsonOutput).To(gomega.Not(gomega.BeEmpty())) 306 307 args = []string{"--namespace=dummy", 308 "pc", "secret", fmt.Sprintf("%s.%s", podID, ns.Name()), "-o", "json"} 309 output, _ = istioCtl.InvokeOrFail(t, args) 310 jsonOutput = jsonUnmarshallOrFail(t, strings.Join(args, " "), output) 311 g.Expect(jsonOutput).To(gomega.HaveKey("dynamicActiveSecrets")) 312 }) 313} 314 315func jsonUnmarshallOrFail(t *testing.T, context, s string) interface{} { 316 t.Helper() 317 var val interface{} 318 319 // this is guarded by prettyPrint 320 if err := json.Unmarshal([]byte(s), &val); err != nil { 321 t.Fatalf("Could not unmarshal %s response %s", context, s) 322 } 323 return val 324} 325