istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/serviceregistry/kube/controller/controller_test.go (about) 1 // Copyright 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 15 package controller 16 17 import ( 18 "context" 19 "fmt" 20 "net" 21 "reflect" 22 "sort" 23 "strconv" 24 "sync" 25 "testing" 26 "time" 27 28 core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 29 "github.com/google/go-cmp/cmp" 30 corev1 "k8s.io/api/core/v1" 31 discovery "k8s.io/api/discovery/v1" 32 "k8s.io/apimachinery/pkg/api/resource" 33 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 34 "k8s.io/apimachinery/pkg/util/intstr" 35 36 "istio.io/api/annotation" 37 "istio.io/api/label" 38 meshconfig "istio.io/api/mesh/v1alpha1" 39 "istio.io/client-go/pkg/apis/networking/v1alpha3" 40 "istio.io/istio/pilot/pkg/features" 41 "istio.io/istio/pilot/pkg/model" 42 "istio.io/istio/pilot/pkg/serviceregistry/kube" 43 "istio.io/istio/pilot/pkg/serviceregistry/provider" 44 labelutil "istio.io/istio/pilot/pkg/serviceregistry/util/label" 45 "istio.io/istio/pilot/pkg/serviceregistry/util/xdsfake" 46 "istio.io/istio/pkg/cluster" 47 "istio.io/istio/pkg/config" 48 "istio.io/istio/pkg/config/constants" 49 "istio.io/istio/pkg/config/labels" 50 "istio.io/istio/pkg/config/mesh" 51 "istio.io/istio/pkg/config/protocol" 52 "istio.io/istio/pkg/config/visibility" 53 kubelib "istio.io/istio/pkg/kube" 54 "istio.io/istio/pkg/kube/kclient/clienttest" 55 "istio.io/istio/pkg/network" 56 "istio.io/istio/pkg/test" 57 "istio.io/istio/pkg/test/util/assert" 58 "istio.io/istio/pkg/test/util/retry" 59 "istio.io/istio/pkg/util/sets" 60 ) 61 62 const ( 63 testService = "test" 64 ) 65 66 // eventually polls cond until it completes (returns true) or times out (resulting in a test failure). 67 func eventually(t test.Failer, cond func() bool) { 68 t.Helper() 69 retry.UntilSuccessOrFail(t, func() error { 70 if !cond() { 71 return fmt.Errorf("failed to get positive condition") 72 } 73 return nil 74 }, retry.Timeout(time.Second), retry.Delay(time.Millisecond*10)) 75 } 76 77 func TestServices(t *testing.T) { 78 networksWatcher := mesh.NewFixedNetworksWatcher(&meshconfig.MeshNetworks{ 79 Networks: map[string]*meshconfig.Network{ 80 "network1": { 81 Endpoints: []*meshconfig.Network_NetworkEndpoints{ 82 { 83 Ne: &meshconfig.Network_NetworkEndpoints_FromCidr{ 84 FromCidr: "10.10.1.1/24", 85 }, 86 }, 87 }, 88 }, 89 "network2": { 90 Endpoints: []*meshconfig.Network_NetworkEndpoints{ 91 { 92 Ne: &meshconfig.Network_NetworkEndpoints_FromCidr{ 93 FromCidr: "10.11.1.1/24", 94 }, 95 }, 96 }, 97 }, 98 }, 99 }) 100 101 ctl, _ := NewFakeControllerWithOptions(t, FakeControllerOptions{NetworksWatcher: networksWatcher}) 102 t.Parallel() 103 ns := "ns-test" 104 105 hostname := kube.ServiceHostname(testService, ns, defaultFakeDomainSuffix) 106 107 var sds model.ServiceDiscovery = ctl 108 // "test", ports: http-example on 80 109 makeService(testService, ns, ctl, t) 110 111 eventually(t, func() bool { 112 out := sds.Services() 113 114 // Original test was checking for 'protocolTCP' - which is incorrect (the 115 // port name is 'http'. It was working because the Service was created with 116 // an invalid protocol, and the code was ignoring that ( not TCP/UDP). 117 for _, item := range out { 118 if item.Hostname == hostname && 119 len(item.Ports) == 1 && 120 item.Ports[0].Protocol == protocol.HTTP { 121 return true 122 } 123 } 124 return false 125 }) 126 127 // 2 ports 1001, 2 IPs 128 createEndpoints(t, ctl, testService, ns, []string{"http-example", "foo"}, []string{"10.10.1.1", "10.11.1.2"}, nil, nil) 129 130 svc := sds.GetService(hostname) 131 if svc == nil { 132 t.Fatalf("GetService(%q) => should exists", hostname) 133 } 134 if svc.Hostname != hostname { 135 t.Fatalf("GetService(%q) => %q", hostname, svc.Hostname) 136 } 137 assert.EventuallyEqual(t, func() int { 138 ep := GetEndpointsForPort(svc, ctl.Endpoints, 80) 139 return len(ep) 140 }, 2) 141 142 ep := GetEndpointsForPort(svc, ctl.Endpoints, 80) 143 if len(ep) != 2 { 144 t.Fatalf("Invalid response for GetEndpoints %v", ep) 145 } 146 147 if ep[0].Address == "10.10.1.1" && ep[0].Network != "network1" { 148 t.Fatalf("Endpoint with IP 10.10.1.1 is expected to be in network1 but get: %s", ep[0].Network) 149 } 150 151 if ep[1].Address == "10.11.1.2" && ep[1].Network != "network2" { 152 t.Fatalf("Endpoint with IP 10.11.1.2 is expected to be in network2 but get: %s", ep[1].Network) 153 } 154 155 missing := kube.ServiceHostname("does-not-exist", ns, defaultFakeDomainSuffix) 156 svc = sds.GetService(missing) 157 if svc != nil { 158 t.Fatalf("GetService(%q) => %s, should not exist", missing, svc.Hostname) 159 } 160 } 161 162 func makeService(n, ns string, cl *FakeController, t *testing.T) { 163 clienttest.Wrap(t, cl.services).Create(&corev1.Service{ 164 ObjectMeta: metav1.ObjectMeta{Name: n, Namespace: ns}, 165 Spec: corev1.ServiceSpec{ 166 Ports: []corev1.ServicePort{ 167 { 168 Port: 80, 169 Name: "http-example", 170 Protocol: corev1.ProtocolTCP, // Not added automatically by fake 171 }, 172 }, 173 }, 174 }) 175 log.Infof("Created service %s", n) 176 cl.opts.XDSUpdater.(*xdsfake.Updater).WaitOrFail(t, "service") 177 } 178 179 func TestController_GetPodLocality(t *testing.T) { 180 pod1 := generatePod("128.0.1.1", "pod1", "nsA", "", "node1", map[string]string{"app": "prod-app"}, map[string]string{}) 181 pod2 := generatePod("128.0.1.2", "pod2", "nsB", "", "node2", map[string]string{"app": "prod-app"}, map[string]string{}) 182 podOverride := generatePod("128.0.1.2", "pod2", "nsB", "", 183 "node1", map[string]string{"app": "prod-app", model.LocalityLabel: "regionOverride.zoneOverride.subzoneOverride"}, map[string]string{}) 184 testCases := []struct { 185 name string 186 pods []*corev1.Pod 187 nodes []*corev1.Node 188 wantAZ map[*corev1.Pod]string 189 }{ 190 { 191 name: "should return correct az for given address", 192 pods: []*corev1.Pod{pod1, pod2}, 193 nodes: []*corev1.Node{ 194 generateNode("node1", map[string]string{NodeZoneLabel: "zone1", NodeRegionLabel: "region1", label.TopologySubzone.Name: "subzone1"}), 195 generateNode("node2", map[string]string{NodeZoneLabel: "zone2", NodeRegionLabel: "region2", label.TopologySubzone.Name: "subzone2"}), 196 }, 197 wantAZ: map[*corev1.Pod]string{ 198 pod1: "region1/zone1/subzone1", 199 pod2: "region2/zone2/subzone2", 200 }, 201 }, 202 { 203 name: "should return correct az for given address", 204 pods: []*corev1.Pod{pod1, pod2}, 205 nodes: []*corev1.Node{ 206 generateNode("node1", map[string]string{NodeZoneLabel: "zone1", NodeRegionLabel: "region1"}), 207 generateNode("node2", map[string]string{NodeZoneLabel: "zone2", NodeRegionLabel: "region2"}), 208 }, 209 wantAZ: map[*corev1.Pod]string{ 210 pod1: "region1/zone1/", 211 pod2: "region2/zone2/", 212 }, 213 }, 214 { 215 name: "should return false if pod isn't in the cache", 216 wantAZ: map[*corev1.Pod]string{ 217 pod1: "", 218 pod2: "", 219 }, 220 }, 221 { 222 name: "should return false if node isn't in the cache", 223 pods: []*corev1.Pod{pod1, pod2}, 224 wantAZ: map[*corev1.Pod]string{ 225 pod1: "", 226 pod2: "", 227 }, 228 }, 229 { 230 name: "should return correct az if node has only region label", 231 pods: []*corev1.Pod{pod1, pod2}, 232 nodes: []*corev1.Node{ 233 generateNode("node1", map[string]string{NodeRegionLabel: "region1"}), 234 generateNode("node2", map[string]string{NodeRegionLabel: "region2"}), 235 }, 236 wantAZ: map[*corev1.Pod]string{ 237 pod1: "region1//", 238 pod2: "region2//", 239 }, 240 }, 241 { 242 name: "should return correct az if node has only zone label", 243 pods: []*corev1.Pod{pod1, pod2}, 244 nodes: []*corev1.Node{ 245 generateNode("node1", map[string]string{NodeZoneLabel: "zone1"}), 246 generateNode("node2", map[string]string{NodeZoneLabel: "zone2"}), 247 }, 248 wantAZ: map[*corev1.Pod]string{ 249 pod1: "/zone1/", 250 pod2: "/zone2/", 251 }, 252 }, 253 { 254 name: "should return correct az if node has only subzone label", 255 pods: []*corev1.Pod{pod1, pod2}, 256 nodes: []*corev1.Node{ 257 generateNode("node1", map[string]string{label.TopologySubzone.Name: "subzone1"}), 258 generateNode("node2", map[string]string{label.TopologySubzone.Name: "subzone2"}), 259 }, 260 wantAZ: map[*corev1.Pod]string{ 261 pod1: "//subzone1", 262 pod2: "//subzone2", 263 }, 264 }, 265 { 266 name: "should return correct az for given address", 267 pods: []*corev1.Pod{podOverride}, 268 nodes: []*corev1.Node{ 269 generateNode("node1", map[string]string{NodeZoneLabel: "zone1", NodeRegionLabel: "region1", label.TopologySubzone.Name: "subzone1"}), 270 }, 271 wantAZ: map[*corev1.Pod]string{ 272 podOverride: "regionOverride/zoneOverride/subzoneOverride", 273 }, 274 }, 275 } 276 277 for _, tc := range testCases { 278 // If using t.Parallel() you must copy the iteration to a new local variable 279 // https://github.com/golang/go/wiki/CommonMistakes#using-goroutines-on-loop-iterator-variables 280 tc := tc 281 t.Run(tc.name, func(t *testing.T) { 282 t.Parallel() 283 // Setup kube caches 284 controller, fx := NewFakeControllerWithOptions(t, FakeControllerOptions{}) 285 286 addNodes(t, controller, tc.nodes...) 287 addPods(t, controller, fx, tc.pods...) 288 289 // Verify expected existing pod AZs 290 for pod, wantAZ := range tc.wantAZ { 291 az := controller.getPodLocality(pod) 292 if wantAZ != "" { 293 if !reflect.DeepEqual(az, wantAZ) { 294 t.Fatalf("Wanted az: %s, got: %s", wantAZ, az) 295 } 296 } else { 297 if az != "" { 298 t.Fatalf("Unexpectedly found az: %s for pod: %s", az, pod.ObjectMeta.Name) 299 } 300 } 301 } 302 }) 303 } 304 } 305 306 func TestProxyK8sHostnameLabel(t *testing.T) { 307 clusterID := cluster.ID("fakeCluster") 308 controller, fx := NewFakeControllerWithOptions(t, FakeControllerOptions{ 309 ClusterID: clusterID, 310 }) 311 312 pod := generatePod("128.0.0.1", "pod1", "nsa", "foo", "node1", map[string]string{"app": "test-app"}, map[string]string{}) 313 addPods(t, controller, fx, pod) 314 315 proxy := &model.Proxy{ 316 Type: model.Router, 317 IPAddresses: []string{"128.0.0.1"}, 318 ID: "pod1.nsa", 319 DNSDomain: "nsa.svc.cluster.local", 320 Metadata: &model.NodeMetadata{Namespace: "nsa", ClusterID: clusterID}, 321 } 322 got := controller.GetProxyWorkloadLabels(proxy) 323 if pod.Spec.NodeName != got[labelutil.LabelHostname] { 324 t.Fatalf("expected node name %v, got %v", pod.Spec.NodeName, got[labelutil.LabelHostname]) 325 } 326 } 327 328 func TestGetProxyServiceTargets(t *testing.T) { 329 clusterID := cluster.ID("fakeCluster") 330 networkID := network.ID("fakeNetwork") 331 controller, fx := NewFakeControllerWithOptions(t, FakeControllerOptions{ 332 ClusterID: clusterID, 333 }) 334 // add a network ID to test endpoints include topology.istio.io/network label 335 controller.network = networkID 336 337 p := generatePod("128.0.0.1", "pod1", "nsa", "foo", "node1", map[string]string{"app": "test-app"}, map[string]string{}) 338 addPods(t, controller, fx, p) 339 340 k8sSaOnVM := "acct4" 341 canonicalSaOnVM := "acctvm2@gserviceaccount2.com" 342 343 createServiceWait(controller, "svc1", "nsa", nil, 344 map[string]string{ 345 annotation.AlphaKubernetesServiceAccounts.Name: k8sSaOnVM, 346 annotation.AlphaCanonicalServiceAccounts.Name: canonicalSaOnVM, 347 }, 348 []int32{8080}, map[string]string{"app": "prod-app"}, t) 349 350 // Endpoints are generated by Kubernetes from pod labels and service selectors. 351 // Here we manually create them for mocking purpose. 352 svc1Ips := []string{"128.0.0.1"} 353 portNames := []string{"tcp-port"} 354 // Create 1 endpoint that refers to a pod in the same namespace. 355 createEndpoints(t, controller, "svc1", "nsA", portNames, svc1Ips, nil, nil) 356 357 // Creates 100 endpoints that refers to a pod in a different namespace. 358 fakeSvcCounts := 100 359 for i := 0; i < fakeSvcCounts; i++ { 360 svcName := fmt.Sprintf("svc-fake-%d", i) 361 createServiceWait(controller, svcName, "nsfake", nil, 362 map[string]string{ 363 annotation.AlphaKubernetesServiceAccounts.Name: k8sSaOnVM, 364 annotation.AlphaCanonicalServiceAccounts.Name: canonicalSaOnVM, 365 }, 366 []int32{8080}, map[string]string{"app": "prod-app"}, t) 367 368 createEndpoints(t, controller, svcName, "nsfake", portNames, svc1Ips, nil, nil) 369 fx.WaitOrFail(t, "eds") 370 } 371 372 // Create 1 endpoint that refers to a pod in the same namespace. 373 createEndpoints(t, controller, "svc1", "nsa", portNames, svc1Ips, nil, nil) 374 fx.WaitOrFail(t, "eds") 375 376 // this can test get pod by proxy ID 377 svcNode := &model.Proxy{ 378 Type: model.Router, 379 IPAddresses: []string{"128.0.0.1"}, 380 ID: "pod1.nsa", 381 DNSDomain: "nsa.svc.cluster.local", 382 Metadata: &model.NodeMetadata{Namespace: "nsa", ClusterID: clusterID}, 383 } 384 serviceInstances := controller.GetProxyServiceTargets(svcNode) 385 386 if len(serviceInstances) != 1 { 387 t.Fatalf("GetProxyServiceTargets() expected 1 instance, got %d", len(serviceInstances)) 388 } 389 390 hostname := kube.ServiceHostname("svc1", "nsa", defaultFakeDomainSuffix) 391 if serviceInstances[0].Service.Hostname != hostname { 392 t.Fatalf("GetProxyServiceTargets() wrong service instance returned => hostname %q, want %q", 393 serviceInstances[0].Service.Hostname, hostname) 394 } 395 396 // Test that we can look up instances just by Proxy metadata 397 metaServices := controller.GetProxyServiceTargets(&model.Proxy{ 398 Type: "sidecar", 399 IPAddresses: []string{"1.1.1.1"}, 400 Locality: &core.Locality{Region: "r", Zone: "z"}, 401 ConfigNamespace: "nsa", 402 Labels: map[string]string{ 403 "app": "prod-app", 404 label.SecurityTlsMode.Name: "mutual", 405 }, 406 Metadata: &model.NodeMetadata{ 407 ServiceAccount: "account", 408 ClusterID: clusterID, 409 Labels: map[string]string{ 410 "app": "prod-app", 411 label.SecurityTlsMode.Name: "mutual", 412 }, 413 }, 414 }) 415 416 expected := model.ServiceTarget{ 417 Service: &model.Service{ 418 Hostname: "svc1.nsa.svc.company.com", 419 ClusterVIPs: model.AddressMap{ 420 Addresses: map[cluster.ID][]string{clusterID: {"10.0.0.1"}}, 421 }, 422 DefaultAddress: "10.0.0.1", 423 Ports: []*model.Port{{Name: "tcp-port", Port: 8080, Protocol: protocol.TCP}}, 424 ServiceAccounts: []string{"acctvm2@gserviceaccount2.com", "spiffe://cluster.local/ns/nsa/sa/acct4"}, 425 Attributes: model.ServiceAttributes{ 426 ServiceRegistry: provider.Kubernetes, 427 Name: "svc1", 428 Namespace: "nsa", 429 LabelSelectors: map[string]string{"app": "prod-app"}, 430 K8sAttributes: model.K8sAttributes{ 431 Type: string(corev1.ServiceTypeClusterIP), 432 }, 433 }, 434 }, 435 Port: model.ServiceInstancePort{ 436 ServicePort: &model.Port{Name: "tcp-port", Port: 8080, Protocol: protocol.TCP}, 437 TargetPort: 0, 438 }, 439 } 440 441 if len(metaServices) != 1 { 442 t.Fatalf("expected 1 instance, got %v", len(metaServices)) 443 } 444 if !reflect.DeepEqual(expected, metaServices[0]) { 445 t.Fatalf("expected instance %v, got %v", expected, metaServices[0]) 446 } 447 448 // Test that we first look up instances by Proxy pod 449 450 node := generateNode("node1", map[string]string{NodeZoneLabel: "zone1", NodeRegionLabel: "region1", label.TopologySubzone.Name: "subzone1"}) 451 addNodes(t, controller, node) 452 453 // 1. pod without `istio-locality` label, get locality from node label. 454 p = generatePod("129.0.0.1", "pod2", "nsa", "svcaccount", "node1", 455 map[string]string{"app": "prod-app"}, nil) 456 addPods(t, controller, fx, p) 457 458 // this can test get pod by proxy ip address 459 podServices := controller.GetProxyServiceTargets(&model.Proxy{ 460 Type: "sidecar", 461 IPAddresses: []string{"129.0.0.1"}, 462 Locality: &core.Locality{Region: "r", Zone: "z"}, 463 ConfigNamespace: "nsa", 464 Labels: map[string]string{ 465 "app": "prod-app", 466 }, 467 Metadata: &model.NodeMetadata{ 468 ServiceAccount: "account", 469 ClusterID: clusterID, 470 Labels: map[string]string{ 471 "app": "prod-app", 472 }, 473 }, 474 }) 475 476 expected = model.ServiceTarget{ 477 Service: &model.Service{ 478 Hostname: "svc1.nsa.svc.company.com", 479 ClusterVIPs: model.AddressMap{ 480 Addresses: map[cluster.ID][]string{clusterID: {"10.0.0.1"}}, 481 }, 482 DefaultAddress: "10.0.0.1", 483 Ports: []*model.Port{{Name: "tcp-port", Port: 8080, Protocol: protocol.TCP}}, 484 ServiceAccounts: []string{"acctvm2@gserviceaccount2.com", "spiffe://cluster.local/ns/nsa/sa/acct4"}, 485 Attributes: model.ServiceAttributes{ 486 ServiceRegistry: provider.Kubernetes, 487 Name: "svc1", 488 Namespace: "nsa", 489 LabelSelectors: map[string]string{"app": "prod-app"}, 490 K8sAttributes: model.K8sAttributes{ 491 Type: string(corev1.ServiceTypeClusterIP), 492 }, 493 }, 494 }, 495 Port: model.ServiceInstancePort{ 496 ServicePort: &model.Port{Name: "tcp-port", Port: 8080, Protocol: protocol.TCP}, 497 TargetPort: 0, 498 }, 499 } 500 if len(podServices) != 1 { 501 t.Fatalf("expected 1 instance, got %v", len(podServices)) 502 } 503 if !reflect.DeepEqual(expected, podServices[0]) { 504 t.Fatalf("expected instance %v, got %v", expected, podServices[0]) 505 } 506 507 // 2. pod with `istio-locality` label, ignore node label. 508 p = generatePod("129.0.0.2", "pod3", "nsa", "svcaccount", "node1", 509 map[string]string{"app": "prod-app", "istio-locality": "region.zone"}, nil) 510 addPods(t, controller, fx, p) 511 512 // this can test get pod by proxy ip address 513 podServices = controller.GetProxyServiceTargets(&model.Proxy{ 514 Type: "sidecar", 515 IPAddresses: []string{"129.0.0.2"}, 516 Locality: &core.Locality{Region: "r", Zone: "z"}, 517 ConfigNamespace: "nsa", 518 Labels: map[string]string{ 519 "app": "prod-app", 520 }, 521 Metadata: &model.NodeMetadata{ 522 ServiceAccount: "account", 523 ClusterID: clusterID, 524 Labels: map[string]string{ 525 "app": "prod-app", 526 }, 527 }, 528 }) 529 530 expected = model.ServiceTarget{ 531 Service: &model.Service{ 532 Hostname: "svc1.nsa.svc.company.com", 533 ClusterVIPs: model.AddressMap{ 534 Addresses: map[cluster.ID][]string{clusterID: {"10.0.0.1"}}, 535 }, 536 DefaultAddress: "10.0.0.1", 537 Ports: []*model.Port{{Name: "tcp-port", Port: 8080, Protocol: protocol.TCP}}, 538 ServiceAccounts: []string{"acctvm2@gserviceaccount2.com", "spiffe://cluster.local/ns/nsa/sa/acct4"}, 539 Attributes: model.ServiceAttributes{ 540 ServiceRegistry: provider.Kubernetes, 541 Name: "svc1", 542 Namespace: "nsa", 543 LabelSelectors: map[string]string{"app": "prod-app"}, 544 K8sAttributes: model.K8sAttributes{ 545 Type: string(corev1.ServiceTypeClusterIP), 546 }, 547 }, 548 }, 549 Port: model.ServiceInstancePort{ 550 ServicePort: &model.Port{Name: "tcp-port", Port: 8080, Protocol: protocol.TCP}, 551 }, 552 } 553 if len(podServices) != 1 { 554 t.Fatalf("expected 1 instance, got %v", len(podServices)) 555 } 556 if !reflect.DeepEqual(expected, podServices[0]) { 557 t.Fatalf("expected instance %v, got %v", expected, podServices[0]) 558 } 559 560 // pod with no services should return no service targets 561 p = generatePod("130.0.0.1", "pod4", "nsa", "foo", "node1", map[string]string{"app": "no-service-app"}, map[string]string{}) 562 addPods(t, controller, fx, p) 563 564 podServices = controller.GetProxyServiceTargets(&model.Proxy{ 565 Type: "sidecar", 566 IPAddresses: []string{"130.0.0.1"}, 567 Locality: &core.Locality{Region: "r", Zone: "z"}, 568 ConfigNamespace: "nsa", 569 Labels: map[string]string{ 570 "app": "no-service-app", 571 }, 572 Metadata: &model.NodeMetadata{ 573 ServiceAccount: "account", 574 ClusterID: clusterID, 575 Labels: map[string]string{ 576 "app": "no-service-app", 577 }, 578 }, 579 }) 580 if len(podServices) != 0 { 581 t.Fatalf("expect 0 instance, got %v", len(podServices)) 582 } 583 } 584 585 func TestGetProxyServiceTargetsWithMultiIPsAndTargetPorts(t *testing.T) { 586 pod1 := generatePod("128.0.0.1", "pod1", "nsa", "foo", "node1", map[string]string{"app": "test-app"}, map[string]string{}) 587 testCases := []struct { 588 name string 589 pods []*corev1.Pod 590 ips []string 591 ports []corev1.ServicePort 592 wantPorts []model.ServiceInstancePort 593 }{ 594 { 595 name: "multiple proxy ips single port", 596 pods: []*corev1.Pod{pod1}, 597 ips: []string{"128.0.0.1", "192.168.2.6"}, 598 ports: []corev1.ServicePort{ 599 { 600 Name: "tcp-port", 601 Port: 8080, 602 Protocol: "http", 603 TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: 8080}, 604 }, 605 }, 606 wantPorts: []model.ServiceInstancePort{ 607 { 608 ServicePort: &model.Port{ 609 Name: "tcp-port", 610 Port: 8080, 611 Protocol: "TCP", 612 }, 613 TargetPort: 8080, 614 }, 615 }, 616 }, 617 { 618 name: "single proxy ip single port", 619 pods: []*corev1.Pod{pod1}, 620 ips: []string{"128.0.0.1"}, 621 ports: []corev1.ServicePort{ 622 { 623 Name: "tcp-port", 624 Port: 8080, 625 Protocol: "TCP", 626 TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: 8080}, 627 }, 628 }, 629 wantPorts: []model.ServiceInstancePort{ 630 { 631 ServicePort: &model.Port{ 632 Name: "tcp-port", 633 Port: 8080, 634 Protocol: "TCP", 635 }, 636 TargetPort: 8080, 637 }, 638 }, 639 }, 640 { 641 name: "multiple proxy ips multiple ports", 642 pods: []*corev1.Pod{pod1}, 643 ips: []string{"128.0.0.1", "192.168.2.6"}, 644 ports: []corev1.ServicePort{ 645 { 646 Name: "tcp-port-1", 647 Port: 8080, 648 Protocol: "http", 649 TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: 8080}, 650 }, 651 { 652 Name: "tcp-port-2", 653 Port: 9090, 654 Protocol: "http", 655 TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: 9090}, 656 }, 657 }, 658 wantPorts: []model.ServiceInstancePort{ 659 { 660 ServicePort: &model.Port{ 661 Name: "tcp-port-1", 662 Port: 8080, 663 Protocol: "TCP", 664 }, 665 TargetPort: 8080, 666 }, 667 { 668 ServicePort: &model.Port{ 669 Name: "tcp-port-2", 670 Port: 9090, 671 Protocol: "TCP", 672 }, 673 TargetPort: 9090, 674 }, 675 { 676 ServicePort: &model.Port{ 677 Name: "tcp-port-1", 678 Port: 7442, 679 Protocol: "TCP", 680 }, 681 TargetPort: 7442, 682 }, 683 }, 684 }, 685 { 686 name: "single proxy ip multiple ports same target port with different protocols", 687 pods: []*corev1.Pod{pod1}, 688 ips: []string{"128.0.0.1"}, 689 ports: []corev1.ServicePort{ 690 { 691 Name: "tcp-port", 692 Port: 8080, 693 Protocol: "TCP", 694 TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: 8080}, 695 }, 696 { 697 Name: "http-port", 698 Port: 9090, 699 Protocol: "TCP", 700 TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: 8080}, 701 }, 702 }, 703 wantPorts: []model.ServiceInstancePort{ 704 { 705 ServicePort: &model.Port{ 706 Name: "tcp-port", 707 Port: 8080, 708 Protocol: "TCP", 709 }, 710 TargetPort: 8080, 711 }, 712 { 713 ServicePort: &model.Port{ 714 Name: "http-port", 715 Port: 9090, 716 Protocol: "HTTP", 717 }, 718 TargetPort: 8080, 719 }, 720 }, 721 }, 722 { 723 name: "single proxy ip multiple ports same target port with overlapping protocols", 724 pods: []*corev1.Pod{pod1}, 725 ips: []string{"128.0.0.1"}, 726 ports: []corev1.ServicePort{ 727 { 728 Name: "http-7442", 729 Port: 7442, 730 Protocol: "TCP", 731 TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: 7442}, 732 }, 733 { 734 Name: "tcp-8443", 735 Port: 8443, 736 Protocol: "TCP", 737 TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: 7442}, 738 }, 739 { 740 Name: "http-7557", 741 Port: 7557, 742 Protocol: "TCP", 743 TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: 7442}, 744 }, 745 }, 746 wantPorts: []model.ServiceInstancePort{ 747 { 748 ServicePort: &model.Port{ 749 Name: "http-7442", 750 Port: 7442, 751 Protocol: "HTTP", 752 }, 753 TargetPort: 7442, 754 }, 755 { 756 ServicePort: &model.Port{ 757 Name: "tcp-8443", 758 Port: 8443, 759 Protocol: "TCP", 760 }, 761 TargetPort: 7442, 762 }, 763 }, 764 }, 765 { 766 name: "single proxy ip multiple ports", 767 pods: []*corev1.Pod{pod1}, 768 ips: []string{"128.0.0.1"}, 769 ports: []corev1.ServicePort{ 770 { 771 Name: "tcp-port", 772 Port: 8080, 773 Protocol: "TCP", 774 TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: 8080}, 775 }, 776 { 777 Name: "http-port", 778 Port: 9090, 779 Protocol: "TCP", 780 TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: 9090}, 781 }, 782 }, 783 wantPorts: []model.ServiceInstancePort{ 784 { 785 ServicePort: &model.Port{ 786 Name: "tcp-port", 787 Port: 8080, 788 Protocol: "TCP", 789 }, 790 TargetPort: 8080, 791 }, 792 { 793 ServicePort: &model.Port{ 794 Name: "http-port", 795 Port: 9090, 796 Protocol: "HTTP", 797 }, 798 TargetPort: 9090, 799 }, 800 }, 801 }, 802 } 803 804 for _, c := range testCases { 805 t.Run(c.name, func(t *testing.T) { 806 // Setup kube caches 807 controller, fx := NewFakeControllerWithOptions(t, FakeControllerOptions{}) 808 809 addPods(t, controller, fx, c.pods...) 810 811 createServiceWithTargetPorts(controller, "svc1", "nsa", 812 map[string]string{ 813 annotation.AlphaKubernetesServiceAccounts.Name: "acct4", 814 annotation.AlphaCanonicalServiceAccounts.Name: "acctvm2@gserviceaccount2.com", 815 }, 816 c.ports, map[string]string{"app": "test-app"}, t) 817 818 serviceInstances := controller.GetProxyServiceTargets(&model.Proxy{Metadata: &model.NodeMetadata{}, IPAddresses: c.ips}) 819 820 for i, svc := range serviceInstances { 821 assert.Equal(t, svc.Port, c.wantPorts[i]) 822 } 823 }) 824 } 825 } 826 827 func TestGetProxyServiceTargets_WorkloadInstance(t *testing.T) { 828 ctl, _ := NewFakeControllerWithOptions(t, FakeControllerOptions{}) 829 830 createServiceWait(ctl, "ratings", "bookinfo-ratings", 831 map[string]string{}, 832 map[string]string{ 833 annotation.AlphaKubernetesServiceAccounts.Name: "ratings", 834 annotation.AlphaCanonicalServiceAccounts.Name: "ratings@gserviceaccount2.com", 835 }, 836 []int32{8080}, map[string]string{"app": "ratings"}, t) 837 838 createServiceWait(ctl, "details", "bookinfo-details", 839 map[string]string{}, 840 map[string]string{ 841 annotation.AlphaKubernetesServiceAccounts.Name: "details", 842 annotation.AlphaCanonicalServiceAccounts.Name: "details@gserviceaccount2.com", 843 }, 844 []int32{9090}, map[string]string{"app": "details"}, t) 845 846 createServiceWait(ctl, "reviews", "bookinfo-reviews", 847 map[string]string{}, 848 map[string]string{ 849 annotation.AlphaKubernetesServiceAccounts.Name: "reviews", 850 annotation.AlphaCanonicalServiceAccounts.Name: "reviews@gserviceaccount2.com", 851 }, 852 []int32{7070}, map[string]string{"app": "reviews"}, t) 853 854 wiRatings1 := &model.WorkloadInstance{ 855 Name: "ratings-1", 856 Namespace: "bookinfo-ratings", 857 Endpoint: &model.IstioEndpoint{ 858 Labels: labels.Instance{"app": "ratings"}, 859 Address: "2.2.2.21", 860 EndpointPort: 8080, 861 }, 862 } 863 864 wiDetails1 := &model.WorkloadInstance{ 865 Name: "details-1", 866 Namespace: "bookinfo-details", 867 Endpoint: &model.IstioEndpoint{ 868 Labels: labels.Instance{"app": "details"}, 869 Address: "2.2.2.21", 870 EndpointPort: 9090, 871 }, 872 } 873 874 wiReviews1 := &model.WorkloadInstance{ 875 Name: "reviews-1", 876 Namespace: "bookinfo-reviews", 877 Endpoint: &model.IstioEndpoint{ 878 Labels: labels.Instance{"app": "reviews"}, 879 Address: "3.3.3.31", 880 EndpointPort: 7070, 881 }, 882 } 883 884 wiReviews2 := &model.WorkloadInstance{ 885 Name: "reviews-2", 886 Namespace: "bookinfo-reviews", 887 Endpoint: &model.IstioEndpoint{ 888 Labels: labels.Instance{"app": "reviews"}, 889 Address: "3.3.3.32", 890 EndpointPort: 7071, 891 }, 892 } 893 894 wiProduct1 := &model.WorkloadInstance{ 895 Name: "productpage-1", 896 Namespace: "bookinfo-productpage", 897 Endpoint: &model.IstioEndpoint{ 898 Labels: labels.Instance{"app": "productpage"}, 899 Address: "4.4.4.41", 900 EndpointPort: 6060, 901 }, 902 } 903 904 for _, wi := range []*model.WorkloadInstance{wiRatings1, wiDetails1, wiReviews1, wiReviews2, wiProduct1} { 905 ctl.workloadInstanceHandler(wi, model.EventAdd) // simulate adding a workload entry 906 } 907 908 cases := []struct { 909 name string 910 proxy *model.Proxy 911 want []model.ServiceTarget 912 }{ 913 { 914 name: "proxy with unspecified IP", 915 proxy: &model.Proxy{Metadata: &model.NodeMetadata{}, IPAddresses: nil}, 916 want: nil, 917 }, 918 { 919 name: "proxy with IP not in the registry", 920 proxy: &model.Proxy{Metadata: &model.NodeMetadata{}, IPAddresses: []string{"1.1.1.1"}}, 921 want: nil, 922 }, 923 { 924 name: "proxy with IP from the registry, 1 matching WE, but no matching Service", 925 proxy: &model.Proxy{Metadata: &model.NodeMetadata{}, IPAddresses: []string{"4.4.4.41"}}, 926 want: nil, 927 }, 928 { 929 name: "proxy with IP from the registry, 1 matching WE, and matching Service", 930 proxy: &model.Proxy{Metadata: &model.NodeMetadata{}, IPAddresses: []string{"3.3.3.31"}}, 931 want: []model.ServiceTarget{{ 932 Service: &model.Service{ 933 Hostname: "reviews.bookinfo-reviews.svc.company.com", 934 }, 935 Port: model.ServiceInstancePort{ 936 ServicePort: nil, 937 TargetPort: 7070, 938 }, 939 }}, 940 }, 941 { 942 name: "proxy with IP from the registry, 2 matching WE, and matching Service", 943 proxy: &model.Proxy{Metadata: &model.NodeMetadata{}, IPAddresses: []string{"2.2.2.21"}}, 944 want: []model.ServiceTarget{{ 945 Service: &model.Service{ 946 Hostname: "details.bookinfo-details.svc.company.com", 947 }, 948 Port: model.ServiceInstancePort{ 949 ServicePort: nil, 950 TargetPort: 9090, 951 }, 952 }}, 953 }, 954 { 955 name: "proxy with IP from the registry, 2 matching WE, and matching Service, and proxy ID equal to WE with a different address", 956 proxy: &model.Proxy{ 957 Metadata: &model.NodeMetadata{}, IPAddresses: []string{"2.2.2.21"}, 958 ID: "reviews-1.bookinfo-reviews", ConfigNamespace: "bookinfo-reviews", 959 }, 960 want: []model.ServiceTarget{{ 961 Service: &model.Service{ 962 Hostname: "details.bookinfo-details.svc.company.com", 963 }, 964 Port: model.ServiceInstancePort{ 965 ServicePort: nil, 966 TargetPort: 9090, 967 }, 968 }}, 969 }, 970 { 971 name: "proxy with IP from the registry, 2 matching WE, and matching Service, and proxy ID equal to WE name, but proxy.ID != proxy.ConfigNamespace", 972 proxy: &model.Proxy{ 973 Metadata: &model.NodeMetadata{}, IPAddresses: []string{"2.2.2.21"}, 974 ID: "ratings-1.bookinfo-ratings", ConfigNamespace: "wrong-namespace", 975 }, 976 want: []model.ServiceTarget{{ 977 Service: &model.Service{ 978 Hostname: "details.bookinfo-details.svc.company.com", 979 }, 980 Port: model.ServiceInstancePort{ 981 ServicePort: nil, 982 TargetPort: 9090, 983 }, 984 }}, 985 }, 986 { 987 name: "proxy with IP from the registry, 2 matching WE, and matching Service, and proxy.ID == WE name", 988 proxy: &model.Proxy{ 989 Metadata: &model.NodeMetadata{}, IPAddresses: []string{"2.2.2.21"}, 990 ID: "ratings-1.bookinfo-ratings", ConfigNamespace: "bookinfo-ratings", 991 }, 992 want: []model.ServiceTarget{{ 993 Service: &model.Service{ 994 Hostname: "ratings.bookinfo-ratings.svc.company.com", 995 }, 996 Port: model.ServiceInstancePort{ 997 ServicePort: nil, 998 TargetPort: 8080, 999 }, 1000 }}, 1001 }, 1002 { 1003 name: "proxy with IP from the registry, 2 matching WE, and matching Service, and proxy.ID != WE name, but proxy.ConfigNamespace == WE namespace", 1004 proxy: &model.Proxy{ 1005 Metadata: &model.NodeMetadata{}, IPAddresses: []string{"2.2.2.21"}, 1006 ID: "wrong-name.bookinfo-ratings", ConfigNamespace: "bookinfo-ratings", 1007 }, 1008 want: []model.ServiceTarget{{ 1009 Service: &model.Service{ 1010 Hostname: "ratings.bookinfo-ratings.svc.company.com", 1011 }, 1012 Port: model.ServiceInstancePort{ 1013 ServicePort: nil, 1014 TargetPort: 8080, 1015 }, 1016 }}, 1017 }, 1018 } 1019 1020 for _, tc := range cases { 1021 t.Run(tc.name, func(t *testing.T) { 1022 got := ctl.GetProxyServiceTargets(tc.proxy) 1023 1024 if diff := cmp.Diff(len(tc.want), len(got)); diff != "" { 1025 t.Fatalf("GetProxyServiceTargets() returned unexpected number of service instances (--want/++got): %v", diff) 1026 } 1027 1028 for i := range tc.want { 1029 assert.Equal(t, tc.want[i].Service.Hostname, got[i].Service.Hostname) 1030 assert.Equal(t, tc.want[i].Port.TargetPort, got[i].Port.TargetPort) 1031 } 1032 }) 1033 } 1034 } 1035 1036 func TestController_Service(t *testing.T) { 1037 controller, _ := NewFakeControllerWithOptions(t, FakeControllerOptions{}) 1038 1039 // Use a timeout to keep the test from hanging. 1040 1041 createServiceWait(controller, "svc1", "nsA", 1042 map[string]string{}, map[string]string{}, 1043 []int32{8080}, map[string]string{"test-app": "test-app-1"}, t) 1044 createServiceWait(controller, "svc2", "nsA", 1045 map[string]string{}, map[string]string{}, 1046 []int32{8081}, map[string]string{"test-app": "test-app-2"}, t) 1047 createServiceWait(controller, "svc3", "nsA", 1048 map[string]string{}, map[string]string{}, 1049 []int32{8082}, map[string]string{"test-app": "test-app-3"}, t) 1050 createServiceWait(controller, "svc4", "nsA", 1051 map[string]string{}, map[string]string{}, 1052 []int32{8083}, map[string]string{"test-app": "test-app-4"}, t) 1053 1054 expectedSvcList := []*model.Service{ 1055 { 1056 Hostname: kube.ServiceHostname("svc1", "nsA", defaultFakeDomainSuffix), 1057 DefaultAddress: "10.0.0.1", 1058 Ports: model.PortList{ 1059 &model.Port{ 1060 Name: "tcp-port", 1061 Port: 8080, 1062 Protocol: protocol.TCP, 1063 }, 1064 }, 1065 }, 1066 { 1067 Hostname: kube.ServiceHostname("svc2", "nsA", defaultFakeDomainSuffix), 1068 DefaultAddress: "10.0.0.1", 1069 Ports: model.PortList{ 1070 &model.Port{ 1071 Name: "tcp-port", 1072 Port: 8081, 1073 Protocol: protocol.TCP, 1074 }, 1075 }, 1076 }, 1077 { 1078 Hostname: kube.ServiceHostname("svc3", "nsA", defaultFakeDomainSuffix), 1079 DefaultAddress: "10.0.0.1", 1080 Ports: model.PortList{ 1081 &model.Port{ 1082 Name: "tcp-port", 1083 Port: 8082, 1084 Protocol: protocol.TCP, 1085 }, 1086 }, 1087 }, 1088 { 1089 Hostname: kube.ServiceHostname("svc4", "nsA", defaultFakeDomainSuffix), 1090 DefaultAddress: "10.0.0.1", 1091 Ports: model.PortList{ 1092 &model.Port{ 1093 Name: "tcp-port", 1094 Port: 8083, 1095 Protocol: protocol.TCP, 1096 }, 1097 }, 1098 }, 1099 } 1100 1101 svcList := controller.Services() 1102 servicesEqual(svcList, expectedSvcList) 1103 } 1104 1105 func TestController_ServiceWithFixedDiscoveryNamespaces(t *testing.T) { 1106 meshWatcher := mesh.NewFixedWatcher(&meshconfig.MeshConfig{ 1107 DiscoverySelectors: []*metav1.LabelSelector{ 1108 { 1109 MatchLabels: map[string]string{ 1110 "pilot-discovery": "enabled", 1111 }, 1112 }, 1113 { 1114 MatchExpressions: []metav1.LabelSelectorRequirement{ 1115 { 1116 Key: "env", 1117 Operator: metav1.LabelSelectorOpIn, 1118 Values: []string{"test", "dev"}, 1119 }, 1120 }, 1121 }, 1122 }, 1123 }) 1124 1125 svc1 := &model.Service{ 1126 Hostname: kube.ServiceHostname("svc1", "nsA", defaultFakeDomainSuffix), 1127 DefaultAddress: "10.0.0.1", 1128 Ports: model.PortList{ 1129 &model.Port{ 1130 Name: "tcp-port", 1131 Port: 8080, 1132 Protocol: protocol.TCP, 1133 }, 1134 }, 1135 } 1136 svc2 := &model.Service{ 1137 Hostname: kube.ServiceHostname("svc2", "nsA", defaultFakeDomainSuffix), 1138 DefaultAddress: "10.0.0.1", 1139 Ports: model.PortList{ 1140 &model.Port{ 1141 Name: "tcp-port", 1142 Port: 8081, 1143 Protocol: protocol.TCP, 1144 }, 1145 }, 1146 } 1147 svc3 := &model.Service{ 1148 Hostname: kube.ServiceHostname("svc3", "nsB", defaultFakeDomainSuffix), 1149 DefaultAddress: "10.0.0.1", 1150 Ports: model.PortList{ 1151 &model.Port{ 1152 Name: "tcp-port", 1153 Port: 8082, 1154 Protocol: protocol.TCP, 1155 }, 1156 }, 1157 } 1158 svc4 := &model.Service{ 1159 Hostname: kube.ServiceHostname("svc4", "nsB", defaultFakeDomainSuffix), 1160 DefaultAddress: "10.0.0.1", 1161 Ports: model.PortList{ 1162 &model.Port{ 1163 Name: "tcp-port", 1164 Port: 8083, 1165 Protocol: protocol.TCP, 1166 }, 1167 }, 1168 } 1169 1170 controller, fx := NewFakeControllerWithOptions(t, FakeControllerOptions{ 1171 MeshWatcher: meshWatcher, 1172 }) 1173 1174 nsA := "nsA" 1175 nsB := "nsB" 1176 1177 // event handlers should only be triggered for services in namespaces selected for discovery 1178 createNamespace(t, controller.client.Kube(), nsA, map[string]string{"pilot-discovery": "enabled"}) 1179 createNamespace(t, controller.client.Kube(), nsB, map[string]string{}) 1180 1181 // service event handlers should trigger for svc1 and svc2 1182 createServiceWait(controller, "svc1", nsA, 1183 map[string]string{}, map[string]string{}, 1184 []int32{8080}, map[string]string{"test-app": "test-app-1"}, t) 1185 createServiceWait(controller, "svc2", nsA, 1186 map[string]string{}, map[string]string{}, 1187 []int32{8081}, map[string]string{"test-app": "test-app-2"}, t) 1188 // service event handlers should not trigger for svc3 and svc4 1189 createService(controller, "svc3", nsB, 1190 map[string]string{}, map[string]string{}, 1191 []int32{8082}, map[string]string{"test-app": "test-app-3"}, t) 1192 createService(controller, "svc4", nsB, 1193 map[string]string{}, map[string]string{}, 1194 []int32{8083}, map[string]string{"test-app": "test-app-4"}, t) 1195 1196 expectedSvcList := []*model.Service{svc1, svc2} 1197 eventually(t, func() bool { 1198 svcList := controller.Services() 1199 return servicesEqual(svcList, expectedSvcList) 1200 }) 1201 1202 // test updating namespace with adding discovery label 1203 updateNamespace(t, controller.client.Kube(), nsB, map[string]string{"env": "test"}) 1204 // service event handlers should trigger for svc3 and svc4 1205 fx.WaitOrFail(t, "service") 1206 fx.WaitOrFail(t, "service") 1207 expectedSvcList = []*model.Service{svc1, svc2, svc3, svc4} 1208 eventually(t, func() bool { 1209 svcList := controller.Services() 1210 return servicesEqual(svcList, expectedSvcList) 1211 }) 1212 1213 // test updating namespace by removing discovery label 1214 updateNamespace(t, controller.client.Kube(), nsA, map[string]string{"pilot-discovery": "disabled"}) 1215 // service event handlers should trigger for svc1 and svc2 1216 fx.WaitOrFail(t, "service") 1217 fx.WaitOrFail(t, "service") 1218 expectedSvcList = []*model.Service{svc3, svc4} 1219 eventually(t, func() bool { 1220 svcList := controller.Services() 1221 return servicesEqual(svcList, expectedSvcList) 1222 }) 1223 } 1224 1225 func TestController_ServiceWithChangingDiscoveryNamespaces(t *testing.T) { 1226 svc1 := &model.Service{ 1227 Hostname: kube.ServiceHostname("svc1", "nsA", defaultFakeDomainSuffix), 1228 DefaultAddress: "10.0.0.1", 1229 Ports: model.PortList{ 1230 &model.Port{ 1231 Name: "tcp-port", 1232 Port: 8080, 1233 Protocol: protocol.TCP, 1234 }, 1235 }, 1236 } 1237 svc2 := &model.Service{ 1238 Hostname: kube.ServiceHostname("svc2", "nsA", defaultFakeDomainSuffix), 1239 DefaultAddress: "10.0.0.1", 1240 Ports: model.PortList{ 1241 &model.Port{ 1242 Name: "tcp-port", 1243 Port: 8081, 1244 Protocol: protocol.TCP, 1245 }, 1246 }, 1247 } 1248 svc3 := &model.Service{ 1249 Hostname: kube.ServiceHostname("svc3", "nsB", defaultFakeDomainSuffix), 1250 DefaultAddress: "10.0.0.1", 1251 Ports: model.PortList{ 1252 &model.Port{ 1253 Name: "tcp-port", 1254 Port: 8082, 1255 Protocol: protocol.TCP, 1256 }, 1257 }, 1258 } 1259 svc4 := &model.Service{ 1260 Hostname: kube.ServiceHostname("svc4", "nsC", defaultFakeDomainSuffix), 1261 DefaultAddress: "10.0.0.1", 1262 Ports: model.PortList{ 1263 &model.Port{ 1264 Name: "tcp-port", 1265 Port: 8083, 1266 Protocol: protocol.TCP, 1267 }, 1268 }, 1269 } 1270 1271 updateMeshConfig := func( 1272 meshConfig *meshconfig.MeshConfig, 1273 expectedSvcList []*model.Service, 1274 expectedNumSvcEvents int, 1275 testMeshWatcher *mesh.TestWatcher, 1276 fx *xdsfake.Updater, 1277 controller *FakeController, 1278 ) { 1279 // update meshConfig 1280 if err := testMeshWatcher.Update(meshConfig, time.Second*5); err != nil { 1281 t.Fatalf("%v", err) 1282 } 1283 1284 // assert firing of service events 1285 for i := 0; i < expectedNumSvcEvents; i++ { 1286 fx.WaitOrFail(t, "service") 1287 } 1288 1289 eventually(t, func() bool { 1290 svcList := controller.Services() 1291 return servicesEqual(svcList, expectedSvcList) 1292 }) 1293 } 1294 1295 meshWatcher := mesh.NewTestWatcher(&meshconfig.MeshConfig{}) 1296 1297 controller, fx := NewFakeControllerWithOptions(t, FakeControllerOptions{ 1298 MeshWatcher: meshWatcher, 1299 }) 1300 1301 nsA := "nsA" 1302 nsB := "nsB" 1303 nsC := "nsC" 1304 1305 createNamespace(t, controller.client.Kube(), nsA, map[string]string{"app": "foo"}) 1306 createNamespace(t, controller.client.Kube(), nsB, map[string]string{"app": "bar"}) 1307 createNamespace(t, controller.client.Kube(), nsC, map[string]string{"app": "baz"}) 1308 1309 // service event handlers should trigger for all svcs 1310 createServiceWait(controller, "svc1", nsA, 1311 map[string]string{}, map[string]string{}, 1312 []int32{8080}, map[string]string{"test-app": "test-app-1"}, t) 1313 createServiceWait(controller, "svc2", nsA, 1314 map[string]string{}, map[string]string{}, 1315 []int32{8081}, map[string]string{"test-app": "test-app-2"}, t) 1316 createServiceWait(controller, "svc3", nsB, 1317 map[string]string{}, map[string]string{}, 1318 []int32{8082}, map[string]string{"test-app": "test-app-3"}, t) 1319 createServiceWait(controller, "svc4", nsC, 1320 map[string]string{}, map[string]string{}, 1321 []int32{8083}, map[string]string{"test-app": "test-app-4"}, t) 1322 1323 expectedSvcList := []*model.Service{svc1, svc2, svc3, svc4} 1324 eventually(t, func() bool { 1325 svcList := controller.Services() 1326 return servicesEqual(svcList, expectedSvcList) 1327 }) 1328 1329 // restrict namespaces to nsA (expect 2 delete events for svc3 and svc4) 1330 updateMeshConfig( 1331 &meshconfig.MeshConfig{ 1332 DiscoverySelectors: []*metav1.LabelSelector{ 1333 { 1334 MatchLabels: map[string]string{ 1335 "app": "foo", 1336 }, 1337 }, 1338 }, 1339 }, 1340 []*model.Service{svc1, svc2}, 1341 2, 1342 meshWatcher, 1343 fx, 1344 controller, 1345 ) 1346 1347 // restrict namespaces to nsB (1 create event should trigger for nsB service and 2 delete events for nsA services) 1348 updateMeshConfig( 1349 &meshconfig.MeshConfig{ 1350 DiscoverySelectors: []*metav1.LabelSelector{ 1351 { 1352 MatchLabels: map[string]string{ 1353 "app": "bar", 1354 }, 1355 }, 1356 }, 1357 }, 1358 []*model.Service{svc3}, 1359 3, 1360 meshWatcher, 1361 fx, 1362 controller, 1363 ) 1364 1365 // expand namespaces to nsA and nsB with selectors (2 create events should trigger for nsA services) 1366 updateMeshConfig( 1367 &meshconfig.MeshConfig{ 1368 DiscoverySelectors: []*metav1.LabelSelector{ 1369 { 1370 MatchExpressions: []metav1.LabelSelectorRequirement{ 1371 { 1372 Key: "app", 1373 Operator: metav1.LabelSelectorOpIn, 1374 Values: []string{"foo", "bar"}, 1375 }, 1376 }, 1377 }, 1378 }, 1379 }, 1380 []*model.Service{svc1, svc2, svc3}, 1381 2, 1382 meshWatcher, 1383 fx, 1384 controller, 1385 ) 1386 1387 // permit all discovery namespaces by omitting discovery selectors (1 create event should trigger for the nsC service) 1388 updateMeshConfig( 1389 &meshconfig.MeshConfig{ 1390 DiscoverySelectors: []*metav1.LabelSelector{}, 1391 }, 1392 []*model.Service{svc1, svc2, svc3, svc4}, 1393 1, 1394 meshWatcher, 1395 fx, 1396 controller, 1397 ) 1398 } 1399 1400 func TestControllerResourceScoping(t *testing.T) { 1401 svc1 := &model.Service{ 1402 Hostname: kube.ServiceHostname("svc1", "nsA", defaultFakeDomainSuffix), 1403 DefaultAddress: "10.0.0.1", 1404 Ports: model.PortList{ 1405 &model.Port{ 1406 Name: "tcp-port", 1407 Port: 8080, 1408 Protocol: protocol.TCP, 1409 }, 1410 }, 1411 } 1412 svc2 := &model.Service{ 1413 Hostname: kube.ServiceHostname("svc2", "nsA", defaultFakeDomainSuffix), 1414 DefaultAddress: "10.0.0.1", 1415 Ports: model.PortList{ 1416 &model.Port{ 1417 Name: "tcp-port", 1418 Port: 8081, 1419 Protocol: protocol.TCP, 1420 }, 1421 }, 1422 } 1423 svc3 := &model.Service{ 1424 Hostname: kube.ServiceHostname("svc3", "nsB", defaultFakeDomainSuffix), 1425 DefaultAddress: "10.0.0.1", 1426 Ports: model.PortList{ 1427 &model.Port{ 1428 Name: "tcp-port", 1429 Port: 8082, 1430 Protocol: protocol.TCP, 1431 }, 1432 }, 1433 } 1434 svc4 := &model.Service{ 1435 Hostname: kube.ServiceHostname("svc4", "nsC", defaultFakeDomainSuffix), 1436 DefaultAddress: "10.0.0.1", 1437 Ports: model.PortList{ 1438 &model.Port{ 1439 Name: "tcp-port", 1440 Port: 8083, 1441 Protocol: protocol.TCP, 1442 }, 1443 }, 1444 } 1445 1446 updateMeshConfig := func( 1447 meshConfig *meshconfig.MeshConfig, 1448 expectedSvcList []*model.Service, 1449 expectedNumSvcEvents int, 1450 testMeshWatcher *mesh.TestWatcher, 1451 fx *xdsfake.Updater, 1452 controller *FakeController, 1453 ) { 1454 t.Helper() 1455 // update meshConfig 1456 if err := testMeshWatcher.Update(meshConfig, time.Second*5); err != nil { 1457 t.Fatalf("%v", err) 1458 } 1459 1460 // assert firing of service events 1461 for i := 0; i < expectedNumSvcEvents; i++ { 1462 fx.WaitOrFail(t, "service") 1463 } 1464 1465 eventually(t, func() bool { 1466 svcList := controller.Services() 1467 return servicesEqual(svcList, expectedSvcList) 1468 }) 1469 } 1470 1471 client := kubelib.NewFakeClient() 1472 t.Cleanup(client.Shutdown) 1473 meshWatcher := mesh.NewTestWatcher(&meshconfig.MeshConfig{}) 1474 1475 nsA := "nsA" 1476 nsB := "nsB" 1477 nsC := "nsC" 1478 1479 createNamespace(t, client.Kube(), nsA, map[string]string{"app": "foo"}) 1480 createNamespace(t, client.Kube(), nsB, map[string]string{"app": "bar"}) 1481 createNamespace(t, client.Kube(), nsC, map[string]string{"app": "baz"}) 1482 1483 controller, fx := NewFakeControllerWithOptions(t, FakeControllerOptions{ 1484 Client: client, 1485 MeshWatcher: meshWatcher, 1486 }) 1487 1488 // service event handlers should trigger for all svcs 1489 createServiceWait(controller, "svc1", nsA, 1490 map[string]string{}, 1491 map[string]string{}, 1492 []int32{8080}, map[string]string{"test-app": "test-app-1"}, t) 1493 1494 createServiceWait(controller, "svc2", nsA, 1495 map[string]string{}, 1496 map[string]string{}, 1497 []int32{8081}, map[string]string{"test-app": "test-app-2"}, t) 1498 1499 createServiceWait(controller, "svc3", nsB, 1500 map[string]string{}, 1501 map[string]string{}, 1502 []int32{8082}, map[string]string{"test-app": "test-app-3"}, t) 1503 1504 createServiceWait(controller, "svc4", nsC, 1505 map[string]string{}, 1506 map[string]string{}, 1507 []int32{8083}, map[string]string{"test-app": "test-app-4"}, t) 1508 1509 expectedSvcList := []*model.Service{svc1, svc2, svc3, svc4} 1510 eventually(t, func() bool { 1511 svcList := controller.Services() 1512 return servicesEqual(svcList, expectedSvcList) 1513 }) 1514 1515 fx.Clear() 1516 1517 // restrict namespaces to nsA (expect 2 delete events for svc3 and svc4) 1518 updateMeshConfig( 1519 &meshconfig.MeshConfig{ 1520 DiscoverySelectors: []*metav1.LabelSelector{ 1521 { 1522 MatchLabels: map[string]string{ 1523 "app": "foo", 1524 }, 1525 }, 1526 }, 1527 }, 1528 []*model.Service{svc1, svc2}, 1529 2, 1530 meshWatcher, 1531 fx, 1532 controller, 1533 ) 1534 1535 // namespace nsB, nsC deselected 1536 fx.AssertEmpty(t, 0) 1537 1538 // create vs1 in nsA 1539 createVirtualService(controller, "vs1", nsA, map[string]string{}, t) 1540 1541 // create vs1 in nsB 1542 createVirtualService(controller, "vs2", nsB, map[string]string{}, t) 1543 1544 // expand namespaces to nsA and nsB with selectors (expect events svc3 and a full push event for nsB selected) 1545 updateMeshConfig( 1546 &meshconfig.MeshConfig{ 1547 DiscoverySelectors: []*metav1.LabelSelector{ 1548 { 1549 MatchExpressions: []metav1.LabelSelectorRequirement{ 1550 { 1551 Key: "app", 1552 Operator: metav1.LabelSelectorOpIn, 1553 Values: []string{"foo", "bar"}, 1554 }, 1555 }, 1556 }, 1557 }, 1558 }, 1559 []*model.Service{svc1, svc2, svc3}, 1560 1, 1561 meshWatcher, 1562 fx, 1563 controller, 1564 ) 1565 1566 // namespace nsB selected 1567 fx.AssertEmpty(t, 0) 1568 } 1569 1570 func TestEndpoints_WorkloadInstances(t *testing.T) { 1571 ctl, _ := NewFakeControllerWithOptions(t, FakeControllerOptions{}) 1572 1573 createServiceWithTargetPorts(ctl, "ratings", "bookinfo-ratings", 1574 map[string]string{ 1575 annotation.AlphaKubernetesServiceAccounts.Name: "ratings", 1576 annotation.AlphaCanonicalServiceAccounts.Name: "ratings@gserviceaccount2.com", 1577 }, 1578 []corev1.ServicePort{ 1579 { 1580 Name: "http-port", 1581 Port: 8080, 1582 Protocol: "TCP", 1583 TargetPort: intstr.IntOrString{Type: intstr.String, StrVal: "http"}, 1584 }, 1585 }, 1586 map[string]string{"app": "ratings"}, t) 1587 1588 wiRatings1 := &model.WorkloadInstance{ 1589 Name: "ratings-1", 1590 Namespace: "bookinfo-ratings", 1591 Endpoint: &model.IstioEndpoint{ 1592 Labels: labels.Instance{"app": "ratings"}, 1593 Address: "2.2.2.2", 1594 EndpointPort: 8081, // should be ignored since it doesn't define PortMap 1595 }, 1596 } 1597 1598 wiRatings2 := &model.WorkloadInstance{ 1599 Name: "ratings-2", 1600 Namespace: "bookinfo-ratings", 1601 Endpoint: &model.IstioEndpoint{ 1602 Labels: labels.Instance{"app": "ratings"}, 1603 Address: "2.2.2.2", 1604 }, 1605 PortMap: map[string]uint32{ 1606 "http": 8082, // should be used 1607 }, 1608 } 1609 1610 wiRatings3 := &model.WorkloadInstance{ 1611 Name: "ratings-3", 1612 Namespace: "bookinfo-ratings", 1613 Endpoint: &model.IstioEndpoint{ 1614 Labels: labels.Instance{"app": "ratings"}, 1615 Address: "2.2.2.2", 1616 }, 1617 PortMap: map[string]uint32{ 1618 "http": 8083, // should be used 1619 }, 1620 } 1621 1622 for _, wi := range []*model.WorkloadInstance{wiRatings1, wiRatings2, wiRatings3} { 1623 ctl.workloadInstanceHandler(wi, model.EventAdd) // simulate adding a workload entry 1624 } 1625 1626 // get service object 1627 svcs := ctl.Services() 1628 if len(svcs) != 1 { 1629 t.Fatalf("failed to get services (%v)", svcs) 1630 } 1631 1632 endpoints := GetEndpoints(svcs[0], ctl.Endpoints) 1633 1634 want := []string{"2.2.2.2:8082", "2.2.2.2:8083"} // expect both WorkloadEntries even though they have the same IP 1635 1636 got := make([]string, 0, len(endpoints)) 1637 for _, instance := range endpoints { 1638 got = append(got, net.JoinHostPort(instance.Address, strconv.Itoa(int(instance.EndpointPort)))) 1639 } 1640 sort.Strings(got) 1641 1642 assert.Equal(t, want, got) 1643 } 1644 1645 func TestExternalNameServiceInstances(t *testing.T) { 1646 t.Run("alias", func(t *testing.T) { 1647 test.SetForTest(t, &features.EnableExternalNameAlias, true) 1648 controller, fx := NewFakeControllerWithOptions(t, FakeControllerOptions{}) 1649 createExternalNameService(controller, "svc5", "nsA", 1650 []int32{1, 2, 3}, "foo.co", t, fx) 1651 1652 converted := controller.Services() 1653 assert.Equal(t, len(converted), 1) 1654 1655 eps := GetEndpointsForPort(converted[0], controller.Endpoints, 1) 1656 assert.Equal(t, len(eps), 0) 1657 assert.Equal(t, converted[0].Attributes, model.ServiceAttributes{ 1658 ServiceRegistry: "Kubernetes", 1659 Name: "svc5", 1660 Namespace: "nsA", 1661 Labels: nil, 1662 ExportTo: nil, 1663 LabelSelectors: nil, 1664 Aliases: nil, 1665 ClusterExternalAddresses: nil, 1666 ClusterExternalPorts: nil, 1667 K8sAttributes: model.K8sAttributes{ 1668 Type: string(corev1.ServiceTypeExternalName), 1669 ExternalName: "foo.co", 1670 }, 1671 }) 1672 }) 1673 t.Run("no alias", func(t *testing.T) { 1674 test.SetForTest(t, &features.EnableExternalNameAlias, false) 1675 controller, fx := NewFakeControllerWithOptions(t, FakeControllerOptions{}) 1676 createExternalNameService(controller, "svc5", "nsA", 1677 []int32{1, 2, 3}, "foo.co", t, fx) 1678 1679 converted := controller.Services() 1680 assert.Equal(t, len(converted), 1) 1681 eps := GetEndpointsForPort(converted[0], controller.Endpoints, 1) 1682 assert.Equal(t, len(eps), 1) 1683 assert.Equal(t, eps[0], &model.IstioEndpoint{ 1684 Address: "foo.co", 1685 ServicePortName: "tcp-port-1", 1686 EndpointPort: 1, 1687 DiscoverabilityPolicy: model.AlwaysDiscoverable, 1688 }) 1689 }) 1690 } 1691 1692 func TestController_ExternalNameService(t *testing.T) { 1693 test.SetForTest(t, &features.EnableExternalNameAlias, false) 1694 deleteWg := sync.WaitGroup{} 1695 controller, fx := NewFakeControllerWithOptions(t, FakeControllerOptions{ 1696 ServiceHandler: func(_, _ *model.Service, e model.Event) { 1697 if e == model.EventDelete { 1698 deleteWg.Done() 1699 } 1700 }, 1701 }) 1702 1703 k8sSvcs := []*corev1.Service{ 1704 createExternalNameService(controller, "svc1", "nsA", 1705 []int32{8080}, "test-app-1.test.svc."+defaultFakeDomainSuffix, t, fx), 1706 createExternalNameService(controller, "svc2", "nsA", 1707 []int32{8081}, "test-app-2.test.svc."+defaultFakeDomainSuffix, t, fx), 1708 createExternalNameService(controller, "svc3", "nsA", 1709 []int32{8082}, "test-app-3.test.pod."+defaultFakeDomainSuffix, t, fx), 1710 createExternalNameService(controller, "svc4", "nsA", 1711 []int32{8083}, "g.co", t, fx), 1712 } 1713 1714 expectedSvcList := []*model.Service{ 1715 { 1716 Hostname: kube.ServiceHostname("svc1", "nsA", defaultFakeDomainSuffix), 1717 Ports: model.PortList{ 1718 &model.Port{ 1719 Name: "tcp-port-8080", 1720 Port: 8080, 1721 Protocol: protocol.TCP, 1722 }, 1723 }, 1724 MeshExternal: true, 1725 Resolution: model.DNSLB, 1726 }, 1727 { 1728 Hostname: kube.ServiceHostname("svc2", "nsA", defaultFakeDomainSuffix), 1729 Ports: model.PortList{ 1730 &model.Port{ 1731 Name: "tcp-port-8081", 1732 Port: 8081, 1733 Protocol: protocol.TCP, 1734 }, 1735 }, 1736 MeshExternal: true, 1737 Resolution: model.DNSLB, 1738 }, 1739 { 1740 Hostname: kube.ServiceHostname("svc3", "nsA", defaultFakeDomainSuffix), 1741 Ports: model.PortList{ 1742 &model.Port{ 1743 Name: "tcp-port-8082", 1744 Port: 8082, 1745 Protocol: protocol.TCP, 1746 }, 1747 }, 1748 MeshExternal: true, 1749 Resolution: model.DNSLB, 1750 }, 1751 { 1752 Hostname: kube.ServiceHostname("svc4", "nsA", defaultFakeDomainSuffix), 1753 Ports: model.PortList{ 1754 &model.Port{ 1755 Name: "tcp-port-8083", 1756 Port: 8083, 1757 Protocol: protocol.TCP, 1758 }, 1759 }, 1760 MeshExternal: true, 1761 Resolution: model.DNSLB, 1762 }, 1763 } 1764 1765 svcList := controller.Services() 1766 if len(svcList) != len(expectedSvcList) { 1767 t.Fatalf("Expecting %d service but got %d\r\n", len(expectedSvcList), len(svcList)) 1768 } 1769 for i, exp := range expectedSvcList { 1770 if exp.Hostname != svcList[i].Hostname { 1771 t.Fatalf("got hostname of %dst service, got:\n%#v\nwanted:\n%#v\n", i+1, svcList[i].Hostname, exp.Hostname) 1772 } 1773 if !reflect.DeepEqual(exp.Ports, svcList[i].Ports) { 1774 t.Fatalf("got ports of %dst service, got:\n%#v\nwanted:\n%#v\n", i+1, svcList[i].Ports, exp.Ports) 1775 } 1776 if svcList[i].MeshExternal != exp.MeshExternal { 1777 t.Fatalf("i=%v, MeshExternal==%v, should be %v: externalName='%s'", i+1, exp.MeshExternal, svcList[i].MeshExternal, k8sSvcs[i].Spec.ExternalName) 1778 } 1779 if svcList[i].Resolution != exp.Resolution { 1780 t.Fatalf("i=%v, Resolution=='%v', should be '%v'", i+1, svcList[i].Resolution, exp.Resolution) 1781 } 1782 endpoints := GetEndpoints(svcList[i], controller.Endpoints) 1783 assert.Equal(t, len(endpoints), 1) 1784 assert.Equal(t, endpoints[0].Address, k8sSvcs[i].Spec.ExternalName) 1785 } 1786 1787 deleteWg.Add(len(k8sSvcs)) 1788 for _, s := range k8sSvcs { 1789 deleteExternalNameService(controller, s.Name, s.Namespace, t, fx) 1790 } 1791 deleteWg.Wait() 1792 1793 svcList = controller.Services() 1794 if len(svcList) != 0 { 1795 t.Fatalf("Should have 0 services at this point") 1796 } 1797 for _, exp := range expectedSvcList { 1798 endpoints := GetEndpoints(exp, controller.Endpoints) 1799 assert.Equal(t, len(endpoints), 0) 1800 } 1801 } 1802 1803 func createEndpoints(t *testing.T, controller *FakeController, name, namespace string, 1804 portNames, ips []string, refs []*corev1.ObjectReference, labels map[string]string, 1805 ) { 1806 if labels == nil { 1807 labels = make(map[string]string) 1808 } 1809 // Add the reference to the service. Used by EndpointSlice logic only. 1810 labels[discovery.LabelServiceName] = name 1811 1812 if refs == nil { 1813 refs = make([]*corev1.ObjectReference, len(ips)) 1814 } 1815 var portNum int32 = 1001 1816 eas := make([]corev1.EndpointAddress, 0) 1817 for i, ip := range ips { 1818 eas = append(eas, corev1.EndpointAddress{IP: ip, TargetRef: refs[i]}) 1819 } 1820 1821 eps := make([]corev1.EndpointPort, 0) 1822 for _, name := range portNames { 1823 eps = append(eps, corev1.EndpointPort{Name: name, Port: portNum}) 1824 } 1825 1826 endpoint := &corev1.Endpoints{ 1827 ObjectMeta: metav1.ObjectMeta{ 1828 Name: name, 1829 Namespace: namespace, 1830 Labels: labels, 1831 }, 1832 Subsets: []corev1.EndpointSubset{{ 1833 Addresses: eas, 1834 Ports: eps, 1835 }}, 1836 } 1837 clienttest.NewWriter[*corev1.Endpoints](t, controller.client).CreateOrUpdate(endpoint) 1838 1839 // Create endpoint slice as well 1840 esps := make([]discovery.EndpointPort, 0) 1841 for _, name := range portNames { 1842 n := name // Create a stable reference to take the pointer from 1843 esps = append(esps, discovery.EndpointPort{Name: &n, Port: &portNum}) 1844 } 1845 1846 sliceEndpoint := make([]discovery.Endpoint, 0, len(ips)) 1847 for i, ip := range ips { 1848 sliceEndpoint = append(sliceEndpoint, discovery.Endpoint{ 1849 Addresses: []string{ip}, 1850 TargetRef: refs[i], 1851 }) 1852 } 1853 endpointSlice := &discovery.EndpointSlice{ 1854 ObjectMeta: metav1.ObjectMeta{ 1855 Name: name, 1856 Namespace: namespace, 1857 Labels: labels, 1858 }, 1859 Endpoints: sliceEndpoint, 1860 Ports: esps, 1861 } 1862 clienttest.NewWriter[*discovery.EndpointSlice](t, controller.client).CreateOrUpdate(endpointSlice) 1863 } 1864 1865 func updateEndpoints(controller *FakeController, name, namespace string, portNames, ips []string, t *testing.T) { 1866 var portNum int32 = 1001 1867 eas := make([]corev1.EndpointAddress, 0) 1868 for _, ip := range ips { 1869 eas = append(eas, corev1.EndpointAddress{IP: ip}) 1870 } 1871 1872 eps := make([]corev1.EndpointPort, 0) 1873 for _, name := range portNames { 1874 eps = append(eps, corev1.EndpointPort{Name: name, Port: portNum}) 1875 } 1876 1877 endpoint := &corev1.Endpoints{ 1878 ObjectMeta: metav1.ObjectMeta{ 1879 Name: name, 1880 Namespace: namespace, 1881 }, 1882 Subsets: []corev1.EndpointSubset{{ 1883 Addresses: eas, 1884 Ports: eps, 1885 }}, 1886 } 1887 if _, err := controller.client.Kube().CoreV1().Endpoints(namespace).Update(context.TODO(), endpoint, metav1.UpdateOptions{}); err != nil { 1888 t.Fatalf("failed to update endpoints %s in namespace %s (error %v)", name, namespace, err) 1889 } 1890 1891 // Update endpoint slice as well 1892 esps := make([]discovery.EndpointPort, 0) 1893 for i := range portNames { 1894 esps = append(esps, discovery.EndpointPort{Name: &portNames[i], Port: &portNum}) 1895 } 1896 endpointSlice := &discovery.EndpointSlice{ 1897 ObjectMeta: metav1.ObjectMeta{ 1898 Name: name, 1899 Namespace: namespace, 1900 Labels: map[string]string{ 1901 discovery.LabelServiceName: name, 1902 }, 1903 }, 1904 Endpoints: []discovery.Endpoint{ 1905 { 1906 Addresses: ips, 1907 }, 1908 }, 1909 Ports: esps, 1910 } 1911 if _, err := controller.client.Kube().DiscoveryV1().EndpointSlices(namespace).Update(context.TODO(), endpointSlice, metav1.UpdateOptions{}); err != nil { 1912 t.Errorf("failed to create endpoint slice %s in namespace %s (error %v)", name, namespace, err) 1913 } 1914 } 1915 1916 func createServiceWithTargetPorts(controller *FakeController, name, namespace string, annotations map[string]string, 1917 svcPorts []corev1.ServicePort, selector map[string]string, t *testing.T, 1918 ) { 1919 service := &corev1.Service{ 1920 ObjectMeta: metav1.ObjectMeta{ 1921 Name: name, 1922 Namespace: namespace, 1923 Annotations: annotations, 1924 }, 1925 Spec: corev1.ServiceSpec{ 1926 ClusterIP: "10.0.0.1", // FIXME: generate? 1927 Ports: svcPorts, 1928 Selector: selector, 1929 Type: corev1.ServiceTypeClusterIP, 1930 }, 1931 } 1932 1933 clienttest.Wrap(t, controller.services).Create(service) 1934 controller.opts.XDSUpdater.(*xdsfake.Updater).WaitOrFail(t, "service") 1935 } 1936 1937 func createServiceWait(controller *FakeController, name, namespace string, labels, annotations map[string]string, 1938 ports []int32, selector map[string]string, t *testing.T, 1939 ) { 1940 t.Helper() 1941 createService(controller, name, namespace, labels, annotations, ports, selector, t) 1942 controller.opts.XDSUpdater.(*xdsfake.Updater).WaitOrFail(t, "service") 1943 } 1944 1945 func createService(controller *FakeController, name, namespace string, labels, annotations map[string]string, 1946 ports []int32, selector map[string]string, t *testing.T, 1947 ) { 1948 service := generateService(name, namespace, labels, annotations, ports, selector, "10.0.0.1") 1949 clienttest.Wrap(t, controller.services).CreateOrUpdate(service) 1950 } 1951 1952 func generateService(name, namespace string, labels, annotations map[string]string, 1953 ports []int32, selector map[string]string, ip string, 1954 ) *corev1.Service { 1955 svcPorts := make([]corev1.ServicePort, 0) 1956 for _, p := range ports { 1957 svcPorts = append(svcPorts, corev1.ServicePort{ 1958 Name: "tcp-port", 1959 Port: p, 1960 Protocol: "http", 1961 }) 1962 } 1963 1964 return &corev1.Service{ 1965 ObjectMeta: metav1.ObjectMeta{ 1966 Name: name, 1967 Namespace: namespace, 1968 Annotations: annotations, 1969 Labels: labels, 1970 }, 1971 Spec: corev1.ServiceSpec{ 1972 ClusterIP: ip, 1973 Ports: svcPorts, 1974 Selector: selector, 1975 Type: corev1.ServiceTypeClusterIP, 1976 }, 1977 } 1978 } 1979 1980 func createVirtualService(controller *FakeController, name, namespace string, 1981 annotations map[string]string, 1982 t *testing.T, 1983 ) { 1984 vs := &v1alpha3.VirtualService{ 1985 ObjectMeta: metav1.ObjectMeta{ 1986 Name: name, 1987 Namespace: namespace, 1988 Annotations: annotations, 1989 }, 1990 } 1991 1992 clienttest.NewWriter[*v1alpha3.VirtualService](t, controller.client).Create(vs) 1993 } 1994 1995 func getService(controller *FakeController, name, namespace string, t *testing.T) *corev1.Service { 1996 svc, err := controller.client.Kube().CoreV1().Services(namespace).Get(context.TODO(), name, metav1.GetOptions{}) 1997 if err != nil { 1998 t.Fatalf("Cannot get service %s in namespace %s (error: %v)", name, namespace, err) 1999 } 2000 return svc 2001 } 2002 2003 func updateService(controller *FakeController, svc *corev1.Service, t *testing.T) *corev1.Service { 2004 svcUpdated, err := controller.client.Kube().CoreV1().Services(svc.Namespace).Update(context.TODO(), svc, metav1.UpdateOptions{}) 2005 if err != nil { 2006 t.Fatalf("Cannot update service %s in namespace %s (error: %v)", svc.Name, svc.Namespace, err) 2007 } 2008 return svcUpdated 2009 } 2010 2011 func createServiceWithoutClusterIP(controller *FakeController, name, namespace string, annotations map[string]string, 2012 ports []int32, selector map[string]string, t *testing.T, 2013 ) { 2014 svcPorts := make([]corev1.ServicePort, 0) 2015 for _, p := range ports { 2016 svcPorts = append(svcPorts, corev1.ServicePort{ 2017 Name: "tcp-port", 2018 Port: p, 2019 Protocol: "http", 2020 }) 2021 } 2022 service := &corev1.Service{ 2023 ObjectMeta: metav1.ObjectMeta{ 2024 Name: name, 2025 Namespace: namespace, 2026 Annotations: annotations, 2027 }, 2028 Spec: corev1.ServiceSpec{ 2029 ClusterIP: corev1.ClusterIPNone, 2030 Ports: svcPorts, 2031 Selector: selector, 2032 Type: corev1.ServiceTypeClusterIP, 2033 }, 2034 } 2035 2036 clienttest.Wrap(t, controller.services).Create(service) 2037 } 2038 2039 // nolint: unparam 2040 func createExternalNameService(controller *FakeController, name, namespace string, 2041 ports []int32, externalName string, t *testing.T, xdsEvents *xdsfake.Updater, 2042 ) *corev1.Service { 2043 svcPorts := make([]corev1.ServicePort, 0) 2044 for _, p := range ports { 2045 svcPorts = append(svcPorts, corev1.ServicePort{ 2046 Name: fmt.Sprintf("tcp-port-%d", p), 2047 Port: p, 2048 Protocol: "http", 2049 }) 2050 } 2051 service := &corev1.Service{ 2052 ObjectMeta: metav1.ObjectMeta{ 2053 Name: name, 2054 Namespace: namespace, 2055 }, 2056 Spec: corev1.ServiceSpec{ 2057 Ports: svcPorts, 2058 Type: corev1.ServiceTypeExternalName, 2059 ExternalName: externalName, 2060 }, 2061 } 2062 2063 clienttest.Wrap(t, controller.services).Create(service) 2064 if features.EnableExternalNameAlias { 2065 xdsEvents.MatchOrFail(t, xdsfake.Event{Type: "service"}) 2066 } else { 2067 xdsEvents.MatchOrFail(t, xdsfake.Event{Type: "service"}, xdsfake.Event{Type: "eds cache"}) 2068 } 2069 return service 2070 } 2071 2072 func deleteExternalNameService(controller *FakeController, name, namespace string, t *testing.T, xdsEvents *xdsfake.Updater) { 2073 clienttest.Wrap(t, controller.services).Delete(name, namespace) 2074 xdsEvents.WaitOrFail(t, "service") 2075 } 2076 2077 func servicesEqual(svcList, expectedSvcList []*model.Service) bool { 2078 if len(svcList) != len(expectedSvcList) { 2079 return false 2080 } 2081 for i, exp := range expectedSvcList { 2082 if exp.Hostname != svcList[i].Hostname { 2083 return false 2084 } 2085 if exp.DefaultAddress != svcList[i].DefaultAddress { 2086 return false 2087 } 2088 if !reflect.DeepEqual(exp.Ports, svcList[i].Ports) { 2089 return false 2090 } 2091 } 2092 return true 2093 } 2094 2095 func addPods(t *testing.T, controller *FakeController, fx *xdsfake.Updater, pods ...*corev1.Pod) { 2096 pc := clienttest.Wrap(t, controller.podsClient) 2097 for _, pod := range pods { 2098 newPod := pc.CreateOrUpdate(pod) 2099 setPodReady(newPod) 2100 // Apiserver doesn't allow Create/Update to modify the pod status. Creating doesn't result in 2101 // events - since PodIP will be "". 2102 newPod.Status.PodIP = pod.Status.PodIP 2103 newPod.Status.Phase = corev1.PodRunning 2104 pc.UpdateStatus(newPod) 2105 waitForPod(t, controller, pod.Status.PodIP) 2106 // pod first time occur will trigger proxy push 2107 fx.WaitOrFail(t, "proxy") 2108 } 2109 } 2110 2111 func setPodReady(pod *corev1.Pod) { 2112 pod.Status.Conditions = []corev1.PodCondition{ 2113 { 2114 Type: corev1.PodReady, 2115 Status: corev1.ConditionTrue, 2116 LastTransitionTime: metav1.Now(), 2117 }, 2118 } 2119 } 2120 2121 func generatePod(ip, name, namespace, saName, node string, labels map[string]string, annotations map[string]string) *corev1.Pod { 2122 automount := false 2123 return &corev1.Pod{ 2124 ObjectMeta: metav1.ObjectMeta{ 2125 Name: name, 2126 Labels: labels, 2127 Annotations: annotations, 2128 Namespace: namespace, 2129 }, 2130 Spec: corev1.PodSpec{ 2131 ServiceAccountName: saName, 2132 NodeName: node, 2133 AutomountServiceAccountToken: &automount, 2134 // Validation requires this 2135 Containers: []corev1.Container{ 2136 { 2137 Name: "test", 2138 Image: "ununtu", 2139 }, 2140 }, 2141 }, 2142 // The cache controller uses this as key, required by our impl. 2143 Status: corev1.PodStatus{ 2144 Conditions: []corev1.PodCondition{ 2145 { 2146 Type: corev1.PodReady, 2147 Status: corev1.ConditionTrue, 2148 LastTransitionTime: metav1.Now(), 2149 }, 2150 }, 2151 PodIP: ip, 2152 HostIP: ip, 2153 PodIPs: []corev1.PodIP{ 2154 { 2155 IP: ip, 2156 }, 2157 }, 2158 Phase: corev1.PodRunning, 2159 }, 2160 } 2161 } 2162 2163 func generateNode(name string, labels map[string]string) *corev1.Node { 2164 return &corev1.Node{ 2165 TypeMeta: metav1.TypeMeta{ 2166 Kind: "Node", 2167 APIVersion: "v1", 2168 }, 2169 ObjectMeta: metav1.ObjectMeta{ 2170 Name: name, 2171 Labels: labels, 2172 }, 2173 } 2174 } 2175 2176 func addNodes(t *testing.T, controller *FakeController, nodes ...*corev1.Node) { 2177 for _, node := range nodes { 2178 clienttest.Wrap(t, controller.nodes).CreateOrUpdate(node) 2179 waitForNode(t, controller, node.Name) 2180 } 2181 } 2182 2183 func TestEndpointUpdate(t *testing.T) { 2184 controller, fx := NewFakeControllerWithOptions(t, FakeControllerOptions{}) 2185 2186 pod1 := generatePod("128.0.0.1", "pod1", "nsA", "", "node1", map[string]string{"app": "prod-app"}, map[string]string{}) 2187 pods := []*corev1.Pod{pod1} 2188 addPods(t, controller, fx, pods...) 2189 2190 // 1. incremental eds for normal service endpoint update 2191 createServiceWait(controller, "svc1", "nsa", nil, nil, 2192 []int32{8080}, map[string]string{"app": "prod-app"}, t) 2193 2194 // Endpoints are generated by Kubernetes from pod labels and service selectors. 2195 // Here we manually create them for mocking purpose. 2196 svc1Ips := []string{"128.0.0.1"} 2197 portNames := []string{"tcp-port"} 2198 // Create 1 endpoint that refers to a pod in the same namespace. 2199 createEndpoints(t, controller, "svc1", "nsa", portNames, svc1Ips, nil, nil) 2200 fx.WaitOrFail(t, "eds") 2201 2202 // delete normal service 2203 clienttest.Wrap(t, controller.services).Delete("svc1", "nsa") 2204 fx.WaitOrFail(t, "service") 2205 2206 // 2. full xds push request for headless service endpoint update 2207 2208 // create a headless service 2209 createServiceWithoutClusterIP(controller, "svc1", "nsa", nil, 2210 []int32{8080}, map[string]string{"app": "prod-app"}, t) 2211 fx.WaitOrFail(t, "service") 2212 2213 // Create 1 endpoint that refers to a pod in the same namespace. 2214 svc1Ips = append(svc1Ips, "128.0.0.2") 2215 updateEndpoints(controller, "svc1", "nsa", portNames, svc1Ips, t) 2216 host := string(kube.ServiceHostname("svc1", "nsa", controller.opts.DomainSuffix)) 2217 fx.MatchOrFail(t, xdsfake.Event{Type: "xds full", ID: host}) 2218 } 2219 2220 // Validates that when Pilot sees Endpoint before the corresponding Pod, it triggers endpoint event on pod event. 2221 func TestEndpointUpdateBeforePodUpdate(t *testing.T) { 2222 controller, fx := NewFakeControllerWithOptions(t, FakeControllerOptions{}) 2223 2224 addNodes(t, controller, generateNode("node1", map[string]string{NodeZoneLabel: "zone1", NodeRegionLabel: "region1", label.TopologySubzone.Name: "subzone1"})) 2225 // Setup help functions to make the test more explicit 2226 addPod := func(name, ip string) { 2227 pod := generatePod(ip, name, "nsA", name, "node1", map[string]string{"app": "prod-app"}, map[string]string{}) 2228 addPods(t, controller, fx, pod) 2229 } 2230 deletePod := func(name, ip string) { 2231 if err := controller.client.Kube().CoreV1().Pods("nsA").Delete(context.TODO(), name, metav1.DeleteOptions{}); err != nil { 2232 t.Fatal(err) 2233 } 2234 retry.UntilSuccessOrFail(t, func() error { 2235 controller.pods.RLock() 2236 defer controller.pods.RUnlock() 2237 if _, ok := controller.pods.podsByIP[ip]; ok { 2238 return fmt.Errorf("pod still present") 2239 } 2240 return nil 2241 }, retry.Timeout(time.Second)) 2242 } 2243 addService := func(name string) { 2244 // create service 2245 createServiceWait(controller, name, "nsA", nil, nil, 2246 []int32{8080}, map[string]string{"app": "prod-app"}, t) 2247 } 2248 addEndpoint := func(svcName string, ips []string, pods []string) { 2249 var refs []*corev1.ObjectReference 2250 for _, pod := range pods { 2251 if pod == "" { 2252 refs = append(refs, nil) 2253 } else { 2254 refs = append(refs, &corev1.ObjectReference{ 2255 Kind: "Pod", 2256 Namespace: "nsA", 2257 Name: pod, 2258 }) 2259 } 2260 } 2261 createEndpoints(t, controller, svcName, "nsA", []string{"tcp-port"}, ips, refs, nil) 2262 } 2263 assertEndpointsEvent := func(ips []string, pods []string) { 2264 t.Helper() 2265 ev := fx.WaitOrFail(t, "eds") 2266 var gotIps []string 2267 for _, e := range ev.Endpoints { 2268 gotIps = append(gotIps, e.Address) 2269 } 2270 var gotSA []string 2271 var expectedSa []string 2272 for _, e := range pods { 2273 if e == "" { 2274 expectedSa = append(expectedSa, "") 2275 } else { 2276 expectedSa = append(expectedSa, "spiffe://cluster.local/ns/nsA/sa/"+e) 2277 } 2278 } 2279 2280 for _, e := range ev.Endpoints { 2281 gotSA = append(gotSA, e.ServiceAccount) 2282 } 2283 if !reflect.DeepEqual(gotIps, ips) { 2284 t.Fatalf("expected ips %v, got %v", ips, gotIps) 2285 } 2286 if !reflect.DeepEqual(gotSA, expectedSa) { 2287 t.Fatalf("expected SAs %v, got %v", expectedSa, gotSA) 2288 } 2289 } 2290 assertPendingResync := func(expected int) { 2291 t.Helper() 2292 retry.UntilSuccessOrFail(t, func() error { 2293 controller.pods.RLock() 2294 defer controller.pods.RUnlock() 2295 if len(controller.pods.needResync) != expected { 2296 return fmt.Errorf("expected %d pods needing resync, got %d", expected, len(controller.pods.needResync)) 2297 } 2298 return nil 2299 }, retry.Timeout(time.Second)) 2300 } 2301 2302 // standard ordering 2303 addService("svc") 2304 addPod("pod1", "172.0.1.1") 2305 addEndpoint("svc", []string{"172.0.1.1"}, []string{"pod1"}) 2306 assertEndpointsEvent([]string{"172.0.1.1"}, []string{"pod1"}) 2307 fx.Clear() 2308 2309 // Create the endpoint, then later add the pod. Should eventually get an update for the endpoint 2310 addEndpoint("svc", []string{"172.0.1.1", "172.0.1.2"}, []string{"pod1", "pod2"}) 2311 assertEndpointsEvent([]string{"172.0.1.1"}, []string{"pod1"}) 2312 fx.Clear() 2313 addPod("pod2", "172.0.1.2") 2314 assertEndpointsEvent([]string{"172.0.1.1", "172.0.1.2"}, []string{"pod1", "pod2"}) 2315 fx.Clear() 2316 2317 // Create the endpoint without a pod reference. We should see it immediately 2318 addEndpoint("svc", []string{"172.0.1.1", "172.0.1.2", "172.0.1.3"}, []string{"pod1", "pod2", ""}) 2319 assertEndpointsEvent([]string{"172.0.1.1", "172.0.1.2", "172.0.1.3"}, []string{"pod1", "pod2", ""}) 2320 fx.Clear() 2321 2322 // Delete a pod before the endpoint 2323 addEndpoint("svc", []string{"172.0.1.1"}, []string{"pod1"}) 2324 deletePod("pod2", "172.0.1.2") 2325 assertEndpointsEvent([]string{"172.0.1.1"}, []string{"pod1"}) 2326 fx.Clear() 2327 2328 // add another service 2329 addService("other") 2330 // Add endpoints for the new service, and the old one. Both should be missing the last IP 2331 addEndpoint("other", []string{"172.0.1.1", "172.0.1.2"}, []string{"pod1", "pod2"}) 2332 addEndpoint("svc", []string{"172.0.1.1", "172.0.1.2"}, []string{"pod1", "pod2"}) 2333 assertEndpointsEvent([]string{"172.0.1.1"}, []string{"pod1"}) 2334 assertEndpointsEvent([]string{"172.0.1.1"}, []string{"pod1"}) 2335 fx.Clear() 2336 // Add the pod, expect the endpoints update for both 2337 addPod("pod2", "172.0.1.2") 2338 assertEndpointsEvent([]string{"172.0.1.1", "172.0.1.2"}, []string{"pod1", "pod2"}) 2339 assertEndpointsEvent([]string{"172.0.1.1", "172.0.1.2"}, []string{"pod1", "pod2"}) 2340 2341 // Check for memory leaks 2342 assertPendingResync(0) 2343 addEndpoint("svc", []string{"172.0.1.1", "172.0.1.2", "172.0.1.3"}, []string{"pod1", "pod2", "pod3"}) 2344 // This is really an implementation detail here - but checking to sanity check our test 2345 assertPendingResync(1) 2346 // Remove the endpoint again, with no pod events in between. Should have no memory leaks 2347 addEndpoint("svc", []string{"172.0.1.1", "172.0.1.2"}, []string{"pod1", "pod2"}) 2348 // TODO this case would leak 2349 // assertPendingResync(0) 2350 2351 // completely remove the endpoint 2352 addEndpoint("svc", []string{"172.0.1.1", "172.0.1.2", "172.0.1.3"}, []string{"pod1", "pod2", "pod3"}) 2353 assertPendingResync(1) 2354 if err := controller.client.Kube().CoreV1().Endpoints("nsA").Delete(context.TODO(), "svc", metav1.DeleteOptions{}); err != nil { 2355 t.Fatal(err) 2356 } 2357 if err := controller.client.Kube().DiscoveryV1().EndpointSlices("nsA").Delete(context.TODO(), "svc", metav1.DeleteOptions{}); err != nil { 2358 t.Fatal(err) 2359 } 2360 assertPendingResync(0) 2361 } 2362 2363 func TestWorkloadInstanceHandlerMultipleEndpoints(t *testing.T) { 2364 controller, fx := NewFakeControllerWithOptions(t, FakeControllerOptions{}) 2365 2366 // Create an initial pod with a service, and endpoint. 2367 pod1 := generatePod("172.0.1.1", "pod1", "nsA", "", "node1", map[string]string{"app": "prod-app"}, map[string]string{}) 2368 pod2 := generatePod("172.0.1.2", "pod2", "nsA", "", "node1", map[string]string{"app": "prod-app"}, map[string]string{}) 2369 pods := []*corev1.Pod{pod1, pod2} 2370 nodes := []*corev1.Node{ 2371 generateNode("node1", map[string]string{NodeZoneLabel: "zone1", NodeRegionLabel: "region1", label.TopologySubzone.Name: "subzone1"}), 2372 } 2373 addNodes(t, controller, nodes...) 2374 addPods(t, controller, fx, pods...) 2375 createServiceWait(controller, "svc1", "nsA", nil, nil, 2376 []int32{8080}, map[string]string{"app": "prod-app"}, t) 2377 pod1Ips := []string{"172.0.1.1"} 2378 portNames := []string{"tcp-port"} 2379 createEndpoints(t, controller, "svc1", "nsA", portNames, pod1Ips, nil, nil) 2380 fx.WaitOrFail(t, "eds") 2381 2382 // Simulate adding a workload entry (fired through invocation of WorkloadInstanceHandler) 2383 controller.workloadInstanceHandler(&model.WorkloadInstance{ 2384 Namespace: "nsA", 2385 Endpoint: &model.IstioEndpoint{ 2386 Labels: labels.Instance{"app": "prod-app"}, 2387 ServiceAccount: "account", 2388 Address: "2.2.2.2", 2389 EndpointPort: 8080, 2390 }, 2391 }, model.EventAdd) 2392 2393 expectedEndpointIPs := []string{"172.0.1.1", "2.2.2.2"} 2394 // Check if an EDS event is fired 2395 ev := fx.WaitOrFail(t, "eds") 2396 // check if the hostname matches that of k8s service svc1.nsA 2397 if ev.ID != "svc1.nsA.svc.company.com" { 2398 t.Fatalf("eds event for workload entry addition did not match the expected service. got %s, want %s", 2399 ev.ID, "svc1.nsA.svc.company.com") 2400 } 2401 // we should have the pod IP and the workload Entry's IP in the endpoints.. 2402 // the first endpoint should be that of the k8s pod and the second one should be the workload entry 2403 2404 gotEndpointIPs := make([]string, 0, len(ev.Endpoints)) 2405 for _, ep := range ev.Endpoints { 2406 gotEndpointIPs = append(gotEndpointIPs, ep.Address) 2407 } 2408 if !reflect.DeepEqual(gotEndpointIPs, expectedEndpointIPs) { 2409 t.Fatalf("eds update after adding workload entry did not match expected list. got %v, want %v", 2410 gotEndpointIPs, expectedEndpointIPs) 2411 } 2412 2413 // Check if InstancesByPort returns the same list 2414 converted := controller.Services() 2415 if len(converted) != 1 { 2416 t.Fatalf("failed to get services (%v), converted", converted) 2417 } 2418 endpoints := GetEndpoints(converted[0], controller.Endpoints) 2419 gotEndpointIPs = []string{} 2420 for _, instance := range endpoints { 2421 gotEndpointIPs = append(gotEndpointIPs, instance.Address) 2422 } 2423 if !reflect.DeepEqual(gotEndpointIPs, expectedEndpointIPs) { 2424 t.Fatalf("InstancesByPort after adding workload entry did not match expected list. got %v, want %v", 2425 gotEndpointIPs, expectedEndpointIPs) 2426 } 2427 2428 // Now add a k8s pod to the service and ensure that eds updates contain both pod IPs and workload entry IPs. 2429 updateEndpoints(controller, "svc1", "nsA", portNames, []string{"172.0.1.1", "172.0.1.2"}, t) 2430 ev = fx.WaitOrFail(t, "eds") 2431 gotEndpointIPs = []string{} 2432 for _, ep := range ev.Endpoints { 2433 gotEndpointIPs = append(gotEndpointIPs, ep.Address) 2434 } 2435 expectedEndpointIPs = []string{"172.0.1.1", "172.0.1.2", "2.2.2.2"} 2436 if !reflect.DeepEqual(gotEndpointIPs, expectedEndpointIPs) { 2437 t.Fatalf("eds update after adding pod did not match expected list. got %v, want %v", 2438 gotEndpointIPs, expectedEndpointIPs) 2439 } 2440 } 2441 2442 func TestWorkloadInstanceHandler_WorkloadInstanceIndex(t *testing.T) { 2443 ctl, _ := NewFakeControllerWithOptions(t, FakeControllerOptions{}) 2444 2445 verifyGetByIP := func(address string, want []*model.WorkloadInstance) { 2446 t.Helper() 2447 got := ctl.workloadInstancesIndex.GetByIP(address) 2448 2449 assert.Equal(t, want, got) 2450 } 2451 2452 wi1 := &model.WorkloadInstance{ 2453 Name: "ratings-1", 2454 Namespace: "bookinfo", 2455 Endpoint: &model.IstioEndpoint{ 2456 Labels: labels.Instance{"app": "ratings"}, 2457 Address: "2.2.2.2", 2458 EndpointPort: 8080, 2459 }, 2460 } 2461 2462 // simulate adding a workload entry 2463 ctl.workloadInstanceHandler(wi1, model.EventAdd) 2464 2465 verifyGetByIP("2.2.2.2", []*model.WorkloadInstance{wi1}) 2466 2467 wi2 := &model.WorkloadInstance{ 2468 Name: "details-1", 2469 Namespace: "bookinfo", 2470 Endpoint: &model.IstioEndpoint{ 2471 Labels: labels.Instance{"app": "details"}, 2472 Address: "3.3.3.3", 2473 EndpointPort: 9090, 2474 }, 2475 } 2476 2477 // simulate adding a workload entry 2478 ctl.workloadInstanceHandler(wi2, model.EventAdd) 2479 2480 verifyGetByIP("2.2.2.2", []*model.WorkloadInstance{wi1}) 2481 verifyGetByIP("3.3.3.3", []*model.WorkloadInstance{wi2}) 2482 2483 wi3 := &model.WorkloadInstance{ 2484 Name: "details-1", 2485 Namespace: "bookinfo", 2486 Endpoint: &model.IstioEndpoint{ 2487 Labels: labels.Instance{"app": "details"}, 2488 Address: "2.2.2.2", // update IP 2489 EndpointPort: 9090, 2490 }, 2491 } 2492 2493 // simulate updating a workload entry 2494 ctl.workloadInstanceHandler(wi3, model.EventUpdate) 2495 2496 verifyGetByIP("3.3.3.3", nil) 2497 verifyGetByIP("2.2.2.2", []*model.WorkloadInstance{wi3, wi1}) 2498 2499 // simulate deleting a workload entry 2500 ctl.workloadInstanceHandler(wi3, model.EventDelete) 2501 2502 verifyGetByIP("2.2.2.2", []*model.WorkloadInstance{wi1}) 2503 2504 // simulate deleting a workload entry 2505 ctl.workloadInstanceHandler(wi1, model.EventDelete) 2506 2507 verifyGetByIP("2.2.2.2", nil) 2508 } 2509 2510 func TestUpdateEdsCacheOnServiceUpdate(t *testing.T) { 2511 controller, fx := NewFakeControllerWithOptions(t, FakeControllerOptions{}) 2512 2513 // Create an initial pod with a service, and endpoint. 2514 pod1 := generatePod("172.0.1.1", "pod1", "nsA", "", "node1", map[string]string{"app": "prod-app"}, map[string]string{}) 2515 pod2 := generatePod("172.0.1.2", "pod2", "nsA", "", "node1", map[string]string{"app": "prod-app"}, map[string]string{}) 2516 pods := []*corev1.Pod{pod1, pod2} 2517 nodes := []*corev1.Node{ 2518 generateNode("node1", map[string]string{NodeZoneLabel: "zone1", NodeRegionLabel: "region1", label.TopologySubzone.Name: "subzone1"}), 2519 } 2520 addNodes(t, controller, nodes...) 2521 addPods(t, controller, fx, pods...) 2522 createServiceWait(controller, "svc1", "nsA", nil, nil, 2523 []int32{8080}, map[string]string{"app": "prod-app"}, t) 2524 2525 pod1Ips := []string{"172.0.1.1"} 2526 portNames := []string{"tcp-port"} 2527 createEndpoints(t, controller, "svc1", "nsA", portNames, pod1Ips, nil, nil) 2528 fx.WaitOrFail(t, "eds") 2529 2530 // update service selector 2531 svc := getService(controller, "svc1", "nsA", t) 2532 svc.Spec.Selector = map[string]string{ 2533 "app": "prod-app", 2534 "foo": "bar", 2535 } 2536 // set `K8SServiceSelectWorkloadEntries` to false temporarily 2537 tmp := features.EnableK8SServiceSelectWorkloadEntries 2538 features.EnableK8SServiceSelectWorkloadEntries = false 2539 defer func() { 2540 features.EnableK8SServiceSelectWorkloadEntries = tmp 2541 }() 2542 svc = updateService(controller, svc, t) 2543 // don't update eds cache if `K8S_SELECT_WORKLOAD_ENTRIES` is disabled 2544 fx.WaitOrFail(t, "service") 2545 fx.AssertEmpty(t, 0) 2546 2547 features.EnableK8SServiceSelectWorkloadEntries = true 2548 svc.Spec.Selector = map[string]string{ 2549 "app": "prod-app", 2550 } 2551 updateService(controller, svc, t) 2552 // update eds cache if `K8S_SELECT_WORKLOAD_ENTRIES` is enabled 2553 fx.WaitOrFail(t, "eds cache") 2554 } 2555 2556 func TestDiscoverySelector(t *testing.T) { 2557 networksWatcher := mesh.NewFixedNetworksWatcher(&meshconfig.MeshNetworks{ 2558 Networks: map[string]*meshconfig.Network{ 2559 "network1": { 2560 Endpoints: []*meshconfig.Network_NetworkEndpoints{ 2561 { 2562 Ne: &meshconfig.Network_NetworkEndpoints_FromCidr{ 2563 FromCidr: "10.10.1.1/24", 2564 }, 2565 }, 2566 }, 2567 }, 2568 "network2": { 2569 Endpoints: []*meshconfig.Network_NetworkEndpoints{ 2570 { 2571 Ne: &meshconfig.Network_NetworkEndpoints_FromCidr{ 2572 FromCidr: "10.11.1.1/24", 2573 }, 2574 }, 2575 }, 2576 }, 2577 }, 2578 }) 2579 ctl, _ := NewFakeControllerWithOptions(t, FakeControllerOptions{NetworksWatcher: networksWatcher}) 2580 t.Parallel() 2581 ns := "ns-test" 2582 2583 hostname := kube.ServiceHostname(testService, ns, defaultFakeDomainSuffix) 2584 2585 var sds model.ServiceDiscovery = ctl 2586 // "test", ports: http-example on 80 2587 makeService(testService, ns, ctl, t) 2588 2589 eventually(t, func() bool { 2590 out := sds.Services() 2591 2592 // Original test was checking for 'protocolTCP' - which is incorrect (the 2593 // port name is 'http'. It was working because the Service was created with 2594 // an invalid protocol, and the code was ignoring that ( not TCP/UDP). 2595 for _, item := range out { 2596 if item.Hostname == hostname && 2597 len(item.Ports) == 1 && 2598 item.Ports[0].Protocol == protocol.HTTP { 2599 return true 2600 } 2601 } 2602 return false 2603 }) 2604 2605 svc := sds.GetService(hostname) 2606 if svc == nil { 2607 t.Fatalf("GetService(%q) => should exists", hostname) 2608 } 2609 if svc.Hostname != hostname { 2610 t.Fatalf("GetService(%q) => %q", hostname, svc.Hostname) 2611 } 2612 2613 missing := kube.ServiceHostname("does-not-exist", ns, defaultFakeDomainSuffix) 2614 svc = sds.GetService(missing) 2615 if svc != nil { 2616 t.Fatalf("GetService(%q) => %s, should not exist", missing, svc.Hostname) 2617 } 2618 } 2619 2620 func TestStripNodeUnusedFields(t *testing.T) { 2621 inputNode := &corev1.Node{ 2622 TypeMeta: metav1.TypeMeta{ 2623 Kind: "Node", 2624 APIVersion: "v1", 2625 }, 2626 ObjectMeta: metav1.ObjectMeta{ 2627 Name: "test", 2628 Labels: map[string]string{ 2629 NodeZoneLabel: "zone1", 2630 NodeRegionLabel: "region1", 2631 label.TopologySubzone.Name: "subzone1", 2632 }, 2633 Annotations: map[string]string{ 2634 "annotation1": "foo", 2635 "annotation2": "bar", 2636 }, 2637 ManagedFields: []metav1.ManagedFieldsEntry{ 2638 { 2639 Manager: "test", 2640 }, 2641 }, 2642 OwnerReferences: []metav1.OwnerReference{ 2643 { 2644 Name: "test", 2645 }, 2646 }, 2647 }, 2648 Status: corev1.NodeStatus{ 2649 Allocatable: map[corev1.ResourceName]resource.Quantity{ 2650 "cpu": { 2651 Format: "500m", 2652 }, 2653 }, 2654 Capacity: map[corev1.ResourceName]resource.Quantity{ 2655 "cpu": { 2656 Format: "500m", 2657 }, 2658 }, 2659 Images: []corev1.ContainerImage{ 2660 { 2661 Names: []string{"test"}, 2662 }, 2663 }, 2664 Conditions: []corev1.NodeCondition{ 2665 { 2666 Type: corev1.NodeMemoryPressure, 2667 }, 2668 }, 2669 }, 2670 } 2671 2672 expectNode := &corev1.Node{ 2673 TypeMeta: metav1.TypeMeta{ 2674 Kind: "Node", 2675 APIVersion: "v1", 2676 }, 2677 ObjectMeta: metav1.ObjectMeta{ 2678 Name: "test", 2679 Labels: map[string]string{ 2680 NodeZoneLabel: "zone1", 2681 NodeRegionLabel: "region1", 2682 label.TopologySubzone.Name: "subzone1", 2683 }, 2684 }, 2685 } 2686 2687 controller, _ := NewFakeControllerWithOptions(t, FakeControllerOptions{}) 2688 addNodes(t, controller, inputNode) 2689 2690 assert.Equal(t, expectNode, controller.nodes.Get(inputNode.Name, "")) 2691 } 2692 2693 func TestStripPodUnusedFields(t *testing.T) { 2694 inputPod := &corev1.Pod{ 2695 TypeMeta: metav1.TypeMeta{ 2696 Kind: "Pod", 2697 APIVersion: "v1", 2698 }, 2699 ObjectMeta: metav1.ObjectMeta{ 2700 Name: "test", 2701 Namespace: "default", 2702 Labels: map[string]string{ 2703 "app": "test", 2704 }, 2705 Annotations: map[string]string{ 2706 "annotation1": "foo", 2707 "annotation2": "bar", 2708 }, 2709 ManagedFields: []metav1.ManagedFieldsEntry{ 2710 { 2711 Manager: "test", 2712 }, 2713 }, 2714 }, 2715 Spec: corev1.PodSpec{ 2716 InitContainers: []corev1.Container{ 2717 { 2718 Name: "init-container", 2719 }, 2720 }, 2721 Containers: []corev1.Container{ 2722 { 2723 Name: "container-1", 2724 Ports: []corev1.ContainerPort{ 2725 { 2726 Name: "http", 2727 }, 2728 }, 2729 }, 2730 { 2731 Name: "container-2", 2732 }, 2733 }, 2734 Volumes: []corev1.Volume{ 2735 { 2736 Name: "test", 2737 }, 2738 }, 2739 }, 2740 Status: corev1.PodStatus{ 2741 InitContainerStatuses: []corev1.ContainerStatus{ 2742 { 2743 Name: "init-container", 2744 }, 2745 }, 2746 ContainerStatuses: []corev1.ContainerStatus{ 2747 { 2748 Name: "container-1", 2749 }, 2750 { 2751 Name: "container-2", 2752 }, 2753 }, 2754 PodIP: "1.1.1.1", 2755 HostIP: "1.1.1.1", 2756 Phase: corev1.PodRunning, 2757 }, 2758 } 2759 2760 expectPod := &corev1.Pod{ 2761 TypeMeta: metav1.TypeMeta{ 2762 Kind: "Pod", 2763 APIVersion: "v1", 2764 }, 2765 ObjectMeta: metav1.ObjectMeta{ 2766 Name: "test", 2767 Namespace: "default", 2768 Labels: map[string]string{ 2769 "app": "test", 2770 }, 2771 Annotations: map[string]string{ 2772 "annotation1": "foo", 2773 "annotation2": "bar", 2774 }, 2775 }, 2776 Spec: corev1.PodSpec{ 2777 Containers: []corev1.Container{ 2778 { 2779 Ports: []corev1.ContainerPort{ 2780 { 2781 Name: "http", 2782 }, 2783 }, 2784 }, 2785 }, 2786 }, 2787 Status: corev1.PodStatus{ 2788 PodIP: "1.1.1.1", 2789 HostIP: "1.1.1.1", 2790 Phase: corev1.PodRunning, 2791 }, 2792 } 2793 2794 controller, fx := NewFakeControllerWithOptions(t, FakeControllerOptions{}) 2795 addPods(t, controller, fx, inputPod) 2796 2797 output := controller.pods.getPodByKey(config.NamespacedName(expectPod)) 2798 // The final pod status conditions will be determined by the function addPods. 2799 // So we assign these status conditions to expect pod. 2800 expectPod.Status.Conditions = output.Status.Conditions 2801 if !reflect.DeepEqual(expectPod, output) { 2802 t.Fatalf("Wanted: %v\n. Got: %v", expectPod, output) 2803 } 2804 } 2805 2806 func TestServiceUpdateNeedsPush(t *testing.T) { 2807 newService := func(exportTo visibility.Instance, ports []int) *model.Service { 2808 s := &model.Service{ 2809 Attributes: model.ServiceAttributes{ 2810 ExportTo: sets.New(exportTo), 2811 }, 2812 } 2813 for _, port := range ports { 2814 s.Ports = append(s.Ports, &model.Port{ 2815 Port: port, 2816 }) 2817 } 2818 return s 2819 } 2820 2821 type testcase struct { 2822 name string 2823 prev *corev1.Service 2824 curr *corev1.Service 2825 prevConv *model.Service 2826 currConv *model.Service 2827 expect bool 2828 } 2829 2830 tests := []testcase{ 2831 { 2832 name: "no change", 2833 prevConv: newService(visibility.Public, []int{80}), 2834 currConv: newService(visibility.Public, []int{80}), 2835 expect: false, 2836 }, 2837 { 2838 name: "new service", 2839 prevConv: nil, 2840 currConv: newService(visibility.Public, []int{80}), 2841 expect: true, 2842 }, 2843 { 2844 name: "new service with none visibility", 2845 prevConv: nil, 2846 currConv: newService(visibility.None, []int{80}), 2847 expect: false, 2848 }, 2849 { 2850 name: "public visibility, spec change", 2851 prevConv: newService(visibility.Public, []int{80}), 2852 currConv: newService(visibility.Public, []int{80, 443}), 2853 expect: true, 2854 }, 2855 { 2856 name: "none visibility, spec change", 2857 prevConv: newService(visibility.None, []int{80}), 2858 currConv: newService(visibility.None, []int{80, 443}), 2859 expect: false, 2860 }, 2861 } 2862 2863 svc := corev1.Service{ 2864 ObjectMeta: metav1.ObjectMeta{ 2865 Name: "foo", 2866 Namespace: "bar", 2867 }, 2868 Spec: corev1.ServiceSpec{ 2869 Ports: []corev1.ServicePort{{Port: 80, TargetPort: intstr.FromInt32(8080)}}, 2870 }, 2871 } 2872 updatedSvc := corev1.Service{ 2873 ObjectMeta: metav1.ObjectMeta{ 2874 Name: "foo", 2875 Namespace: "bar", 2876 }, 2877 Spec: corev1.ServiceSpec{ 2878 Ports: []corev1.ServicePort{{Port: 80, TargetPort: intstr.FromInt32(8081)}}, 2879 }, 2880 } 2881 tests = append(tests, 2882 testcase{ 2883 name: "target ports changed", 2884 prev: &svc, 2885 curr: &updatedSvc, 2886 prevConv: kube.ConvertService(svc, constants.DefaultClusterLocalDomain, ""), 2887 currConv: kube.ConvertService(updatedSvc, constants.DefaultClusterLocalDomain, ""), 2888 expect: true, 2889 }, 2890 testcase{ 2891 name: "target ports unchanged", 2892 prev: &svc, 2893 curr: &svc, 2894 prevConv: kube.ConvertService(svc, constants.DefaultClusterLocalDomain, ""), 2895 currConv: kube.ConvertService(svc, constants.DefaultClusterLocalDomain, ""), 2896 expect: false, 2897 }) 2898 2899 for _, test := range tests { 2900 actual := serviceUpdateNeedsPush(test.prev, test.curr, test.prevConv, test.currConv) 2901 if actual != test.expect { 2902 t.Fatalf("%s: expected %v, got %v", test.name, test.expect, actual) 2903 } 2904 } 2905 }