github.com/cilium/cilium@v1.16.2/test/k8s/lrp.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package k8sTest
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"strings"
    10  	"sync"
    11  	"time"
    12  
    13  	. "github.com/onsi/gomega"
    14  
    15  	. "github.com/cilium/cilium/test/ginkgo-ext"
    16  	"github.com/cilium/cilium/test/helpers"
    17  )
    18  
    19  // The 5.4 CI job is intended to catch BPF complexity regressions and as such
    20  // doesn't need to execute this test suite.
    21  var _ = SkipDescribeIf(func() bool { return helpers.RunsOn54Kernel() && helpers.DoesNotRunOnAKS() }, "K8sDatapathLRPTests", func() {
    22  	var (
    23  		kubectl        *helpers.Kubectl
    24  		ciliumFilename string
    25  	)
    26  
    27  	BeforeAll(func() {
    28  		kubectl = helpers.CreateKubectl(helpers.K8s1VMName(), logger)
    29  		ciliumFilename = helpers.TimestampFilename("cilium.yaml")
    30  	})
    31  
    32  	JustAfterEach(func() {
    33  		kubectl.ValidateNoErrorsInLogs(CurrentGinkgoTestDescription().Duration)
    34  	})
    35  
    36  	AfterAll(func() {
    37  		UninstallCiliumFromManifest(kubectl, ciliumFilename)
    38  		kubectl.CloseSSHClient()
    39  	})
    40  
    41  	AfterFailed(func() {
    42  		kubectl.CiliumReport("cilium-dbg lrp list", "cilium-dbg service list")
    43  	})
    44  
    45  	SkipContextIf(func() bool { return helpers.RunsOnAKS() }, "Checks local redirect policy", func() {
    46  		const (
    47  			lrpServiceName = "lrp-demo-service"
    48  			be1Name        = "k8s1-backend"
    49  			be2Name        = "k8s2-backend"
    50  			feFilter       = "role=frontend"
    51  			beFilter       = "role=backend"
    52  			beFilter2      = "role=lrpAddrBackend"
    53  			lrpAddrIP      = "169.254.169.254"
    54  		)
    55  
    56  		var (
    57  			deploymentYAML string
    58  			lrpSvcYAML     string
    59  			svcIP          string
    60  			curl4TCP       string
    61  			curl4UDP       string
    62  			curl4in6TCP    string
    63  			curl4in6UDP    string
    64  			curlTCPAddr    string
    65  			curlUDPAddr    string
    66  			be3Name        string
    67  			be4Name        string
    68  		)
    69  
    70  		BeforeAll(func() {
    71  			DeployCiliumOptionsAndDNS(kubectl, ciliumFilename, map[string]string{
    72  				"localRedirectPolicy": "true",
    73  			})
    74  			deploymentYAML = helpers.ManifestGet(kubectl.BasePath(), "lrp-test.yaml")
    75  			lrpSvcYAML = helpers.ManifestGet(kubectl.BasePath(), "lrp-svc.yaml")
    76  			res := kubectl.ApplyDefault(deploymentYAML)
    77  			res.ExpectSuccess("Unable to apply %s", deploymentYAML)
    78  			for _, pod := range []string{feFilter, beFilter, beFilter2} {
    79  				err := kubectl.WaitforPods(helpers.DefaultNamespace, fmt.Sprintf("-l %s", pod), helpers.HelperTimeout)
    80  				Expect(err).Should(BeNil())
    81  			}
    82  			clusterIP, _, err := kubectl.GetServiceHostPort(helpers.DefaultNamespace, lrpServiceName)
    83  			svcIP = clusterIP
    84  			Expect(err).To(BeNil(), "Cannot get svc IP")
    85  
    86  			http4SVCURL := getHTTPLink(svcIP, 80)
    87  			tftp4SVCURL := getTFTPLink(svcIP, 69)
    88  			http4in6SVCURL := getHTTPLink("::ffff:"+svcIP, 80)
    89  			tftp4in6SVCURL := getTFTPLink("::ffff:"+svcIP, 69)
    90  
    91  			curl4TCP = helpers.CurlFailNoStats(http4SVCURL)
    92  			curl4UDP = helpers.CurlFailNoStats(tftp4SVCURL)
    93  			curl4in6TCP = helpers.CurlFailNoStats(http4in6SVCURL)
    94  			curl4in6UDP = helpers.CurlFailNoStats(tftp4in6SVCURL)
    95  			curlTCPAddr = helpers.CurlFailNoStats(getHTTPLink(lrpAddrIP, 80))
    96  			curlUDPAddr = helpers.CurlFailNoStats(getTFTPLink(lrpAddrIP, 69))
    97  
    98  			// Hostnames for host networked pods
    99  			be3Name, _ = kubectl.GetNodeInfo(helpers.K8s1)
   100  			be4Name, _ = kubectl.GetNodeInfo(helpers.K8s2)
   101  		})
   102  
   103  		AfterAll(func() {
   104  			_ = kubectl.Delete(deploymentYAML)
   105  			ExpectAllPodsTerminated(kubectl)
   106  		})
   107  
   108  		It("LRP connectivity", func() {
   109  			type lrpTestCase struct {
   110  				selector string
   111  				cmd      string
   112  				want     string
   113  				notWant  string
   114  			}
   115  
   116  			// Basic sanity check
   117  			ciliumPods, err := kubectl.GetCiliumPods()
   118  			Expect(err).To(BeNil(), "Cannot get cilium pods")
   119  			for _, pod := range ciliumPods {
   120  				service := kubectl.CiliumExecMustSucceed(context.TODO(), pod, fmt.Sprintf("cilium-dbg service list | grep \" %s:\"", svcIP), "Cannot retrieve services on cilium pod")
   121  				service.ExpectContains("LocalRedirect", "LocalRedirect is not present in the cilium service list for [%s]", svcIP)
   122  				service2 := kubectl.CiliumExecMustSucceed(context.TODO(), pod, fmt.Sprintf("cilium-dbg service list | grep \" %s:\"", lrpAddrIP), "Cannot retrieve services on cilium pod")
   123  				service2.ExpectContains("LocalRedirect", "LocalRedirect is not present in the cilium service list for [%s]", lrpAddrIP)
   124  			}
   125  
   126  			By("Checking traffic goes to local backend")
   127  			testCases := []lrpTestCase{
   128  				{
   129  					selector: "id=app1",
   130  					cmd:      curl4TCP,
   131  					// Expects to see local backend name in returned Hostname field
   132  					want: be1Name,
   133  					// Expects never to see remote backend name in returned Hostname field
   134  					notWant: be2Name,
   135  				},
   136  				{
   137  					selector: "id=app2",
   138  					cmd:      curl4TCP,
   139  					want:     be2Name,
   140  					notWant:  be1Name,
   141  				},
   142  				{
   143  					selector: "id=app1",
   144  					cmd:      curl4UDP,
   145  					want:     be1Name,
   146  					notWant:  be2Name,
   147  				},
   148  				{
   149  					selector: "id=app2",
   150  					cmd:      curl4UDP,
   151  					want:     be2Name,
   152  					notWant:  be1Name,
   153  				},
   154  				{
   155  					selector: "id=app1",
   156  					cmd:      curl4in6TCP,
   157  					want:     be1Name,
   158  					notWant:  be2Name,
   159  				},
   160  				{
   161  					selector: "id=app2",
   162  					cmd:      curl4in6TCP,
   163  					want:     be2Name,
   164  					notWant:  be1Name,
   165  				},
   166  				{
   167  					selector: "id=app1",
   168  					cmd:      curl4in6UDP,
   169  					want:     be1Name,
   170  					notWant:  be2Name,
   171  				},
   172  				{
   173  					selector: "id=app2",
   174  					cmd:      curl4in6UDP,
   175  					want:     be2Name,
   176  					notWant:  be1Name,
   177  				},
   178  				// Address matcher test cases.
   179  				{
   180  					selector: "id=app1",
   181  					cmd:      curlTCPAddr,
   182  					want:     be3Name,
   183  					notWant:  be4Name,
   184  				},
   185  				{
   186  					selector: "id=app2",
   187  					cmd:      curlUDPAddr,
   188  					want:     be4Name,
   189  					notWant:  be3Name,
   190  				},
   191  			}
   192  
   193  			var wg sync.WaitGroup
   194  			for _, testCase := range testCases {
   195  				wg.Add(1)
   196  				go func(tc lrpTestCase) {
   197  					defer GinkgoRecover()
   198  					defer wg.Done()
   199  					Consistently(func() bool {
   200  						pods, err := kubectl.GetPodNames(helpers.DefaultNamespace, tc.selector)
   201  						Expect(err).Should(BeNil(), "cannot retrieve pod names by filter %q", tc.selector)
   202  						Expect(len(pods)).Should(BeNumerically(">", 0), "no pod exists by filter %q", tc.selector)
   203  						ret := true
   204  						for _, pod := range pods {
   205  							res := kubectl.ExecPodCmd(helpers.DefaultNamespace, pod, tc.cmd)
   206  							Expect(err).To(BeNil(), "%s failed in %s pod", tc.cmd, pod)
   207  							ret = ret && strings.Contains(res.Stdout(), tc.want) && !strings.Contains(res.Stdout(), tc.notWant)
   208  						}
   209  						return ret
   210  					}, 30*time.Second, 1*time.Second).Should(BeTrue(), "assertion fails for test case: %v", tc)
   211  				}(testCase)
   212  			}
   213  			wg.Wait()
   214  		})
   215  
   216  		It("LRP restores service when removed", func() {
   217  			type lrpTestCase struct {
   218  				selector string
   219  				cmd      string
   220  			}
   221  
   222  			_ = kubectl.Delete(lrpSvcYAML)
   223  			// Basic sanity check
   224  			ciliumPods, err := kubectl.GetCiliumPods()
   225  			Expect(err).To(BeNil(), "Cannot get cilium pods")
   226  			for _, pod := range ciliumPods {
   227  				service := kubectl.CiliumExecMustSucceed(context.TODO(), pod, fmt.Sprintf("cilium-dbg service list | grep \" %s:\"", svcIP), "Cannot retrieve services on cilium pod")
   228  				service.ExpectContains("ClusterIP", "Original service is not present in the cilium service list")
   229  			}
   230  
   231  			By("Checking traffic goes to both backends")
   232  			testCases := []lrpTestCase{
   233  				{
   234  					selector: "id=app1",
   235  					cmd:      curl4TCP,
   236  				},
   237  				{
   238  					selector: "id=app2",
   239  					cmd:      curl4TCP,
   240  				},
   241  				{
   242  					selector: "id=app1",
   243  					cmd:      curl4UDP,
   244  				},
   245  				{
   246  					selector: "id=app2",
   247  					cmd:      curl4UDP,
   248  				},
   249  			}
   250  
   251  			var wg sync.WaitGroup
   252  			for _, tc := range testCases {
   253  				pods, err := kubectl.GetPodNames(helpers.DefaultNamespace, tc.selector)
   254  				Expect(err).Should(BeNil(), "cannot retrieve pod names by filter %q", tc.selector)
   255  				Expect(len(pods)).Should(BeNumerically(">", 0), "no pod exists by filter %q", tc.selector)
   256  				for _, pod := range pods {
   257  					wg.Add(1)
   258  					go func(tc lrpTestCase, pod string) {
   259  						defer GinkgoRecover()
   260  						defer wg.Done()
   261  						want := []string{be1Name, be2Name}
   262  						be1Found := false
   263  						be2Found := false
   264  						Eventually(func() bool {
   265  							res := kubectl.ExecPodCmd(helpers.DefaultNamespace, pod, tc.cmd)
   266  							ExpectWithOffset(1, res).Should(helpers.CMDSuccess(),
   267  								"%s failed in %s pod", tc.cmd, pod)
   268  							be1Found = be1Found || strings.Contains(res.Stdout(), want[0])
   269  							be2Found = be2Found || strings.Contains(res.Stdout(), want[1])
   270  							return be1Found && be2Found
   271  						}, 30*time.Second, 1*time.Second).Should(BeTrue(), "assertion fails for test case: %v", tc)
   272  					}(tc, pod)
   273  				}
   274  			}
   275  			wg.Wait()
   276  		})
   277  	})
   278  
   279  })