github.phpd.cn/cilium/cilium@v1.6.12/test/runtime/lb.go (about) 1 // Copyright 2017-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 RuntimeTest 16 17 import ( 18 "fmt" 19 "os" 20 21 . "github.com/cilium/cilium/test/ginkgo-ext" 22 "github.com/cilium/cilium/test/helpers" 23 "github.com/cilium/cilium/test/helpers/constants" 24 25 . "github.com/onsi/gomega" 26 ) 27 28 var _ = Describe("RuntimeLB", func() { 29 var ( 30 vm *helpers.SSHMeta 31 monitorStop = func() error { return nil } 32 ) 33 34 BeforeAll(func() { 35 vm = helpers.InitRuntimeHelper(helpers.Runtime, logger) 36 ExpectCiliumReady(vm) 37 }) 38 39 AfterAll(func() { 40 vm.ServiceDelAll().ExpectSuccess() 41 vm.CloseSSHClient() 42 }) 43 44 JustBeforeEach(func() { 45 monitorStop = vm.MonitorStart() 46 }) 47 48 JustAfterEach(func() { 49 vm.ValidateNoErrorsInLogs(CurrentGinkgoTestDescription().Duration) 50 Expect(monitorStop()).To(BeNil(), "cannot stop monitor command") 51 }) 52 53 AfterFailed(func() { 54 vm.ReportFailed( 55 "cilium service list", 56 "cilium bpf lb list", 57 "cilium policy get") 58 }) 59 60 AfterEach(func() { 61 cleanupLBDevice(vm) 62 }, 500) 63 64 images := map[string]string{ 65 helpers.Httpd1: constants.HttpdImage, 66 helpers.Httpd2: constants.HttpdImage, 67 helpers.Client: constants.NetperfImage, 68 } 69 70 createContainers := func() { 71 By("Creating containers for traffic test") 72 73 for k, v := range images { 74 vm.ContainerCreate(k, v, helpers.CiliumDockerNetwork, fmt.Sprintf("-l id.%s", k)) 75 } 76 epStatus := vm.WaitEndpointsReady() 77 Expect(epStatus).Should(BeTrue()) 78 } 79 80 deleteContainers := func() { 81 for k := range images { 82 vm.ContainerRm(k) 83 } 84 } 85 86 BeforeEach(func() { 87 vm.ServiceDelAll().ExpectSuccess() 88 }, 500) 89 90 It("validates basic service management functionality", func() { 91 result := vm.ServiceAdd(1, "[::]:80", []string{"[::1]:90", "[::2]:91"}) 92 result.ExpectSuccess("unexpected failure to add service") 93 result = vm.ServiceGet(1) 94 result.ExpectSuccess("unexpected failure to retrieve service") 95 frontendAddress, err := vm.ServiceGetFrontendAddress(1) 96 Expect(err).Should(BeNil()) 97 Expect(frontendAddress).To(ContainSubstring("[::]:80"), 98 "failed to retrieve frontend address: %q", result.Output()) 99 100 //TODO: This need to be with Wait,Timeout 101 helpers.Sleep(5) 102 103 By("Checking that BPF maps are updated based on service configuration") 104 105 result = vm.ExecCilium("bpf lb list") 106 result.ExpectSuccess("bpf lb map cannot be retrieved correctly") 107 Expect(result.Output()).To(ContainSubstring("[::1]:90"), fmt.Sprintf( 108 "service backends not added to BPF map: %q", result.Output())) 109 110 By("Adding services that should not be allowed") 111 112 result = vm.ServiceAdd(0, "[::]:10000", []string{"[::1]:90", "[::2]:91"}) 113 result.ExpectFail("unexpected success adding service with id 0") 114 result = vm.ServiceAdd(-1, "[::]:10000", []string{"[::1]:90", "[::2]:91"}) 115 result.ExpectFail("unexpected success adding service with id -1") 116 result = vm.ServiceAdd(1, "[::]:10000", []string{"[::1]:90", "[::2]:91"}) 117 result.ExpectFail("unexpected success adding service with duplicate id 1") 118 result = vm.ServiceAdd(2, "2.2.2.2:0", []string{"3.3.3.3:90", "4.4.4.4:91"}) 119 result.ExpectFail("unexpected success adding service with L3=>L4 redirect") 120 121 By("Adding duplicate service FE address (IPv6)") 122 123 //Trying to create a new service with id 10, that conflicts with the FE addr on id=1 124 result = vm.ServiceAdd(10, "[::]:80", []string{"[::1]:90", "[::2]:91"}) 125 result.ExpectFail("unexpected success adding service with duplicate frontend address (id 10)") 126 result = vm.ServiceGet(10) 127 result.ExpectFail("unexpected success fetching service with id 10, service should not be present") 128 129 By("Deleting IPv6 service") 130 result = vm.ServiceDel(1) 131 result.ExpectSuccess("unexpected failure deleting service with id 1") 132 133 By("Creating a valid IPv4 service with id 1") 134 135 result = vm.ServiceAdd(1, "127.0.0.1:80", []string{"127.0.0.1:90", "127.0.0.1:91"}) 136 result.ExpectSuccess("unexpected failure adding valid service") 137 result = vm.ServiceGet(1) 138 result.ExpectSuccess("unexpected failure to retrieve service") 139 140 By("Adding duplicate service FE address (IPv4)") 141 142 result = vm.ServiceAdd(20, "127.0.0.1:80", []string{"127.0.0.1:90", "127.0.0.1:91"}) 143 result.ExpectFail("unexpected success adding service with duplicate frontend address (id 20)") 144 result = vm.ServiceGet(20) 145 result.ExpectFail("unexpected success fetching service with id 20, service should not be present") 146 }, 500) 147 148 Context("With Containers", func() { 149 150 BeforeAll(func() { 151 createContainers() 152 }) 153 154 AfterAll(func() { 155 deleteContainers() 156 }) 157 158 It("validates that services work for L3 (IP) loadbalancing", func() { 159 err := createLBDevice(vm) 160 if err != nil { 161 log.Errorf("error creating interface: %s", err) 162 } 163 Expect(err).Should(BeNil()) 164 165 httpd1, err := vm.ContainerInspectNet(helpers.Httpd1) 166 Expect(err).Should(BeNil()) 167 httpd2, err := vm.ContainerInspectNet(helpers.Httpd2) 168 Expect(err).Should(BeNil()) 169 170 By("Creating services") 171 172 services := map[string][]string{ 173 "2.2.2.2:0": { 174 fmt.Sprintf("%s:0", httpd1[helpers.IPv4]), 175 fmt.Sprintf("%s:0", httpd2[helpers.IPv4]), 176 }, 177 "[f00d::1:1]:0": { 178 fmt.Sprintf("[%s]:0", httpd1[helpers.IPv6]), 179 fmt.Sprintf("[%s]:0", httpd2[helpers.IPv6]), 180 }, 181 "3.3.3.3:0": { 182 fmt.Sprintf("%s:0", "10.0.2.15"), 183 }, 184 "[f00d::1:2]:0": { 185 fmt.Sprintf("[%s]:0", "fd02:1:1:1:1:1:1:1"), 186 }, 187 } 188 svc := 1 189 for fe, be := range services { 190 status := vm.ServiceAdd(svc, fe, be) 191 status.ExpectSuccess(fmt.Sprintf("failed to create service %s=>%v", fe, be)) 192 svc++ 193 } 194 195 By("Pinging host => bpf_lb => container") 196 197 status := vm.Exec(helpers.Ping("2.2.2.2")) 198 status.ExpectSuccess("failed to ping service IP from host") 199 // FIXME GH-2889: createLBDevice() doesn't configure host IPv6 200 //status = vm.Exec(helpers.Ping6("f00d::1:1")) 201 //status.ExpectSuccess("failed to ping service IP from host") 202 203 By("Pinging container => bpf_lb => container") 204 205 status = vm.ContainerExec(helpers.Client, helpers.Ping("2.2.2.2")) 206 status.ExpectSuccess("failed to ping service IP 2.2.2.2") 207 status = vm.ContainerExec(helpers.Client, helpers.Ping6("f00d::1:1")) 208 status.ExpectSuccess("failed to ping service IP f00d::1:1") 209 210 By("Pinging container => bpf_lb => host") 211 212 status = vm.ContainerExec(helpers.Client, helpers.Ping("3.3.3.3")) 213 status.ExpectSuccess("failed to ping service IP 3.3.3.3") 214 status = vm.ContainerExec(helpers.Client, helpers.Ping("f00d::1:2")) 215 status.ExpectSuccess("failed to ping service IP f00d::1:2") 216 217 By("Configuring services to point to own IP via service") 218 219 vm.ServiceDelAll().ExpectSuccess() 220 loopbackServices := map[string]string{ 221 "2.2.2.2:0": fmt.Sprintf("%s:0", httpd1[helpers.IPv4]), 222 "[f00d::1:1]:0": fmt.Sprintf("[%s]:0", httpd1[helpers.IPv6]), 223 } 224 svc = 1 225 for fe, be := range loopbackServices { 226 status := vm.ServiceAdd(svc, fe, []string{be}) 227 status.ExpectSuccess(fmt.Sprintf("failed to create service %s=>%v", fe, be)) 228 svc++ 229 } 230 231 By("Pinging from server1 to its own service IP") 232 233 status = vm.ContainerExec(helpers.Httpd1, helpers.Ping("2.2.2.2")) 234 status.ExpectSuccess("failed to ping service IP 2.2.2.2") 235 status = vm.ContainerExec(helpers.Httpd1, helpers.Ping6("f00d::1:1")) 236 status.ExpectSuccess("failed to ping service IP f00d::1:1") 237 }, 500) 238 239 It("validates that services work for L4 (IP+Port) loadbalancing", func() { 240 err := createLBDevice(vm) 241 if err != nil { 242 log.Errorf("error creating interface: %s", err) 243 } 244 Expect(err).Should(BeNil()) 245 246 httpd1, err := vm.ContainerInspectNet(helpers.Httpd1) 247 Expect(err).Should(BeNil()) 248 httpd2, err := vm.ContainerInspectNet(helpers.Httpd2) 249 Expect(err).Should(BeNil()) 250 251 By("Creating services") 252 253 services := map[string][]string{ 254 "2.2.2.2:80": { 255 fmt.Sprintf("%s:80", httpd1[helpers.IPv4]), 256 fmt.Sprintf("%s:80", httpd2[helpers.IPv4]), 257 }, 258 "[f00d::1:1]:80": { 259 fmt.Sprintf("[%s]:80", httpd1[helpers.IPv6]), 260 fmt.Sprintf("[%s]:80", httpd2[helpers.IPv6]), 261 }, 262 } 263 svc := 1 264 for fe, be := range services { 265 status := vm.ServiceAdd(svc, fe, be) 266 status.ExpectSuccess("failed to create service %s=>%v", fe, be) 267 svc++ 268 } 269 270 By("Making HTTP requests from container => bpf_lb => container") 271 272 for ip := range services { 273 url := fmt.Sprintf("http://%s/public", ip) 274 status := vm.ContainerExec(helpers.Client, helpers.CurlFail(url)) 275 status.ExpectSuccess(fmt.Sprintf("failed to fetch via URL %s", url)) 276 } 277 }, 500) 278 279 It("validates service recovery on restart", func() { 280 service := "2.2.2.2:80" 281 svcID := 1 282 testCmd := helpers.CurlFail(fmt.Sprintf("http://%s/public", service)) 283 284 httpd1, err := vm.ContainerInspectNet("httpd1") 285 Expect(err).Should(BeNil()) 286 httpd2, err := vm.ContainerInspectNet("httpd2") 287 Expect(err).Should(BeNil()) 288 289 status := vm.ServiceAdd(svcID, service, []string{ 290 fmt.Sprintf("%s:80", httpd1["IPv4"]), 291 fmt.Sprintf("%s:80", httpd2["IPv4"])}) 292 status.ExpectSuccess("failed to create service %s=>{httpd1,httpd2}", service) 293 294 By("Making HTTP request via the service before restart") 295 296 status = vm.ContainerExec(helpers.Client, testCmd) 297 status.ExpectSuccess("Failed to fetch URL via service") 298 oldSvc := vm.ServiceList() 299 oldSvc.ExpectSuccess("Cannot retrieve service list") 300 301 By("Fetching service state before restart") 302 303 oldSvcIds, err := vm.ServiceGetIds() 304 Expect(err).Should(BeNil()) 305 oldBpfLB, err := vm.BpfLBList(false) 306 Expect(err).Should(BeNil()) 307 308 err = vm.RestartCilium() 309 Expect(err).Should(BeNil(), "restarting Cilium failed") 310 311 By("Checking that the service was restored correctly") 312 313 svcIds, err := vm.ServiceGetIds() 314 Expect(err).Should(BeNil()) 315 Expect(len(svcIds)).Should(Equal(len(oldSvcIds)), 316 "Service ids %s do not match old service ids %s", svcIds, oldSvcIds) 317 newSvc := vm.ServiceList() 318 newSvc.ExpectSuccess("Cannot retrieve service list after restart") 319 newSvc.ExpectEqual(oldSvc.Output().String(), "Service list does not match") 320 321 By("Checking that BPF LB maps match the service") 322 323 newBpfLB, err := vm.BpfLBList(false) 324 Expect(err).Should(BeNil(), "Cannot retrieve bpf lb list after restart") 325 Expect(oldBpfLB).Should(Equal(newBpfLB)) 326 svcSync, err := vm.ServiceIsSynced(svcID) 327 Expect(err).Should(BeNil(), "Service is not sync with BPF LB") 328 Expect(svcSync).Should(BeTrue()) 329 330 By("Making HTTP request via the service after restart") 331 332 status = vm.ContainerExec("client", testCmd) 333 status.ExpectSuccess("Failed to fetch URL via service") 334 }) 335 }) 336 337 Context("Services Policies", func() { 338 339 BeforeAll(func() { 340 vm.SampleContainersActions(helpers.Create, helpers.CiliumDockerNetwork) 341 }) 342 343 AfterAll(func() { 344 vm.SampleContainersActions(helpers.Delete, helpers.CiliumDockerNetwork) 345 }) 346 347 AfterEach(func() { 348 vm.PolicyDelAll().ExpectSuccess() 349 vm.ServiceDelAll().ExpectSuccess() 350 351 status := vm.ExecCilium(fmt.Sprintf("config %s=false", 352 helpers.OptionConntrackLocal)) 353 status.ExpectSuccess() 354 }) 355 356 testServicesWithPolicies := func(svcPort int) { 357 ready := vm.WaitEndpointsReady() 358 Expect(ready).To(BeTrue()) 359 360 httpd1, err := vm.ContainerInspectNet(helpers.Httpd1) 361 Expect(err).Should(BeNil()) 362 httpd2, err := vm.ContainerInspectNet(helpers.Httpd2) 363 Expect(err).Should(BeNil()) 364 365 By("Configuring services") 366 367 service1 := fmt.Sprintf("2.2.2.100:%d", svcPort) 368 service2 := fmt.Sprintf("[f00d::1:1]:%d", svcPort) 369 service3 := fmt.Sprintf("2.2.2.101:%d", svcPort) 370 services := map[string]string{ 371 service1: fmt.Sprintf("%s:80", httpd1[helpers.IPv4]), 372 service2: fmt.Sprintf("[%s]:80", httpd2[helpers.IPv6]), 373 service3: fmt.Sprintf("%s:80", httpd2[helpers.IPv4]), 374 } 375 svc := 100 376 for fe, be := range services { 377 status := vm.ServiceAdd(svc, fe, []string{be}) 378 status.ExpectSuccess(fmt.Sprintf("failed to create service %s=>%s", fe, be)) 379 svc++ 380 } 381 382 getHTTP := func(service, target string) string { 383 return helpers.CurlFail(fmt.Sprintf( 384 "http://%s/%s", service, target)) 385 } 386 387 _, err = vm.PolicyImportAndWait(vm.GetFullPath(policiesL7JSON), helpers.HelperTimeout) 388 Expect(err).Should(BeNil()) 389 390 By("Making HTTP request to service with ingress policy") 391 392 status := vm.ContainerExec(helpers.App1, getHTTP(service1, helpers.Public)) 393 status.ExpectSuccess() 394 status = vm.ContainerExec(helpers.App3, getHTTP(service1, helpers.Public)) 395 status.ExpectFail() 396 397 By("Making HTTP request via egress policy to service IP") 398 399 status = vm.ContainerExec(helpers.App2, getHTTP(service2, helpers.Public)) 400 status.ExpectSuccess() 401 status = vm.ContainerExec(helpers.App2, getHTTP(service2, helpers.Private)) 402 status.ExpectFail() 403 status = vm.ContainerExec(helpers.App2, getHTTP(service3, helpers.Public)) 404 status.ExpectSuccess() 405 406 By("Making HTTP requests to service with multiple ingress policies") 407 408 vm.PolicyDelAll() 409 _, err = vm.PolicyImportAndWait(vm.GetFullPath(multL7PoliciesJSON), helpers.HelperTimeout) 410 Expect(err).Should(BeNil()) 411 412 status = vm.ContainerExec(helpers.App1, getHTTP(service1, helpers.Public)) 413 status.ExpectSuccess() 414 status = vm.ContainerExec(helpers.App1, getHTTP(service1, helpers.Private)) 415 status.ExpectFail() 416 status = vm.ContainerExec(helpers.App3, getHTTP(service1, helpers.Public)) 417 status.ExpectFail() 418 419 By("Making HTTP requests via multiple egress policies to service IP") 420 421 status = vm.ContainerExec(helpers.App2, getHTTP(service2, helpers.Public)) 422 status.ExpectSuccess() 423 status = vm.ContainerExec(helpers.App2, getHTTP(service2, helpers.Private)) 424 status.ExpectFail() 425 status = vm.ContainerExec(helpers.App2, getHTTP(service3, helpers.Public)) 426 status.ExpectSuccess() 427 } 428 429 It("tests with conntrack enabled", func() { 430 status := vm.ExecCilium(fmt.Sprintf("config %s=true", 431 helpers.OptionConntrackLocal)) 432 status.ExpectSuccess() 433 testServicesWithPolicies(80) 434 }) 435 436 It("tests with conntrack disabled", func() { 437 status := vm.ExecCilium(fmt.Sprintf("config %s=false", 438 helpers.OptionConntrackLocal)) 439 status.ExpectSuccess() 440 testServicesWithPolicies(80) 441 }) 442 443 /* Policy is written against egress to port 80, so when an 444 * app makes requests on port 1234, the service translation 445 * should occur before applying the egress policy. 446 */ 447 It("tests with service performing L4 port mapping", func() { 448 testServicesWithPolicies(1234) 449 }) 450 }) 451 }) 452 453 // createLBDevice instantiates a device with the bpf_lb program to handle 454 // loadbalancing as though it were attached to a physical device on the system. 455 // This is implemented through a veth pair with two ends, lbtest1 and lbtest2. 456 // bpf_lb is attached to ingress at lbtest2, so when traffic is sent through 457 // lbtest1 it is forwarded through the veth pair into lbtest2 where the BPF 458 // program executes the services functionality. 459 // 460 // The following traffic is routed to lbtest1 (so it goes to the LB BPF prog): 461 // * 3.3.3.3/32 462 // * 2.2.2.2/32 463 // * f00d:1:1/128 464 // * fbfb::10:10/128 465 // 466 // Additionally, the following IPs are associated with the cilium_host device, 467 // so they may be used as backends for services and they will receive a 468 // response from the host: 469 // * 10.0.2.15 (inherited from virtualbox VM configuration) 470 // * fd02:1:1:1:1:1:1:1 (explicitly configured below) 471 func createLBDevice(node *helpers.SSHMeta) error { 472 script := `#!/bin/bash 473 function mac2array() { 474 echo "{0x${1//:/,0x}}" 475 } 476 477 ip link add lbtest1 type veth peer name lbtest2 478 ip link set lbtest1 up 479 480 # Route f00d::1:1 IPv6 packets to a fantasy router ("fbfb::10:10") behind lbtest1 481 ip -6 route add fbfb::10:10/128 dev lbtest1 482 MAC=$(ip link show lbtest1 | grep ether | awk '{print $2}') 483 ip neigh add fbfb::10:10 lladdr $MAC dev lbtest1 484 ip -6 route add f00d::1:1/128 via fbfb::10:10 485 486 # Route 2.2.2.2 IPv4 packets to a fantasy router ("3.3.3.3") behind lbtest1 487 ip route add 3.3.3.3/32 dev lbtest1 488 MAC=$(ip link show lbtest1 | grep ether | awk '{print $2}') 489 ip neigh add 3.3.3.3 lladdr $MAC dev lbtest1 490 ip route add 2.2.2.2/32 via 3.3.3.3 491 492 ip link set lbtest2 up 493 494 LIB=/var/lib/cilium/bpf 495 RUN=/var/run/cilium/state 496 NH_IFINDEX=$(cat /sys/class/net/cilium_host/ifindex) 497 NH_MAC=$(ip link show cilium_host | grep ether | awk '{print $2}') 498 NH_MAC="{.addr=$(mac2array $NH_MAC)}" 499 CLANG_OPTS="-D__NR_CPUS__=$(nproc) -DLB_L3 -DLB_REDIRECT=$NH_IFINDEX -DLB_DSTMAC=$NH_MAC -DCALLS_MAP=lbtest -O2 -target bpf -I. -I$LIB -I$LIB/include -I$RUN/globals -DDEBUG -Wno-address-of-packed-member -Wno-unknown-warning-option" 500 touch netdev_config.h 501 clang $CLANG_OPTS -c $LIB/bpf_lb.c -o tmp_lb.o 502 503 tc qdisc del dev lbtest2 clsact 2> /dev/null || true 504 tc qdisc add dev lbtest2 clsact 505 tc filter add dev lbtest2 ingress bpf da obj tmp_lb.o sec from-netdev 506 ` 507 By("Creating LB device to handle service requests") 508 scriptName := "create_veth_interface" 509 log.Infof("generating veth script: %s", scriptName) 510 err := helpers.RenderTemplateToFile(scriptName, script, os.ModePerm) 511 if err != nil { 512 return err 513 } 514 515 // filesystem is mounted at path /vagrant on VM 516 scriptPath := fmt.Sprintf("%s/%s", helpers.BasePath, scriptName) 517 518 ipAddrCmd := "sudo ip addr add fd02:1:1:1:1:1:1:1 dev cilium_host" 519 res := node.Exec(ipAddrCmd) 520 log.Infof("output of %q: %s", ipAddrCmd, res.CombineOutput()) 521 522 log.Infof("running script %s", scriptPath) 523 runScriptCmd := fmt.Sprintf("sudo %s", scriptPath) 524 res = node.Exec(runScriptCmd) 525 log.Infof("output of %q: %s", runScriptCmd, res.CombineOutput()) 526 log.Infof("removing file %q", scriptName) 527 err = os.Remove(scriptName) 528 return err 529 } 530 531 func cleanupLBDevice(node *helpers.SSHMeta) { 532 ipAddrCmd := "sudo ip addr del fd02:1:1:1:1:1:1:1/128 dev cilium_host" 533 res := node.Exec(ipAddrCmd) 534 log.Infof("output of %q: %s", ipAddrCmd, res.CombineOutput()) 535 536 ipLinkCmd := "sudo ip link del dev lbtest1" 537 res = node.Exec(ipLinkCmd) 538 log.Infof("output of %q: %s", ipLinkCmd, res.CombineOutput()) 539 }