github.com/looshlee/beatles@v0.0.0-20220727174639-742810ab631c/test/k8sT/istio.go (about) 1 // Copyright 2018-2019 Authors of Cilium 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 15 package k8sTest 16 17 import ( 18 "context" 19 "fmt" 20 "time" 21 22 . "github.com/cilium/cilium/test/ginkgo-ext" 23 "github.com/cilium/cilium/test/helpers" 24 25 . "github.com/onsi/gomega" 26 ) 27 28 // This tests the Istio 1.4.6 integration, following the configuration 29 // instructions specified in the Istio Getting Started Guide in 30 // Documentation/gettingstarted/istio.rst. 31 // Changes to the Getting Started Guide may require re-generating or copying 32 // the following manifests: 33 // - istio-crds.yaml 34 // - istio-cilium.yaml 35 // - bookinfo-v1-istio.yaml 36 // - bookinfo-v2-istio.yaml 37 // Cf. the comments below for each manifest. 38 var _ = Describe("K8sIstioTest", func() { 39 40 var ( 41 // istioSystemNamespace is the default namespace into which Istio is 42 // installed. 43 istioSystemNamespace = "istio-system" 44 45 // istioCRDYAMLPath is the file generated from istio-init during a 46 // step in Documentation/gettingstarted/istio.rst to setup 47 // Istio 1.4.6. In the GSG the file is directly piped to kubectl. 48 istioCRDYAMLPath = helpers.ManifestGet("istio-crds.yaml") 49 50 // istioYAMLPath is the istio-cilium.yaml file generated following the 51 // instructions in Documentation/gettingstarted/istio.rst to setup 52 // Istio 1.4.6. mTLS is enabled. 53 istioYAMLPath = helpers.ManifestGet("istio-cilium.yaml") 54 55 // istioServiceNames is the subset of Istio services in the Istio 56 // namespace that are accessed from sidecar proxies. 57 istioServiceNames = []string{ 58 // All the services created by Istio are listed here, but only 59 // those that we care about are uncommented. 60 // "istio-citadel", 61 // "istio-galley", 62 "istio-ingressgateway", 63 "istio-pilot", 64 // "istio-policy", 65 // "istio-telemetry", 66 // "prometheus", 67 } 68 69 // wgetCommand is the command used in this test because the Istio apps 70 // do not provide curl. 71 wgetCommand = fmt.Sprintf("wget --tries=2 --connect-timeout %d", helpers.CurlConnectTimeout) 72 73 kubectl *helpers.Kubectl 74 microscopeCancel = func() error { return nil } 75 uptimeCancel context.CancelFunc 76 77 teardownTimeout = 10 * time.Minute 78 ) 79 80 BeforeAll(func() { 81 k8sVersion := helpers.GetCurrentK8SEnv() 82 switch k8sVersion { 83 case "1.7", "1.8", "1.9", "1.10", "1.11", "1.12", "1.13": 84 Skip(fmt.Sprintf("Istio 1.4.6 doesn't support K8S %s", k8sVersion)) 85 } 86 87 kubectl = helpers.CreateKubectl(helpers.K8s1VMName(), logger) 88 DeployCiliumAndDNS(kubectl) 89 90 By("Creating the istio-system namespace") 91 res := kubectl.NamespaceCreate(istioSystemNamespace) 92 res.ExpectSuccess("unable to create namespace %q", istioSystemNamespace) 93 94 By("Creating the Istio CRDs") 95 96 res = kubectl.ApplyDefault(istioCRDYAMLPath) 97 res.ExpectSuccess("unable to create Istio CRDs") 98 99 By("Waiting for Istio CRDs to be ready") 100 err := kubectl.WaitForCRDCount("istio.io|certmanager.k8s.io", 23, helpers.HelperTimeout) 101 Expect(err).To(BeNil(), 102 "Istio CRDs are not ready after timeout") 103 104 By("Creating the Istio system PODs") 105 106 res = kubectl.ApplyDefault(istioYAMLPath) 107 res.ExpectSuccess("unable to create Istio resources") 108 }) 109 110 AfterAll(func() { 111 By("Deleting the Istio resources") 112 _ = kubectl.Delete(istioYAMLPath) 113 114 By("Deleting the Istio CRDs") 115 _ = kubectl.Delete(istioCRDYAMLPath) 116 117 By("Waiting all terminating PODs to disappear") 118 err := kubectl.WaitCleanAllTerminatingPods(teardownTimeout) 119 ExpectWithOffset(1, err).To(BeNil(), "terminating Istio PODs are not deleted after timeout") 120 121 By("Deleting the istio-system namespace") 122 _ = kubectl.NamespaceDelete(istioSystemNamespace) 123 124 kubectl.CloseSSHClient() 125 }) 126 127 JustBeforeEach(func() { 128 var err error 129 err, microscopeCancel = kubectl.MicroscopeStart() 130 Expect(err).To(BeNil(), "Microscope cannot be started") 131 132 uptimeCancel, err = kubectl.BackgroundReport("uptime") 133 Expect(err).To(BeNil(), "Cannot start background report process") 134 }) 135 136 JustAfterEach(func() { 137 Expect(microscopeCancel()).To(BeNil(), "Cannot stop microscope") 138 uptimeCancel() 139 140 kubectl.ValidateNoErrorsInLogs(CurrentGinkgoTestDescription().Duration) 141 }) 142 143 AfterFailed(func() { 144 kubectl.CiliumReport(helpers.KubeSystemNamespace, 145 "cilium endpoint list", 146 "cilium bpf proxy list") 147 }) 148 149 // This is defined as a separate function to be called from the test below 150 // so that we properly capture test artifacts if any of the assertions fail 151 // (see https://github.com/cilium/cilium/pull/8508). 152 waitIstioReady := func() { 153 // Ignore one-time jobs and Prometheus. All other pods in the 154 // namespaces have an "istio" label. 155 By("Waiting for Istio pods to be ready") 156 // First wait for at least one POD to get into running state so that WaitforPods 157 // below does not succeed if there are no PODs with the "istio" label. 158 err := kubectl.WaitforNPodsRunning(istioSystemNamespace, "-l istio", 1, helpers.HelperTimeout) 159 ExpectWithOffset(1, err).To(BeNil(), 160 "No Istio POD is Running after timeout in namespace %q", istioSystemNamespace) 161 162 // Then wait for all the Istio PODs to get Ready 163 // Note that this succeeds if there are no PODs matching the filter (-l istio -n istio-system). 164 err = kubectl.WaitforPods(istioSystemNamespace, "-l istio", helpers.HelperTimeout) 165 ExpectWithOffset(1, err).To(BeNil(), 166 "Istio pods are not ready after timeout in namespace %q", istioSystemNamespace) 167 168 for _, name := range istioServiceNames { 169 By("Waiting for Istio service %q to be ready", name) 170 err = kubectl.WaitForServiceEndpoints( 171 istioSystemNamespace, "", name, helpers.HelperTimeout) 172 ExpectWithOffset(1, err).Should(BeNil(), "Service %q is not ready after timeout", name) 173 } 174 175 for _, name := range istioServiceNames { 176 By("Waiting for DNS to resolve Istio service %q", name) 177 err = kubectl.WaitForKubeDNSEntry(name, istioSystemNamespace) 178 ExpectWithOffset(1, err).To(BeNil(), "DNS entry is not ready after timeout") 179 } 180 } 181 182 // This is a subset of Services's "Bookinfo Demo" test suite, with the pods 183 // injected with Istio sidecar proxies and Istio mTLS enabled. 184 Context("Istio Bookinfo Demo", func() { 185 186 var ( 187 resourceYAMLPaths []string 188 policyPaths []string 189 ) 190 191 AfterEach(func() { 192 for _, resourcePath := range resourceYAMLPaths { 193 By("Deleting resource in file %q", resourcePath) 194 // Explicitly do not check result to avoid having assertions in AfterEach. 195 _ = kubectl.Delete(resourcePath) 196 } 197 198 for _, policyPath := range policyPaths { 199 By("Deleting policy in file %q", policyPath) 200 // Explicitly do not check result to avoid having assertions in AfterEach. 201 _ = kubectl.Delete(policyPath) 202 } 203 }) 204 205 // shouldConnect checks that srcPod can connect to dstURI. 206 shouldConnect := func(srcPod, dstURI string) bool { 207 By("Checking that %q can connect to %q", srcPod, dstURI) 208 res := kubectl.ExecPodCmd( 209 helpers.DefaultNamespace, srcPod, fmt.Sprintf("%s %s", wgetCommand, dstURI)) 210 if !res.WasSuccessful() { 211 GinkgoPrint("Unable to connect from %q to %q: %s", srcPod, dstURI, res.OutputPrettyPrint()) 212 return false 213 } 214 return true 215 } 216 217 // shouldNotConnect checks that srcPod cannot connect to dstURI. 218 shouldNotConnect := func(srcPod, dstURI string) bool { 219 By("Checking that %q cannot connect to %q", srcPod, dstURI) 220 res := kubectl.ExecPodCmd( 221 helpers.DefaultNamespace, srcPod, fmt.Sprintf("%s %s", wgetCommand, dstURI)) 222 if res.WasSuccessful() { 223 GinkgoPrint("Was able to connect from %q to %q, but expected no connection: %s", srcPod, dstURI, res.OutputPrettyPrint()) 224 return false 225 } 226 return true 227 } 228 229 // formatLabelArgument formats the provided key-value pairs as labels for use in 230 // querying Kubernetes. 231 formatLabelArgument := func(firstKey, firstValue string, nextLabels ...string) string { 232 baseString := fmt.Sprintf("-l %s=%s", firstKey, firstValue) 233 if nextLabels == nil { 234 return baseString 235 } else if len(nextLabels)%2 != 0 { 236 Fail("must provide even number of arguments for label key-value pairings") 237 } else { 238 for i := 0; i < len(nextLabels); i += 2 { 239 baseString = fmt.Sprintf("%s,%s=%s", baseString, nextLabels[i], nextLabels[i+1]) 240 } 241 } 242 return baseString 243 } 244 245 // formatAPI is a helper function which formats a URI to access. 246 formatAPI := func(service, port, resource string) string { 247 target := fmt.Sprintf( 248 "%s.%s.svc.cluster.local:%s", 249 service, helpers.DefaultNamespace, port) 250 if resource != "" { 251 return fmt.Sprintf("%s/%s", target, resource) 252 } 253 return target 254 } 255 256 It("Tests bookinfo inter-service connectivity", func() { 257 var err error 258 version := "version" 259 v1 := "v1" 260 261 productPage := "productpage" 262 reviews := "reviews" 263 ratings := "ratings" 264 details := "details" 265 dnsChecks := []string{productPage, reviews, ratings, details} 266 app := "app" 267 health := "health" 268 ratingsPath := "ratings/0" 269 apiPort := "9080" 270 podNameFilter := "{.items[*].metadata.name}" 271 272 // Those YAML files are the bookinfo-v1.yaml and bookinfo-v2.yaml 273 // manifests injected with Istio sidecars using those commands: 274 // cd test/k8sT/manifests/ 275 // istioctl kube-inject -f bookinfo-v1.yaml > bookinfo-v1-istio.yaml 276 // istioctl kube-inject -f bookinfo-v2.yaml > bookinfo-v2-istio.yaml 277 bookinfoV1YAML := helpers.ManifestGet("bookinfo-v1-istio.yaml") 278 bookinfoV2YAML := helpers.ManifestGet("bookinfo-v2-istio.yaml") 279 l7PolicyPath := helpers.ManifestGet("cnp-specs.yaml") 280 281 waitIstioReady() 282 283 // Create the L7 policy before creating the pods, in order to test 284 // that the sidecar proxy mode doesn't deadlock on endpoint 285 // creation in this case. 286 policyPaths = []string{l7PolicyPath} 287 for _, policyPath := range policyPaths { 288 By("Creating policy in file %q", policyPath) 289 _, err := kubectl.CiliumPolicyAction(helpers.DefaultNamespace, policyPath, helpers.KubectlApply, helpers.HelperTimeout) 290 Expect(err).Should(BeNil(), "Unable to create policy %q", policyPath) 291 } 292 293 resourceYAMLPaths = []string{bookinfoV2YAML, bookinfoV1YAML} 294 for _, resourcePath := range resourceYAMLPaths { 295 By("Creating resources in file %q", resourcePath) 296 res := kubectl.Create(resourcePath) 297 res.ExpectSuccess("Unable to create resource %q", resourcePath) 298 } 299 300 // Wait for pods and endpoints to be ready before creating the 301 // next resources to reduce the load on the next pod creations, 302 // in order to reduce the probability of regeneration timeout. 303 By("Waiting for Bookinfo pods to be ready") 304 err = kubectl.WaitforPods(helpers.DefaultNamespace, "-l zgroup=bookinfo", helpers.HelperTimeout) 305 Expect(err).Should(BeNil(), "Pods are not ready after timeout") 306 307 By("Waiting for Bookinfo endpoints to be ready") 308 err = kubectl.CiliumEndpointWaitReady() 309 Expect(err).Should(BeNil(), "Endpoints are not ready after timeout") 310 311 for _, service := range []string{details, ratings, reviews, productPage} { 312 By("Waiting for Bookinfo service %q to be ready", service) 313 err = kubectl.WaitForServiceEndpoints( 314 helpers.DefaultNamespace, "", service, 315 helpers.HelperTimeout) 316 Expect(err).Should(BeNil(), "Service %q is not ready after timeout", service) 317 } 318 319 for _, name := range dnsChecks { 320 By("Waiting for DNS to resolve Bookinfo service %q", name) 321 err = kubectl.WaitForKubeDNSEntry(name, helpers.DefaultNamespace) 322 Expect(err).To(BeNil(), "DNS entry is not ready after timeout") 323 } 324 325 By("Testing L7 filtering") 326 reviewsPodV1, err := kubectl.GetPods(helpers.DefaultNamespace, formatLabelArgument(app, reviews, version, v1)).Filter(podNameFilter) 327 Expect(err).Should(BeNil(), "Cannot get reviewsV1 pods") 328 productpagePodV1, err := kubectl.GetPods(helpers.DefaultNamespace, formatLabelArgument(app, productPage, version, v1)).Filter(podNameFilter) 329 Expect(err).Should(BeNil(), "Cannot get productpageV1 pods") 330 331 // Connectivity checks often need to be repeated because Pilot 332 // is eventually consistent, i.e. it may take some time for a 333 // sidecar proxy to get updated with the configuration for another 334 // new endpoint and it rejects egress traffic with 503s in the 335 // meantime. 336 err = helpers.WithTimeout(func() bool { 337 allGood := true 338 339 allGood = shouldConnect(reviewsPodV1.String(), formatAPI(ratings, apiPort, health)) && allGood 340 allGood = shouldNotConnect(reviewsPodV1.String(), formatAPI(ratings, apiPort, ratingsPath)) && allGood 341 342 allGood = shouldConnect(productpagePodV1.String(), formatAPI(details, apiPort, health)) && allGood 343 344 allGood = shouldNotConnect(productpagePodV1.String(), formatAPI(ratings, apiPort, health)) && allGood 345 allGood = shouldNotConnect(productpagePodV1.String(), formatAPI(ratings, apiPort, ratingsPath)) && allGood 346 347 return allGood 348 }, "Istio sidecar proxies are not configured", &helpers.TimeoutConfig{Timeout: helpers.HelperTimeout}) 349 Expect(err).Should(BeNil(), "Cannot configure Istio sidecar proxies") 350 }) 351 }) 352 })