istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/model/service_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 model 16 17 import ( 18 "testing" 19 20 "github.com/google/go-cmp/cmp" 21 "github.com/google/go-cmp/cmp/cmpopts" 22 fuzz "github.com/google/gofuzz" 23 24 "istio.io/istio/pkg/cluster" 25 "istio.io/istio/pkg/config/constants" 26 "istio.io/istio/pkg/config/host" 27 "istio.io/istio/pkg/config/labels" 28 "istio.io/istio/pkg/config/visibility" 29 "istio.io/istio/pkg/test/util/assert" 30 ) 31 32 func TestGetByPort(t *testing.T) { 33 ports := PortList{{ 34 Name: "http", 35 Port: 80, 36 }} 37 38 if port, exists := ports.GetByPort(80); !exists || port == nil || port.Name != "http" { 39 t.Errorf("GetByPort(80) => want http but got %v, %t", port, exists) 40 } 41 if port, exists := ports.GetByPort(88); exists || port != nil { 42 t.Errorf("GetByPort(88) => want none but got %v, %t", port, exists) 43 } 44 } 45 46 func BenchmarkParseSubsetKey(b *testing.B) { 47 for n := 0; n < b.N; n++ { 48 ParseSubsetKey("outbound|80|v1|example.com") 49 ParseSubsetKey("outbound_.8080_.v1_.foo.example.org") 50 } 51 } 52 53 func TestParseSubsetKey(t *testing.T) { 54 tests := []struct { 55 input string 56 direction TrafficDirection 57 subsetName string 58 hostname host.Name 59 port int 60 }{ 61 {"outbound|80|v1|example.com", TrafficDirectionOutbound, "v1", "example.com", 80}, 62 {"", "", "", "", 0}, 63 {"|||", "", "", "", 0}, 64 {"outbound_.8080_.v1_.foo.example.org", TrafficDirectionOutbound, "v1", "foo.example.org", 8080}, 65 {"inbound_.8080_.v1_.foo.example.org", TrafficDirectionInbound, "v1", "foo.example.org", 8080}, 66 } 67 68 for _, tt := range tests { 69 t.Run(tt.input, func(t *testing.T) { 70 d, s, h, p := ParseSubsetKey(tt.input) 71 if d != tt.direction { 72 t.Errorf("Expected direction %v got %v", tt.direction, d) 73 } 74 if s != tt.subsetName { 75 t.Errorf("Expected subset %v got %v", tt.subsetName, s) 76 } 77 if h != tt.hostname { 78 t.Errorf("Expected hostname %v got %v", tt.hostname, h) 79 } 80 if p != tt.port { 81 t.Errorf("Expected direction %v got %v", tt.port, p) 82 } 83 }) 84 } 85 } 86 87 func TestIsValidSubsetKey(t *testing.T) { 88 cases := []struct { 89 subsetkey string 90 expectErr bool 91 }{ 92 { 93 subsetkey: "outbound|80|subset|hostname", 94 expectErr: false, 95 }, 96 { 97 subsetkey: "outbound|80||hostname", 98 expectErr: false, 99 }, 100 { 101 subsetkey: "outbound|80|subset||hostname", 102 expectErr: true, 103 }, 104 { 105 subsetkey: "", 106 expectErr: true, 107 }, 108 } 109 110 for _, c := range cases { 111 err := IsValidSubsetKey(c.subsetkey) 112 if !err != c.expectErr { 113 t.Errorf("got %v but want %v\n", err, c.expectErr) 114 } 115 } 116 } 117 118 func TestWorkloadInstanceEqual(t *testing.T) { 119 exampleInstance := &WorkloadInstance{ 120 Endpoint: &IstioEndpoint{ 121 Labels: labels.Instance{"app": "prod-app"}, 122 Address: "an-address", 123 ServicePortName: "service-port-name", 124 ServiceAccount: "service-account", 125 Network: "Network", 126 Locality: Locality{ 127 ClusterID: "cluster-id", 128 Label: "region1/zone1/subzone1", 129 }, 130 EndpointPort: 22, 131 LbWeight: 100, 132 TLSMode: "mutual", 133 }, 134 } 135 differingAddr := exampleInstance.DeepCopy() 136 differingAddr.Endpoint.Address = "another-address" 137 differingNetwork := exampleInstance.DeepCopy() 138 differingNetwork.Endpoint.Network = "AnotherNetwork" 139 differingTLSMode := exampleInstance.DeepCopy() 140 differingTLSMode.Endpoint.TLSMode = "permitted" 141 differingLabels := exampleInstance.DeepCopy() 142 differingLabels.Endpoint.Labels = labels.Instance{ 143 "app": "prod-app", 144 "another-app": "blah", 145 } 146 differingServiceAccount := exampleInstance.DeepCopy() 147 differingServiceAccount.Endpoint.ServiceAccount = "service-account-two" 148 differingLocality := exampleInstance.DeepCopy() 149 differingLocality.Endpoint.Locality = Locality{ 150 ClusterID: "cluster-id-two", 151 Label: "region2/zone2/subzone2", 152 } 153 differingLbWeight := exampleInstance.DeepCopy() 154 differingLbWeight.Endpoint.LbWeight = 0 155 156 cases := []struct { 157 comparer *WorkloadInstance 158 comparee *WorkloadInstance 159 shouldEq bool 160 name string 161 }{ 162 { 163 comparer: &WorkloadInstance{}, 164 comparee: &WorkloadInstance{}, 165 shouldEq: true, 166 name: "two null endpoints", 167 }, 168 { 169 comparer: exampleInstance.DeepCopy(), 170 comparee: exampleInstance.DeepCopy(), 171 shouldEq: true, 172 name: "exact same endpoints", 173 }, 174 { 175 comparer: exampleInstance.DeepCopy(), 176 comparee: differingAddr.DeepCopy(), 177 shouldEq: false, 178 name: "different Addresses", 179 }, 180 { 181 comparer: exampleInstance.DeepCopy(), 182 comparee: differingNetwork.DeepCopy(), 183 shouldEq: false, 184 name: "different Network", 185 }, 186 { 187 comparer: exampleInstance.DeepCopy(), 188 comparee: differingTLSMode.DeepCopy(), 189 shouldEq: false, 190 name: "different TLS Mode", 191 }, 192 { 193 comparer: exampleInstance.DeepCopy(), 194 comparee: differingLabels.DeepCopy(), 195 shouldEq: false, 196 name: "different Labels", 197 }, 198 { 199 comparer: exampleInstance.DeepCopy(), 200 comparee: differingServiceAccount.DeepCopy(), 201 shouldEq: false, 202 name: "different Service Account", 203 }, 204 { 205 comparer: exampleInstance.DeepCopy(), 206 comparee: differingLocality.DeepCopy(), 207 shouldEq: false, 208 name: "different Locality", 209 }, 210 { 211 comparer: exampleInstance.DeepCopy(), 212 comparee: differingLbWeight.DeepCopy(), 213 shouldEq: false, 214 name: "different LbWeight", 215 }, 216 } 217 218 for _, testCase := range cases { 219 t.Run("WorkloadInstancesEqual: "+testCase.name, func(t *testing.T) { 220 isEq := WorkloadInstancesEqual(testCase.comparer, testCase.comparee) 221 isEqReverse := WorkloadInstancesEqual(testCase.comparee, testCase.comparer) 222 223 if isEq != isEqReverse { 224 t.Errorf( 225 "returned different for reversing arguments for structs: %v , and %v", 226 testCase.comparer, 227 testCase.comparee, 228 ) 229 } 230 if isEq != testCase.shouldEq { 231 t.Errorf( 232 "equality of %v , and %v do not equal expected %t", 233 testCase.comparer, 234 testCase.comparee, 235 testCase.shouldEq, 236 ) 237 } 238 }) 239 } 240 } 241 242 func TestServicesEqual(t *testing.T) { 243 cases := []struct { 244 first *Service 245 other *Service 246 shouldEq bool 247 name string 248 }{ 249 { 250 first: nil, 251 other: &Service{}, 252 shouldEq: false, 253 name: "first nil services", 254 }, 255 { 256 first: &Service{}, 257 other: nil, 258 shouldEq: false, 259 name: "other nil services", 260 }, 261 { 262 first: nil, 263 other: nil, 264 shouldEq: true, 265 name: "both nil services", 266 }, 267 { 268 first: &Service{}, 269 other: &Service{}, 270 shouldEq: true, 271 name: "two empty services", 272 }, 273 { 274 first: &Service{ 275 Ports: port7000, 276 }, 277 other: &Service{ 278 Ports: port7442, 279 }, 280 shouldEq: false, 281 name: "different ports", 282 }, 283 { 284 first: &Service{ 285 Ports: twoMatchingPorts, 286 }, 287 other: &Service{ 288 Ports: twoMatchingPorts, 289 }, 290 shouldEq: true, 291 name: "matching ports", 292 }, 293 { 294 first: &Service{ 295 ServiceAccounts: []string{"sa1"}, 296 }, 297 other: &Service{ 298 ServiceAccounts: []string{"sa1"}, 299 }, 300 shouldEq: true, 301 name: "matching service accounts", 302 }, 303 { 304 first: &Service{ 305 ServiceAccounts: []string{"sa1"}, 306 }, 307 other: &Service{ 308 ServiceAccounts: []string{"sa2"}, 309 }, 310 shouldEq: false, 311 name: "different service accounts", 312 }, 313 { 314 first: &Service{ 315 ClusterVIPs: AddressMap{ 316 Addresses: map[cluster.ID][]string{ 317 "cluster-1": {"c1-vip1,c1-vip2"}, 318 "cluster-2": {"c2-vip1,c2-vip2"}, 319 }, 320 }, 321 }, 322 other: &Service{ 323 ClusterVIPs: AddressMap{ 324 Addresses: map[cluster.ID][]string{ 325 "cluster-1": {"c1-vip1,c1-vip2"}, 326 "cluster-2": {"c2-vip1,c2-vip2"}, 327 }, 328 }, 329 }, 330 shouldEq: true, 331 name: "matching cluster VIPs", 332 }, 333 { 334 first: &Service{ 335 ClusterVIPs: AddressMap{ 336 Addresses: map[cluster.ID][]string{ 337 "cluster-1": {"c1-vip1,c1-vip2"}, 338 }, 339 }, 340 }, 341 other: &Service{ 342 ClusterVIPs: AddressMap{ 343 Addresses: map[cluster.ID][]string{ 344 "cluster-1": {"c1-vip1,c1-vip2"}, 345 "cluster-2": {"c2-vip1,c2-vip2"}, 346 }, 347 }, 348 }, 349 shouldEq: false, 350 name: "different cluster VIPs", 351 }, 352 { 353 first: &Service{ 354 Attributes: ServiceAttributes{ 355 Name: "test", 356 Namespace: "testns", 357 Labels: map[string]string{ 358 "label-1": "value-1", 359 }, 360 }, 361 }, 362 other: &Service{ 363 Attributes: ServiceAttributes{ 364 Name: "test", 365 Namespace: "testns", 366 Labels: map[string]string{ 367 "label-1": "value-1", 368 }, 369 }, 370 }, 371 shouldEq: true, 372 name: "same service attributes", 373 }, 374 { 375 first: &Service{ 376 Attributes: ServiceAttributes{ 377 Name: "test", 378 Namespace: "testns", 379 Labels: map[string]string{ 380 "label-1": "value-1", 381 }, 382 }, 383 }, 384 other: &Service{ 385 Attributes: ServiceAttributes{ 386 Name: "test", 387 Namespace: "testns", 388 Labels: map[string]string{ 389 "label-1": "value-2", 390 }, 391 }, 392 }, 393 shouldEq: false, 394 name: "different service attributes", 395 }, 396 { 397 first: &Service{ 398 ClusterVIPs: AddressMap{ 399 Addresses: map[cluster.ID][]string{ 400 "cluster-1": {"c1-vip1,c1-vip2"}, 401 }, 402 }, 403 ServiceAccounts: []string{"sa-1", "sa-2"}, 404 Ports: twoMatchingPorts, 405 Attributes: ServiceAttributes{ 406 Name: "test", 407 Namespace: "testns", 408 Labels: map[string]string{ 409 "label-1": "value-1", 410 }, 411 }, 412 }, 413 other: &Service{ 414 ClusterVIPs: AddressMap{ 415 Addresses: map[cluster.ID][]string{ 416 "cluster-1": {"c1-vip1,c1-vip2"}, 417 "cluster-2": {"c2-vip1,c2-vip2"}, 418 }, 419 }, 420 Ports: twoMatchingPorts, 421 ServiceAccounts: []string{"sa-1", "sa-2"}, 422 Attributes: ServiceAttributes{ 423 Name: "test", 424 Namespace: "testns", 425 Labels: map[string]string{ 426 "label-1": "value-2", 427 }, 428 }, 429 }, 430 shouldEq: false, 431 name: "service with just label change", 432 }, 433 { 434 first: &Service{ 435 Attributes: ServiceAttributes{ 436 K8sAttributes: K8sAttributes{ 437 Type: "ClusterIP", 438 }, 439 }, 440 }, 441 other: &Service{ 442 Attributes: ServiceAttributes{ 443 K8sAttributes: K8sAttributes{ 444 Type: "NodePort", 445 }, 446 }, 447 }, 448 shouldEq: false, 449 name: "different types", 450 }, 451 { 452 first: &Service{ 453 Attributes: ServiceAttributes{ 454 K8sAttributes: K8sAttributes{ 455 ExternalName: "foo.com", 456 }, 457 }, 458 }, 459 other: &Service{ 460 Attributes: ServiceAttributes{ 461 K8sAttributes: K8sAttributes{ 462 ExternalName: "bar.com", 463 }, 464 }, 465 }, 466 shouldEq: false, 467 name: "different external names", 468 }, 469 { 470 first: &Service{ 471 Attributes: ServiceAttributes{ 472 K8sAttributes: K8sAttributes{ 473 NodeLocal: false, 474 }, 475 }, 476 }, 477 other: &Service{ 478 Attributes: ServiceAttributes{ 479 K8sAttributes: K8sAttributes{ 480 NodeLocal: true, 481 }, 482 }, 483 }, 484 shouldEq: false, 485 name: "different internal traffic policies", 486 }, 487 { 488 first: &Service{Hostname: host.Name("foo.com")}, 489 other: &Service{Hostname: host.Name("foo1.com")}, 490 shouldEq: false, 491 name: "different hostname", 492 }, 493 { 494 first: &Service{DefaultAddress: constants.UnspecifiedIPv6}, 495 other: &Service{DefaultAddress: constants.UnspecifiedIP}, 496 shouldEq: false, 497 name: "different default address", 498 }, 499 { 500 first: &Service{AutoAllocatedIPv4Address: "240.240.0.100"}, 501 other: &Service{AutoAllocatedIPv4Address: "240.240.0.101"}, 502 shouldEq: false, 503 name: "different auto allocated IPv4 addresses", 504 }, 505 { 506 first: &Service{AutoAllocatedIPv6Address: "2001:2::f0f0:e351"}, 507 other: &Service{AutoAllocatedIPv6Address: "2001:2::f0f1:e351"}, 508 shouldEq: false, 509 name: "different auto allocated IPv6 addresses", 510 }, 511 { 512 first: &Service{Resolution: ClientSideLB}, 513 other: &Service{Resolution: Passthrough}, 514 shouldEq: false, 515 name: "different resolution", 516 }, 517 { 518 first: &Service{MeshExternal: true}, 519 other: &Service{MeshExternal: false}, 520 shouldEq: false, 521 name: "different mesh external setting", 522 }, 523 } 524 525 for _, testCase := range cases { 526 t.Run("ServicesEqual: "+testCase.name, func(t *testing.T) { 527 isEq := testCase.first.Equals(testCase.other) 528 if isEq != testCase.shouldEq { 529 t.Errorf( 530 "equality of %v , and %v are not equal expected %t", 531 testCase.first, 532 testCase.other, 533 testCase.shouldEq, 534 ) 535 } 536 }) 537 } 538 } 539 540 func BenchmarkBuildSubsetKey(b *testing.B) { 541 for n := 0; n < b.N; n++ { 542 _ = BuildSubsetKey(TrafficDirectionInbound, "v1", "someHost", 80) 543 } 544 } 545 546 func BenchmarkServiceDeepCopy(b *testing.B) { 547 svc1 := buildHTTPService("test.com", visibility.Public, "10.10.0.1", "default", 80, 8080, 9090, 9999) 548 svc1.ServiceAccounts = []string{"sa1"} 549 svc1.ClusterVIPs = AddressMap{ 550 Addresses: map[cluster.ID][]string{ 551 "cluster1": {"10.10.0.1"}, 552 "cluster2": {"10.10.0.2"}, 553 }, 554 } 555 for n := 0; n < b.N; n++ { 556 _ = svc1.DeepCopy() 557 } 558 } 559 560 func TestFuzzServiceDeepCopy(t *testing.T) { 561 fuzzer := fuzz.New() 562 originalSvc := &Service{} 563 fuzzer.Fuzz(originalSvc) 564 copied := originalSvc.DeepCopy() 565 opts := []cmp.Option{cmp.AllowUnexported(), cmpopts.IgnoreFields(AddressMap{}, "mutex")} 566 if !cmp.Equal(originalSvc, copied, opts...) { 567 diff := cmp.Diff(originalSvc, copied, opts...) 568 t.Errorf("unexpected diff %v", diff) 569 } 570 } 571 572 func TestParseSubsetKeyHostname(t *testing.T) { 573 tests := []struct { 574 in, out string 575 }{ 576 {"outbound|80|subset|host.com", "host.com"}, 577 {"outbound|80|subset|", ""}, 578 {"|||", ""}, 579 {"||||||", ""}, 580 {"", ""}, 581 {"outbound_.80_._.test.local", "test.local"}, 582 } 583 for _, tt := range tests { 584 t.Run(tt.in, func(t *testing.T) { 585 assert.Equal(t, ParseSubsetKeyHostname(tt.in), tt.out) 586 }) 587 } 588 }