istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/serviceregistry/kube/controller/ambient/workloads_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 ambient 16 17 import ( 18 "fmt" 19 "net/netip" 20 "testing" 21 22 v1 "k8s.io/api/core/v1" 23 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 25 meshapi "istio.io/api/mesh/v1alpha1" 26 networking "istio.io/api/networking/v1alpha3" 27 networkingclient "istio.io/client-go/pkg/apis/networking/v1alpha3" 28 securityclient "istio.io/client-go/pkg/apis/security/v1beta1" 29 "istio.io/istio/pilot/pkg/model" 30 "istio.io/istio/pkg/config/labels" 31 "istio.io/istio/pkg/config/schema/kind" 32 "istio.io/istio/pkg/kube/krt" 33 "istio.io/istio/pkg/network" 34 "istio.io/istio/pkg/slices" 35 "istio.io/istio/pkg/test/util/assert" 36 "istio.io/istio/pkg/workloadapi" 37 ) 38 39 func TestPodWorkloads(t *testing.T) { 40 cases := []struct { 41 name string 42 inputs []any 43 pod *v1.Pod 44 result *workloadapi.Workload 45 }{ 46 { 47 name: "simple pod not running and not have podIP", 48 inputs: []any{}, 49 pod: &v1.Pod{ 50 TypeMeta: metav1.TypeMeta{}, 51 ObjectMeta: metav1.ObjectMeta{ 52 Name: "name", 53 Namespace: "ns", 54 }, 55 Spec: v1.PodSpec{}, 56 Status: v1.PodStatus{ 57 Phase: v1.PodPending, 58 }, 59 }, 60 result: nil, 61 }, 62 { 63 name: "simple pod not running but have podIP", 64 inputs: []any{}, 65 pod: &v1.Pod{ 66 TypeMeta: metav1.TypeMeta{}, 67 ObjectMeta: metav1.ObjectMeta{ 68 Name: "name", 69 Namespace: "ns", 70 }, 71 Spec: v1.PodSpec{}, 72 Status: v1.PodStatus{ 73 Phase: v1.PodPending, 74 PodIP: "1.2.3.4", 75 }, 76 }, 77 result: &workloadapi.Workload{ 78 Uid: "cluster0//Pod/ns/name", 79 Name: "name", 80 Namespace: "ns", 81 Addresses: [][]byte{netip.AddrFrom4([4]byte{1, 2, 3, 4}).AsSlice()}, 82 Network: testNW, 83 CanonicalName: "name", 84 CanonicalRevision: "latest", 85 WorkloadType: workloadapi.WorkloadType_POD, 86 WorkloadName: "name", 87 Status: workloadapi.WorkloadStatus_UNHEALTHY, 88 ClusterId: testC, 89 }, 90 }, 91 { 92 name: "simple pod not ready", 93 inputs: []any{}, 94 pod: &v1.Pod{ 95 TypeMeta: metav1.TypeMeta{}, 96 ObjectMeta: metav1.ObjectMeta{ 97 Name: "name", 98 Namespace: "ns", 99 }, 100 Spec: v1.PodSpec{}, 101 Status: v1.PodStatus{ 102 Phase: v1.PodRunning, 103 PodIP: "1.2.3.4", 104 }, 105 }, 106 result: &workloadapi.Workload{ 107 Uid: "cluster0//Pod/ns/name", 108 Name: "name", 109 Namespace: "ns", 110 Addresses: [][]byte{netip.AddrFrom4([4]byte{1, 2, 3, 4}).AsSlice()}, 111 Network: testNW, 112 CanonicalName: "name", 113 CanonicalRevision: "latest", 114 WorkloadType: workloadapi.WorkloadType_POD, 115 WorkloadName: "name", 116 Status: workloadapi.WorkloadStatus_UNHEALTHY, 117 ClusterId: testC, 118 }, 119 }, 120 { 121 name: "pod with service", 122 inputs: []any{ 123 model.ServiceInfo{ 124 Service: &workloadapi.Service{ 125 Name: "svc", 126 Namespace: "ns", 127 Hostname: "hostname", 128 Ports: []*workloadapi.Port{{ 129 ServicePort: 80, 130 TargetPort: 8080, 131 }}, 132 }, 133 LabelSelector: model.NewSelector(map[string]string{"app": "foo"}), 134 }, 135 }, 136 pod: &v1.Pod{ 137 TypeMeta: metav1.TypeMeta{}, 138 ObjectMeta: metav1.ObjectMeta{ 139 Name: "name", 140 Namespace: "ns", 141 Labels: map[string]string{ 142 "app": "foo", 143 }, 144 }, 145 Spec: v1.PodSpec{}, 146 Status: v1.PodStatus{ 147 Phase: v1.PodRunning, 148 Conditions: podReady, 149 PodIP: "1.2.3.4", 150 }, 151 }, 152 result: &workloadapi.Workload{ 153 Uid: "cluster0//Pod/ns/name", 154 Name: "name", 155 Namespace: "ns", 156 Addresses: [][]byte{netip.AddrFrom4([4]byte{1, 2, 3, 4}).AsSlice()}, 157 Network: testNW, 158 CanonicalName: "foo", 159 CanonicalRevision: "latest", 160 WorkloadType: workloadapi.WorkloadType_POD, 161 WorkloadName: "name", 162 Status: workloadapi.WorkloadStatus_HEALTHY, 163 ClusterId: testC, 164 Services: map[string]*workloadapi.PortList{ 165 "ns/hostname": { 166 Ports: []*workloadapi.Port{{ 167 ServicePort: 80, 168 TargetPort: 8080, 169 }}, 170 }, 171 }, 172 }, 173 }, 174 { 175 name: "pod with service named ports", 176 inputs: []any{ 177 model.ServiceInfo{ 178 Service: &workloadapi.Service{ 179 Name: "svc", 180 Namespace: "ns", 181 Hostname: "hostname", 182 Ports: []*workloadapi.Port{ 183 { 184 ServicePort: 80, 185 TargetPort: 8080, 186 }, 187 { 188 ServicePort: 81, 189 TargetPort: 0, 190 }, 191 { 192 ServicePort: 82, 193 TargetPort: 0, 194 }, 195 }, 196 }, 197 PortNames: map[int32]model.ServicePortName{ 198 // Not a named port 199 80: {PortName: "80"}, 200 // Named port found in pod 201 81: {PortName: "81", TargetPortName: "81-target"}, 202 // Named port not found in pod 203 82: {PortName: "82", TargetPortName: "82-target"}, 204 }, 205 LabelSelector: model.NewSelector(map[string]string{"app": "foo"}), 206 }, 207 }, 208 pod: &v1.Pod{ 209 TypeMeta: metav1.TypeMeta{}, 210 ObjectMeta: metav1.ObjectMeta{ 211 Name: "name", 212 Namespace: "ns", 213 Labels: map[string]string{ 214 "app": "foo", 215 }, 216 }, 217 Spec: v1.PodSpec{ 218 Containers: []v1.Container{{Ports: []v1.ContainerPort{ 219 { 220 Name: "81-target", 221 ContainerPort: 9090, 222 Protocol: v1.ProtocolTCP, 223 }, 224 }}}, 225 }, 226 Status: v1.PodStatus{ 227 Phase: v1.PodRunning, 228 Conditions: podReady, 229 PodIP: "1.2.3.4", 230 }, 231 }, 232 result: &workloadapi.Workload{ 233 Uid: "cluster0//Pod/ns/name", 234 Name: "name", 235 Namespace: "ns", 236 Addresses: [][]byte{netip.AddrFrom4([4]byte{1, 2, 3, 4}).AsSlice()}, 237 Network: testNW, 238 CanonicalName: "foo", 239 CanonicalRevision: "latest", 240 WorkloadType: workloadapi.WorkloadType_POD, 241 WorkloadName: "name", 242 Status: workloadapi.WorkloadStatus_HEALTHY, 243 ClusterId: testC, 244 Services: map[string]*workloadapi.PortList{ 245 "ns/hostname": { 246 Ports: []*workloadapi.Port{{ 247 ServicePort: 80, 248 TargetPort: 8080, 249 }, { 250 ServicePort: 81, 251 TargetPort: 9090, 252 }}, 253 }, 254 }, 255 }, 256 }, 257 { 258 name: "simple pod with locality", 259 inputs: []any{ 260 &v1.Node{ 261 ObjectMeta: metav1.ObjectMeta{ 262 Name: "node", 263 Labels: map[string]string{ 264 v1.LabelTopologyRegion: "region", 265 v1.LabelTopologyZone: "zone", 266 }, 267 }, 268 }, 269 }, 270 pod: &v1.Pod{ 271 TypeMeta: metav1.TypeMeta{}, 272 ObjectMeta: metav1.ObjectMeta{ 273 Name: "name", 274 Namespace: "ns", 275 }, 276 Spec: v1.PodSpec{NodeName: "node"}, 277 Status: v1.PodStatus{ 278 Phase: v1.PodPending, 279 PodIP: "1.2.3.4", 280 }, 281 }, 282 result: &workloadapi.Workload{ 283 Uid: "cluster0//Pod/ns/name", 284 Name: "name", 285 Namespace: "ns", 286 Node: "node", 287 Addresses: [][]byte{netip.AddrFrom4([4]byte{1, 2, 3, 4}).AsSlice()}, 288 Network: testNW, 289 CanonicalName: "name", 290 CanonicalRevision: "latest", 291 WorkloadType: workloadapi.WorkloadType_POD, 292 WorkloadName: "name", 293 Status: workloadapi.WorkloadStatus_UNHEALTHY, 294 ClusterId: testC, 295 Locality: &workloadapi.Locality{ 296 Region: "region", 297 Zone: "zone", 298 }, 299 }, 300 }, 301 } 302 for _, tt := range cases { 303 t.Run(tt.name, func(t *testing.T) { 304 inputs := tt.inputs 305 a := newAmbientUnitTest() 306 AuthorizationPolicies := krt.NewStaticCollection(extractType[model.WorkloadAuthorization](&inputs)) 307 PeerAuths := krt.NewStaticCollection(extractType[*securityclient.PeerAuthentication](&inputs)) 308 Waypoints := krt.NewStaticCollection(extractType[Waypoint](&inputs)) 309 WorkloadServices := krt.NewStaticCollection(extractType[model.ServiceInfo](&inputs)) 310 MeshConfig := krt.NewStatic(&MeshConfig{slices.First(extractType[meshapi.MeshConfig](&inputs))}) 311 Namespaces := krt.NewStaticCollection(extractType[*v1.Namespace](&inputs)) 312 Nodes := krt.NewStaticCollection(extractType[*v1.Node](&inputs)) 313 assert.Equal(t, len(inputs), 0, fmt.Sprintf("some inputs were not consumed: %v", inputs)) 314 WorkloadServicesNamespaceIndex := krt.NewNamespaceIndex(WorkloadServices) 315 builder := a.podWorkloadBuilder(MeshConfig, AuthorizationPolicies, PeerAuths, Waypoints, WorkloadServices, WorkloadServicesNamespaceIndex, Namespaces, Nodes) 316 wrapper := builder(krt.TestingDummyContext{}, tt.pod) 317 var res *workloadapi.Workload 318 if wrapper != nil { 319 res = wrapper.Workload 320 } 321 assert.Equal(t, res, tt.result) 322 }) 323 } 324 } 325 326 func TestWorkloadEntryWorkloads(t *testing.T) { 327 cases := []struct { 328 name string 329 inputs []any 330 we *networkingclient.WorkloadEntry 331 result *workloadapi.Workload 332 }{ 333 { 334 name: "we with service", 335 inputs: []any{ 336 model.ServiceInfo{ 337 Service: &workloadapi.Service{ 338 Name: "svc", 339 Namespace: "ns", 340 Hostname: "hostname", 341 Ports: []*workloadapi.Port{{ 342 ServicePort: 80, 343 TargetPort: 8080, 344 }}, 345 }, 346 LabelSelector: model.NewSelector(map[string]string{"app": "foo"}), 347 }, 348 }, 349 we: &networkingclient.WorkloadEntry{ 350 TypeMeta: metav1.TypeMeta{}, 351 ObjectMeta: metav1.ObjectMeta{ 352 Name: "name", 353 Namespace: "ns", 354 Labels: map[string]string{ 355 "app": "foo", 356 }, 357 }, 358 Spec: networking.WorkloadEntry{ 359 Address: "1.2.3.4", 360 }, 361 }, 362 result: &workloadapi.Workload{ 363 Uid: "cluster0/networking.istio.io/WorkloadEntry/ns/name", 364 Name: "name", 365 Namespace: "ns", 366 Addresses: [][]byte{netip.AddrFrom4([4]byte{1, 2, 3, 4}).AsSlice()}, 367 Network: testNW, 368 CanonicalName: "foo", 369 CanonicalRevision: "latest", 370 WorkloadType: workloadapi.WorkloadType_POD, 371 WorkloadName: "name", 372 Status: workloadapi.WorkloadStatus_HEALTHY, 373 ClusterId: testC, 374 Services: map[string]*workloadapi.PortList{ 375 "ns/hostname": { 376 Ports: []*workloadapi.Port{{ 377 ServicePort: 80, 378 TargetPort: 8080, 379 }}, 380 }, 381 }, 382 }, 383 }, 384 { 385 name: "pod with service named ports", 386 inputs: []any{ 387 model.ServiceInfo{ 388 Service: &workloadapi.Service{ 389 Name: "svc", 390 Namespace: "ns", 391 Hostname: "hostname", 392 Ports: []*workloadapi.Port{ 393 { 394 ServicePort: 80, 395 TargetPort: 8080, 396 }, 397 { 398 ServicePort: 81, 399 TargetPort: 0, 400 }, 401 { 402 ServicePort: 82, 403 TargetPort: 0, 404 }, 405 { 406 ServicePort: 83, 407 TargetPort: 0, 408 }, 409 }, 410 }, 411 PortNames: map[int32]model.ServicePortName{ 412 // Not a named port 413 80: {PortName: "80"}, 414 // Named port found in WE 415 81: {PortName: "81", TargetPortName: "81-target"}, 416 // Named port target found in WE 417 82: {PortName: "82", TargetPortName: "82-target"}, 418 // Named port not found in WE 419 83: {PortName: "83", TargetPortName: "83-target"}, 420 }, 421 LabelSelector: model.NewSelector(map[string]string{"app": "foo"}), 422 Source: kind.Service, 423 }, 424 }, 425 we: &networkingclient.WorkloadEntry{ 426 TypeMeta: metav1.TypeMeta{}, 427 ObjectMeta: metav1.ObjectMeta{ 428 Name: "name", 429 Namespace: "ns", 430 Labels: map[string]string{ 431 "app": "foo", 432 }, 433 }, 434 Spec: networking.WorkloadEntry{ 435 Ports: map[string]uint32{ 436 "81": 8180, 437 "82-target": 8280, 438 }, 439 Address: "1.2.3.4", 440 }, 441 }, 442 result: &workloadapi.Workload{ 443 Uid: "cluster0/networking.istio.io/WorkloadEntry/ns/name", 444 Name: "name", 445 Namespace: "ns", 446 Addresses: [][]byte{netip.AddrFrom4([4]byte{1, 2, 3, 4}).AsSlice()}, 447 Network: testNW, 448 CanonicalName: "foo", 449 CanonicalRevision: "latest", 450 WorkloadType: workloadapi.WorkloadType_POD, 451 WorkloadName: "name", 452 Status: workloadapi.WorkloadStatus_HEALTHY, 453 ClusterId: testC, 454 Services: map[string]*workloadapi.PortList{ 455 "ns/hostname": { 456 Ports: []*workloadapi.Port{ 457 { 458 ServicePort: 80, 459 TargetPort: 8080, 460 }, 461 { 462 ServicePort: 82, 463 TargetPort: 8280, 464 }, 465 }, 466 }, 467 }, 468 }, 469 }, 470 { 471 name: "pod with serviceentry named ports", 472 inputs: []any{ 473 model.ServiceInfo{ 474 Service: &workloadapi.Service{ 475 Name: "svc", 476 Namespace: "ns", 477 Hostname: "hostname", 478 Ports: []*workloadapi.Port{ 479 { 480 ServicePort: 80, 481 TargetPort: 8080, 482 }, 483 { 484 ServicePort: 81, 485 TargetPort: 0, 486 }, 487 { 488 ServicePort: 82, 489 TargetPort: 0, 490 }, 491 }, 492 }, 493 PortNames: map[int32]model.ServicePortName{ 494 // TargetPort explicitly set 495 80: {PortName: "80"}, 496 // Port name found 497 81: {PortName: "81"}, 498 // Port name not found 499 82: {PortName: "82"}, 500 }, 501 LabelSelector: model.NewSelector(map[string]string{"app": "foo"}), 502 Source: kind.ServiceEntry, 503 }, 504 }, 505 we: &networkingclient.WorkloadEntry{ 506 TypeMeta: metav1.TypeMeta{}, 507 ObjectMeta: metav1.ObjectMeta{ 508 Name: "name", 509 Namespace: "ns", 510 Labels: map[string]string{ 511 "app": "foo", 512 }, 513 }, 514 Spec: networking.WorkloadEntry{ 515 Ports: map[string]uint32{ 516 "81": 8180, 517 }, 518 Address: "1.2.3.4", 519 }, 520 }, 521 result: &workloadapi.Workload{ 522 Uid: "cluster0/networking.istio.io/WorkloadEntry/ns/name", 523 Name: "name", 524 Namespace: "ns", 525 Addresses: [][]byte{netip.AddrFrom4([4]byte{1, 2, 3, 4}).AsSlice()}, 526 Network: testNW, 527 CanonicalName: "foo", 528 CanonicalRevision: "latest", 529 WorkloadType: workloadapi.WorkloadType_POD, 530 WorkloadName: "name", 531 Status: workloadapi.WorkloadStatus_HEALTHY, 532 ClusterId: testC, 533 Services: map[string]*workloadapi.PortList{ 534 "ns/hostname": { 535 Ports: []*workloadapi.Port{ 536 { 537 ServicePort: 80, 538 TargetPort: 8080, 539 }, 540 { 541 ServicePort: 81, 542 TargetPort: 8180, 543 }, 544 { 545 ServicePort: 82, 546 TargetPort: 82, 547 }, 548 }, 549 }, 550 }, 551 }, 552 }, 553 } 554 for _, tt := range cases { 555 t.Run(tt.name, func(t *testing.T) { 556 inputs := tt.inputs 557 a := newAmbientUnitTest() 558 AuthorizationPolicies := krt.NewStaticCollection(extractType[model.WorkloadAuthorization](&inputs)) 559 PeerAuths := krt.NewStaticCollection(extractType[*securityclient.PeerAuthentication](&inputs)) 560 Waypoints := krt.NewStaticCollection(extractType[Waypoint](&inputs)) 561 WorkloadServices := krt.NewStaticCollection(extractType[model.ServiceInfo](&inputs)) 562 Namespaces := krt.NewStaticCollection(extractType[*v1.Namespace](&inputs)) 563 MeshConfig := krt.NewStatic(&MeshConfig{slices.First(extractType[meshapi.MeshConfig](&inputs))}) 564 assert.Equal(t, len(inputs), 0, fmt.Sprintf("some inputs were not consumed: %v", inputs)) 565 WorkloadServicesNamespaceIndex := krt.NewNamespaceIndex(WorkloadServices) 566 builder := a.workloadEntryWorkloadBuilder( 567 MeshConfig, 568 AuthorizationPolicies, 569 PeerAuths, 570 Waypoints, 571 WorkloadServices, 572 WorkloadServicesNamespaceIndex, 573 Namespaces, 574 ) 575 wrapper := builder(krt.TestingDummyContext{}, tt.we) 576 var res *workloadapi.Workload 577 if wrapper != nil { 578 res = wrapper.Workload 579 } 580 assert.Equal(t, res, tt.result) 581 }) 582 } 583 } 584 585 func newAmbientUnitTest() *index { 586 return &index{ 587 networkUpdateTrigger: krt.NewRecomputeTrigger(), 588 ClusterID: testC, 589 Network: func(endpointIP string, labels labels.Instance) network.ID { 590 return testNW 591 }, 592 } 593 } 594 595 func extractType[T any](items *[]any) []T { 596 var matched []T 597 var unmatched []any 598 arr := *items 599 for _, val := range arr { 600 if c, ok := val.(T); ok { 601 matched = append(matched, c) 602 } else { 603 unmatched = append(unmatched, val) 604 } 605 } 606 607 *items = unmatched 608 return matched 609 } 610 611 var podReady = []v1.PodCondition{ 612 { 613 Type: v1.PodReady, 614 Status: v1.ConditionTrue, 615 LastTransitionTime: metav1.Now(), 616 }, 617 }