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  })