istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/networking/core/cluster_builder_test.go (about) 1 // Copyright Istio Authors. All Rights Reserved. 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 core 16 17 import ( 18 "encoding/json" 19 "fmt" 20 "reflect" 21 "sort" 22 "strings" 23 "sync" 24 "testing" 25 "time" 26 27 cluster "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" 28 core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 29 endpoint "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3" 30 tls "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" 31 http "github.com/envoyproxy/go-control-plane/envoy/extensions/upstreams/http/v3" 32 "github.com/google/go-cmp/cmp" 33 "google.golang.org/protobuf/testing/protocmp" 34 "google.golang.org/protobuf/types/known/durationpb" 35 "google.golang.org/protobuf/types/known/structpb" 36 wrappers "google.golang.org/protobuf/types/known/wrapperspb" 37 38 meshconfig "istio.io/api/mesh/v1alpha1" 39 networking "istio.io/api/networking/v1alpha3" 40 "istio.io/api/type/v1beta1" 41 "istio.io/istio/pilot/pkg/features" 42 "istio.io/istio/pilot/pkg/model" 43 "istio.io/istio/pilot/pkg/networking/util" 44 authn_model "istio.io/istio/pilot/pkg/security/model" 45 "istio.io/istio/pilot/pkg/serviceregistry/provider" 46 "istio.io/istio/pilot/pkg/xds/endpoints" 47 xdsfilters "istio.io/istio/pilot/pkg/xds/filters" 48 v3 "istio.io/istio/pilot/pkg/xds/v3" 49 "istio.io/istio/pilot/test/xdstest" 50 istiocluster "istio.io/istio/pkg/cluster" 51 "istio.io/istio/pkg/config" 52 "istio.io/istio/pkg/config/constants" 53 "istio.io/istio/pkg/config/host" 54 "istio.io/istio/pkg/config/labels" 55 "istio.io/istio/pkg/config/protocol" 56 "istio.io/istio/pkg/config/schema/gvk" 57 "istio.io/istio/pkg/network" 58 "istio.io/istio/pkg/security" 59 "istio.io/istio/pkg/test" 60 "istio.io/istio/pkg/test/util/assert" 61 ) 62 63 func TestApplyDestinationRule(t *testing.T) { 64 servicePort := model.PortList{ 65 &model.Port{ 66 Name: "default", 67 Port: 8080, 68 Protocol: protocol.HTTP, 69 }, 70 &model.Port{ 71 Name: "auto", 72 Port: 9090, 73 Protocol: protocol.Unsupported, 74 }, 75 } 76 http2ServicePort := model.PortList{ 77 &model.Port{ 78 Name: "default", 79 Port: 8080, 80 Protocol: protocol.HTTP2, 81 }, 82 &model.Port{ 83 Name: "auto", 84 Port: 9090, 85 Protocol: protocol.Unsupported, 86 }, 87 } 88 service := &model.Service{ 89 Hostname: host.Name("foo.default.svc.cluster.local"), 90 Ports: servicePort, 91 Resolution: model.ClientSideLB, 92 Attributes: model.ServiceAttributes{ 93 Namespace: TestServiceNamespace, 94 }, 95 } 96 http2Service := &model.Service{ 97 Hostname: host.Name("foo.default.svc.cluster.local"), 98 Ports: http2ServicePort, 99 Resolution: model.ClientSideLB, 100 Attributes: model.ServiceAttributes{ 101 Namespace: TestServiceNamespace, 102 }, 103 } 104 105 cases := []struct { 106 name string 107 cluster *cluster.Cluster 108 clusterMode ClusterMode 109 service *model.Service 110 port *model.Port 111 proxyView model.ProxyView 112 destRule *networking.DestinationRule 113 meshConfig *meshconfig.MeshConfig 114 expectedSubsetClusters []*cluster.Cluster 115 }{ 116 // TODO(ramaraochavali): Add more tests to cover additional conditions. 117 { 118 name: "nil destination rule", 119 cluster: &cluster.Cluster{}, 120 clusterMode: DefaultClusterMode, 121 service: &model.Service{}, 122 port: &model.Port{}, 123 proxyView: model.ProxyViewAll, 124 destRule: nil, 125 expectedSubsetClusters: []*cluster.Cluster{}, 126 }, 127 { 128 name: "destination rule with subsets", 129 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 130 clusterMode: DefaultClusterMode, 131 service: service, 132 port: servicePort[0], 133 proxyView: model.ProxyViewAll, 134 destRule: &networking.DestinationRule{ 135 Host: "foo.default.svc.cluster.local", 136 Subsets: []*networking.Subset{ 137 { 138 Name: "foobar", 139 Labels: map[string]string{"foo": "bar"}, 140 }, 141 }, 142 }, 143 expectedSubsetClusters: []*cluster.Cluster{ 144 { 145 Name: "outbound|8080|foobar|foo.default.svc.cluster.local", 146 ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}, 147 EdsClusterConfig: &cluster.Cluster_EdsClusterConfig{ 148 ServiceName: "outbound|8080|foobar|foo.default.svc.cluster.local", 149 }, 150 }, 151 }, 152 }, 153 { 154 name: "destination rule with pass through subsets", 155 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_STATIC}}, 156 clusterMode: DefaultClusterMode, 157 service: service, 158 port: servicePort[0], 159 proxyView: model.ProxyViewAll, 160 destRule: &networking.DestinationRule{ 161 Host: "foo.default.svc.cluster.local", 162 Subsets: []*networking.Subset{ 163 { 164 Name: "foobar", 165 Labels: map[string]string{"foo": "bar"}, 166 TrafficPolicy: &networking.TrafficPolicy{ 167 LoadBalancer: &networking.LoadBalancerSettings{ 168 LbPolicy: &networking.LoadBalancerSettings_Simple{Simple: networking.LoadBalancerSettings_PASSTHROUGH}, 169 }, 170 }, 171 }, 172 }, 173 }, 174 expectedSubsetClusters: []*cluster.Cluster{ 175 { 176 Name: "outbound|8080|foobar|foo.default.svc.cluster.local", 177 ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_ORIGINAL_DST}, 178 }, 179 }, 180 }, 181 { 182 name: "destination rule static with pass", 183 cluster: &cluster.Cluster{ 184 Name: "foo", 185 ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_STATIC}, 186 LoadAssignment: &endpoint.ClusterLoadAssignment{}, 187 }, 188 clusterMode: DefaultClusterMode, 189 service: service, 190 port: servicePort[0], 191 proxyView: model.ProxyViewAll, 192 destRule: &networking.DestinationRule{ 193 Host: "foo.default.svc.cluster.local", 194 TrafficPolicy: &networking.TrafficPolicy{ 195 LoadBalancer: &networking.LoadBalancerSettings{ 196 LbPolicy: &networking.LoadBalancerSettings_Simple{Simple: networking.LoadBalancerSettings_PASSTHROUGH}, 197 }, 198 }, 199 }, 200 }, 201 { 202 name: "destination rule with subsets for SniDnat cluster", 203 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 204 clusterMode: SniDnatClusterMode, 205 service: service, 206 port: servicePort[0], 207 proxyView: model.ProxyViewAll, 208 destRule: &networking.DestinationRule{ 209 Host: "foo.default.svc.cluster.local", 210 Subsets: []*networking.Subset{ 211 { 212 Name: "foobar", 213 Labels: map[string]string{"foo": "bar"}, 214 }, 215 }, 216 }, 217 expectedSubsetClusters: []*cluster.Cluster{ 218 { 219 Name: "outbound_.8080_.foobar_.foo.default.svc.cluster.local", 220 ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}, 221 EdsClusterConfig: &cluster.Cluster_EdsClusterConfig{ 222 ServiceName: "outbound_.8080_.foobar_.foo.default.svc.cluster.local", 223 }, 224 }, 225 }, 226 }, 227 { 228 name: "destination rule with subset traffic policy", 229 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 230 clusterMode: DefaultClusterMode, 231 service: service, 232 port: servicePort[0], 233 proxyView: model.ProxyViewAll, 234 destRule: &networking.DestinationRule{ 235 Host: "foo.default.svc.cluster.local", 236 Subsets: []*networking.Subset{ 237 { 238 Name: "foobar", 239 Labels: map[string]string{"foo": "bar"}, 240 TrafficPolicy: &networking.TrafficPolicy{ 241 ConnectionPool: &networking.ConnectionPoolSettings{ 242 Http: &networking.ConnectionPoolSettings_HTTPSettings{ 243 MaxRetries: 10, 244 }, 245 }, 246 }, 247 }, 248 }, 249 }, 250 expectedSubsetClusters: []*cluster.Cluster{ 251 { 252 Name: "outbound|8080|foobar|foo.default.svc.cluster.local", 253 ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}, 254 EdsClusterConfig: &cluster.Cluster_EdsClusterConfig{ 255 ServiceName: "outbound|8080|foobar|foo.default.svc.cluster.local", 256 }, 257 CircuitBreakers: &cluster.CircuitBreakers{ 258 Thresholds: []*cluster.CircuitBreakers_Thresholds{ 259 { 260 MaxRetries: &wrappers.UInt32Value{ 261 Value: 10, 262 }, 263 }, 264 }, 265 }, 266 }, 267 }, 268 }, 269 { 270 name: "destination rule with subset traffic policy and alt statname", 271 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 272 clusterMode: DefaultClusterMode, 273 service: service, 274 port: servicePort[0], 275 proxyView: model.ProxyViewAll, 276 destRule: &networking.DestinationRule{ 277 Host: "foo.default.svc.cluster.local", 278 Subsets: []*networking.Subset{ 279 { 280 Name: "foobar", 281 Labels: map[string]string{"foo": "bar"}, 282 TrafficPolicy: &networking.TrafficPolicy{ 283 ConnectionPool: &networking.ConnectionPoolSettings{ 284 Http: &networking.ConnectionPoolSettings_HTTPSettings{ 285 MaxRetries: 10, 286 }, 287 }, 288 }, 289 }, 290 }, 291 }, 292 meshConfig: &meshconfig.MeshConfig{ 293 OutboundClusterStatName: "%SERVICE%_%SUBSET_NAME%_%SERVICE_PORT_NAME%_%SERVICE_PORT%", 294 InboundTrafficPolicy: &meshconfig.MeshConfig_InboundTrafficPolicy{}, 295 EnableAutoMtls: &wrappers.BoolValue{ 296 Value: false, 297 }, 298 }, 299 expectedSubsetClusters: []*cluster.Cluster{ 300 { 301 Name: "outbound|8080|foobar|foo.default.svc.cluster.local", 302 ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}, 303 EdsClusterConfig: &cluster.Cluster_EdsClusterConfig{ 304 ServiceName: "outbound|8080|foobar|foo.default.svc.cluster.local", 305 }, 306 CircuitBreakers: &cluster.CircuitBreakers{ 307 Thresholds: []*cluster.CircuitBreakers_Thresholds{ 308 { 309 MaxRetries: &wrappers.UInt32Value{ 310 Value: 10, 311 }, 312 }, 313 }, 314 }, 315 AltStatName: "foo.default.svc.cluster.local_foobar_default_8080", 316 }, 317 }, 318 }, 319 { 320 name: "destination rule with use client protocol traffic policy", 321 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 322 clusterMode: DefaultClusterMode, 323 service: service, 324 port: servicePort[0], 325 proxyView: model.ProxyViewAll, 326 destRule: &networking.DestinationRule{ 327 Host: "foo.default.svc.cluster.local", 328 TrafficPolicy: &networking.TrafficPolicy{ 329 ConnectionPool: &networking.ConnectionPoolSettings{ 330 Http: &networking.ConnectionPoolSettings_HTTPSettings{ 331 MaxRetries: 10, 332 UseClientProtocol: true, 333 }, 334 }, 335 }, 336 }, 337 expectedSubsetClusters: []*cluster.Cluster{}, 338 }, 339 { 340 name: "destination rule with maxRequestsPerConnection", 341 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 342 clusterMode: DefaultClusterMode, 343 service: service, 344 port: servicePort[0], 345 proxyView: model.ProxyViewAll, 346 destRule: &networking.DestinationRule{ 347 Host: "foo.default.svc.cluster.local", 348 TrafficPolicy: &networking.TrafficPolicy{ 349 ConnectionPool: &networking.ConnectionPoolSettings{ 350 Http: &networking.ConnectionPoolSettings_HTTPSettings{ 351 MaxRetries: 10, 352 MaxRequestsPerConnection: 10, 353 }, 354 }, 355 }, 356 }, 357 expectedSubsetClusters: []*cluster.Cluster{}, 358 }, 359 { 360 name: "destination rule with maxConcurrentStreams", 361 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 362 clusterMode: DefaultClusterMode, 363 service: http2Service, 364 port: http2ServicePort[0], 365 proxyView: model.ProxyViewAll, 366 destRule: &networking.DestinationRule{ 367 Host: "foo.default.svc.cluster.local", 368 TrafficPolicy: &networking.TrafficPolicy{ 369 ConnectionPool: &networking.ConnectionPoolSettings{ 370 Http: &networking.ConnectionPoolSettings_HTTPSettings{ 371 MaxRetries: 10, 372 MaxConcurrentStreams: 10, 373 }, 374 }, 375 }, 376 }, 377 expectedSubsetClusters: []*cluster.Cluster{}, 378 }, 379 { 380 name: "subset without labels in both", 381 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_STRICT_DNS}}, 382 clusterMode: DefaultClusterMode, 383 service: &model.Service{ 384 Hostname: host.Name("foo.example.com"), 385 Ports: servicePort, 386 Resolution: model.DNSLB, 387 Attributes: model.ServiceAttributes{ 388 Namespace: TestServiceNamespace, 389 }, 390 }, 391 port: servicePort[0], 392 proxyView: model.ProxyViewAll, 393 destRule: &networking.DestinationRule{ 394 Host: "foo.example.com", 395 Subsets: []*networking.Subset{{Name: "v1"}}, 396 }, 397 expectedSubsetClusters: []*cluster.Cluster{{ 398 Name: "outbound|8080|v1|foo.example.com", 399 ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_STRICT_DNS}, 400 }}, 401 }, 402 { 403 name: "subset without labels in dest rule", 404 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_STRICT_DNS}}, 405 clusterMode: DefaultClusterMode, 406 service: &model.Service{ 407 Hostname: host.Name("foo.example.com"), 408 Ports: servicePort, 409 Resolution: model.DNSLB, 410 Attributes: model.ServiceAttributes{ 411 Namespace: TestServiceNamespace, 412 Labels: map[string]string{"foo": "bar"}, 413 }, 414 }, 415 port: servicePort[0], 416 proxyView: model.ProxyViewAll, 417 destRule: &networking.DestinationRule{ 418 Host: "foo.example.com", 419 Subsets: []*networking.Subset{{Name: "v1"}}, 420 }, 421 expectedSubsetClusters: []*cluster.Cluster{{ 422 Name: "outbound|8080|v1|foo.example.com", 423 ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_STRICT_DNS}, 424 }}, 425 }, 426 { 427 name: "subset with labels in both", 428 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_STRICT_DNS}}, 429 clusterMode: DefaultClusterMode, 430 service: &model.Service{ 431 Hostname: host.Name("foo.example.com"), 432 Ports: servicePort, 433 Resolution: model.DNSLB, 434 Attributes: model.ServiceAttributes{ 435 Namespace: TestServiceNamespace, 436 Labels: map[string]string{"foo": "bar"}, 437 }, 438 }, 439 port: servicePort[0], 440 proxyView: model.ProxyViewAll, 441 destRule: &networking.DestinationRule{ 442 Host: "foo.example.com", 443 Subsets: []*networking.Subset{{ 444 Name: "v1", 445 Labels: map[string]string{"foo": "bar"}, 446 }}, 447 }, 448 expectedSubsetClusters: []*cluster.Cluster{{ 449 Name: "outbound|8080|v1|foo.example.com", 450 ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_STRICT_DNS}, 451 }}, 452 }, 453 { 454 name: "subset with labels in both, not matching", 455 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_STRICT_DNS}}, 456 clusterMode: DefaultClusterMode, 457 service: &model.Service{ 458 Hostname: host.Name("foo.example.com"), 459 Ports: servicePort, 460 Resolution: model.DNSLB, 461 Attributes: model.ServiceAttributes{ 462 Namespace: TestServiceNamespace, 463 Labels: map[string]string{"foo": "bar"}, 464 }, 465 }, 466 port: servicePort[0], 467 proxyView: model.ProxyViewAll, 468 destRule: &networking.DestinationRule{ 469 Host: "foo.example.com", 470 Subsets: []*networking.Subset{{ 471 Name: "v1", 472 Labels: map[string]string{"foo": "not-match"}, 473 }}, 474 }, 475 expectedSubsetClusters: []*cluster.Cluster{}, 476 }, 477 { 478 name: "subset without labels in both and resolution of DNS_ROUND_ROBIN", 479 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_LOGICAL_DNS}}, 480 clusterMode: DefaultClusterMode, 481 service: &model.Service{ 482 Hostname: host.Name("foo.example.com"), 483 Ports: servicePort, 484 Resolution: model.DNSRoundRobinLB, 485 Attributes: model.ServiceAttributes{ 486 Namespace: TestServiceNamespace, 487 }, 488 }, 489 port: servicePort[0], 490 proxyView: model.ProxyViewAll, 491 destRule: &networking.DestinationRule{ 492 Host: "foo.example.com", 493 Subsets: []*networking.Subset{{Name: "v1"}}, 494 }, 495 expectedSubsetClusters: []*cluster.Cluster{{ 496 Name: "outbound|8080|v1|foo.example.com", 497 ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_LOGICAL_DNS}, 498 }}, 499 }, 500 { 501 name: "subset without labels in dest rule and a resolution of DNS_ROUND_ROBIN", 502 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_LOGICAL_DNS}}, 503 clusterMode: DefaultClusterMode, 504 service: &model.Service{ 505 Hostname: host.Name("foo.example.com"), 506 Ports: servicePort, 507 Resolution: model.DNSRoundRobinLB, 508 Attributes: model.ServiceAttributes{ 509 Namespace: TestServiceNamespace, 510 Labels: map[string]string{"foo": "bar"}, 511 }, 512 }, 513 port: servicePort[0], 514 proxyView: model.ProxyViewAll, 515 destRule: &networking.DestinationRule{ 516 Host: "foo.example.com", 517 Subsets: []*networking.Subset{{Name: "v1"}}, 518 }, 519 expectedSubsetClusters: []*cluster.Cluster{{ 520 Name: "outbound|8080|v1|foo.example.com", 521 ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_LOGICAL_DNS}, 522 }}, 523 }, 524 { 525 name: "subset with labels in both", 526 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_LOGICAL_DNS}}, 527 clusterMode: DefaultClusterMode, 528 service: &model.Service{ 529 Hostname: host.Name("foo.example.com"), 530 Ports: servicePort, 531 Resolution: model.DNSRoundRobinLB, 532 Attributes: model.ServiceAttributes{ 533 Namespace: TestServiceNamespace, 534 Labels: map[string]string{"foo": "bar"}, 535 }, 536 }, 537 port: servicePort[0], 538 proxyView: model.ProxyViewAll, 539 destRule: &networking.DestinationRule{ 540 Host: "foo.example.com", 541 Subsets: []*networking.Subset{{ 542 Name: "v1", 543 Labels: map[string]string{"foo": "bar"}, 544 }}, 545 }, 546 expectedSubsetClusters: []*cluster.Cluster{{ 547 Name: "outbound|8080|v1|foo.example.com", 548 ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_LOGICAL_DNS}, 549 }}, 550 }, 551 { 552 name: "subset with labels in both, not matching", 553 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_LOGICAL_DNS}}, 554 clusterMode: DefaultClusterMode, 555 service: &model.Service{ 556 Hostname: host.Name("foo.example.com"), 557 Ports: servicePort, 558 Resolution: model.DNSRoundRobinLB, 559 Attributes: model.ServiceAttributes{ 560 Namespace: TestServiceNamespace, 561 Labels: map[string]string{"foo": "bar"}, 562 }, 563 }, 564 port: servicePort[0], 565 proxyView: model.ProxyViewAll, 566 destRule: &networking.DestinationRule{ 567 Host: "foo.example.com", 568 Subsets: []*networking.Subset{{ 569 Name: "v1", 570 Labels: map[string]string{"foo": "not-match"}, 571 }}, 572 }, 573 expectedSubsetClusters: []*cluster.Cluster{}, 574 }, 575 { 576 name: "destination rule with tls mode SIMPLE", 577 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 578 clusterMode: DefaultClusterMode, 579 service: service, 580 port: servicePort[0], 581 proxyView: model.ProxyViewAll, 582 destRule: &networking.DestinationRule{ 583 Host: "foo.default.svc.cluster.local", 584 TrafficPolicy: &networking.TrafficPolicy{ 585 Tls: &networking.ClientTLSSettings{Mode: networking.ClientTLSSettings_SIMPLE}, 586 }, 587 }, 588 expectedSubsetClusters: []*cluster.Cluster{}, 589 }, 590 { 591 name: "destination rule with tls mode MUTUAL", 592 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 593 clusterMode: DefaultClusterMode, 594 service: service, 595 port: servicePort[0], 596 proxyView: model.ProxyViewAll, 597 destRule: &networking.DestinationRule{ 598 Host: "foo.default.svc.cluster.local", 599 TrafficPolicy: &networking.TrafficPolicy{ 600 Tls: &networking.ClientTLSSettings{Mode: networking.ClientTLSSettings_MUTUAL}, 601 }, 602 }, 603 expectedSubsetClusters: []*cluster.Cluster{}, 604 }, 605 { 606 name: "destination rule with tls mode ISTIO_MUTUAL", 607 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 608 clusterMode: DefaultClusterMode, 609 service: service, 610 port: servicePort[0], 611 proxyView: model.ProxyViewAll, 612 destRule: &networking.DestinationRule{ 613 Host: "foo.default.svc.cluster.local", 614 TrafficPolicy: &networking.TrafficPolicy{ 615 Tls: &networking.ClientTLSSettings{Mode: networking.ClientTLSSettings_ISTIO_MUTUAL}, 616 }, 617 }, 618 expectedSubsetClusters: []*cluster.Cluster{}, 619 }, 620 { 621 name: "port level destination rule with tls mode SIMPLE", 622 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 623 clusterMode: DefaultClusterMode, 624 service: service, 625 port: servicePort[0], 626 proxyView: model.ProxyViewAll, 627 destRule: &networking.DestinationRule{ 628 Host: "foo.default.svc.cluster.local", 629 TrafficPolicy: &networking.TrafficPolicy{ 630 PortLevelSettings: []*networking.TrafficPolicy_PortTrafficPolicy{ 631 { 632 Port: &networking.PortSelector{Number: uint32(servicePort[0].Port)}, 633 Tls: &networking.ClientTLSSettings{Mode: networking.ClientTLSSettings_SIMPLE}, 634 }, 635 }, 636 }, 637 }, 638 expectedSubsetClusters: []*cluster.Cluster{}, 639 }, 640 { 641 name: "port level destination rule with tls mode MUTUAL", 642 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 643 clusterMode: DefaultClusterMode, 644 service: service, 645 port: servicePort[0], 646 proxyView: model.ProxyViewAll, 647 destRule: &networking.DestinationRule{ 648 Host: "foo.default.svc.cluster.local", 649 TrafficPolicy: &networking.TrafficPolicy{ 650 PortLevelSettings: []*networking.TrafficPolicy_PortTrafficPolicy{ 651 { 652 Port: &networking.PortSelector{Number: uint32(servicePort[0].Port)}, 653 Tls: &networking.ClientTLSSettings{Mode: networking.ClientTLSSettings_MUTUAL}, 654 }, 655 }, 656 }, 657 }, 658 expectedSubsetClusters: []*cluster.Cluster{}, 659 }, 660 { 661 name: "port level destination rule with tls mode ISTIO_MUTUAL", 662 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 663 clusterMode: DefaultClusterMode, 664 service: service, 665 port: servicePort[0], 666 proxyView: model.ProxyViewAll, 667 destRule: &networking.DestinationRule{ 668 Host: "foo.default.svc.cluster.local", 669 TrafficPolicy: &networking.TrafficPolicy{ 670 PortLevelSettings: []*networking.TrafficPolicy_PortTrafficPolicy{ 671 { 672 Port: &networking.PortSelector{Number: uint32(servicePort[0].Port)}, 673 Tls: &networking.ClientTLSSettings{Mode: networking.ClientTLSSettings_ISTIO_MUTUAL}, 674 }, 675 }, 676 }, 677 }, 678 expectedSubsetClusters: []*cluster.Cluster{}, 679 }, 680 { 681 name: "subset destination rule with tls mode SIMPLE", 682 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 683 clusterMode: DefaultClusterMode, 684 service: service, 685 port: servicePort[0], 686 proxyView: model.ProxyViewAll, 687 destRule: &networking.DestinationRule{ 688 Host: "foo.default.svc.cluster.local", 689 Subsets: []*networking.Subset{ 690 { 691 Name: "v1", 692 TrafficPolicy: &networking.TrafficPolicy{ 693 Tls: &networking.ClientTLSSettings{Mode: networking.ClientTLSSettings_SIMPLE}, 694 }, 695 }, 696 }, 697 }, 698 expectedSubsetClusters: []*cluster.Cluster{{ 699 Name: "outbound|8080|v1|foo.default.svc.cluster.local", 700 ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}, 701 EdsClusterConfig: &cluster.Cluster_EdsClusterConfig{ServiceName: "outbound|8080|v1|foo.default.svc.cluster.local"}, 702 }}, 703 }, 704 { 705 name: "subset destination rule with tls mode MUTUAL", 706 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 707 clusterMode: DefaultClusterMode, 708 service: service, 709 port: servicePort[0], 710 proxyView: model.ProxyViewAll, 711 destRule: &networking.DestinationRule{ 712 Host: "foo.default.svc.cluster.local", 713 Subsets: []*networking.Subset{ 714 { 715 Name: "v1", 716 TrafficPolicy: &networking.TrafficPolicy{ 717 Tls: &networking.ClientTLSSettings{Mode: networking.ClientTLSSettings_MUTUAL}, 718 }, 719 }, 720 }, 721 }, 722 expectedSubsetClusters: []*cluster.Cluster{{ 723 Name: "outbound|8080|v1|foo.default.svc.cluster.local", 724 ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}, 725 EdsClusterConfig: &cluster.Cluster_EdsClusterConfig{ServiceName: "outbound|8080|v1|foo.default.svc.cluster.local"}, 726 }}, 727 }, 728 { 729 name: "subset destination rule with tls mode MUTUAL", 730 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 731 clusterMode: DefaultClusterMode, 732 service: service, 733 port: servicePort[0], 734 proxyView: model.ProxyViewAll, 735 destRule: &networking.DestinationRule{ 736 Host: "foo.default.svc.cluster.local", 737 Subsets: []*networking.Subset{ 738 { 739 Name: "v1", 740 TrafficPolicy: &networking.TrafficPolicy{ 741 Tls: &networking.ClientTLSSettings{Mode: networking.ClientTLSSettings_ISTIO_MUTUAL}, 742 }, 743 }, 744 }, 745 }, 746 expectedSubsetClusters: []*cluster.Cluster{{ 747 Name: "outbound|8080|v1|foo.default.svc.cluster.local", 748 ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}, 749 EdsClusterConfig: &cluster.Cluster_EdsClusterConfig{ServiceName: "outbound|8080|v1|foo.default.svc.cluster.local"}, 750 }}, 751 }, 752 } 753 754 for _, tt := range cases { 755 t.Run(tt.name, func(t *testing.T) { 756 instances := []*model.ServiceInstance{ 757 { 758 Service: tt.service, 759 ServicePort: tt.port, 760 Endpoint: &model.IstioEndpoint{ 761 ServicePortName: tt.port.Name, 762 Address: "192.168.1.1", 763 EndpointPort: 10001, 764 Locality: model.Locality{ 765 ClusterID: "", 766 Label: "region1/zone1/subzone1", 767 }, 768 Labels: tt.service.Attributes.Labels, 769 TLSMode: model.IstioMutualTLSModeLabel, 770 }, 771 }, 772 } 773 774 var cfg *config.Config 775 if tt.destRule != nil { 776 cfg = &config.Config{ 777 Meta: config.Meta{ 778 GroupVersionKind: gvk.DestinationRule, 779 Name: "acme", 780 Namespace: "default", 781 }, 782 Spec: tt.destRule, 783 } 784 } 785 cg := NewConfigGenTest(t, TestOptions{ 786 Instances: instances, 787 ConfigPointers: []*config.Config{cfg}, 788 Services: []*model.Service{tt.service}, 789 MeshConfig: tt.meshConfig, 790 }) 791 proxy := cg.SetupProxy(nil) 792 cb := NewClusterBuilder(proxy, &model.PushRequest{Push: cg.PushContext()}, nil) 793 794 tt.cluster.CommonLbConfig = &cluster.Cluster_CommonLbConfig{} 795 796 ec := newClusterWrapper(tt.cluster) 797 // Set cluster wrapping with HTTP2 options if port protocol is HTTP2 798 if tt.port.Protocol == protocol.HTTP2 { 799 setH2Options(ec) 800 } 801 destRule := proxy.SidecarScope.DestinationRule(model.TrafficDirectionOutbound, proxy, tt.service.Hostname) 802 eb := endpoints.NewCDSEndpointBuilder(proxy, cb.req.Push, tt.cluster.Name, 803 model.TrafficDirectionOutbound, "", tt.service.Hostname, tt.port.Port, 804 tt.service, destRule) 805 subsetClusters := cb.applyDestinationRule(ec, tt.clusterMode, tt.service, tt.port, eb, destRule.GetRule(), nil) 806 if len(subsetClusters) != len(tt.expectedSubsetClusters) { 807 t.Fatalf("Unexpected subset clusters want %v, got %v. keys=%v", 808 len(tt.expectedSubsetClusters), len(subsetClusters), xdstest.MapKeys(xdstest.ExtractClusters(subsetClusters))) 809 } 810 if len(tt.expectedSubsetClusters) > 0 { 811 compareClusters(t, tt.expectedSubsetClusters[0], subsetClusters[0]) 812 } 813 // Validate that use client protocol configures cluster correctly. 814 if tt.destRule != nil && tt.destRule.TrafficPolicy != nil && tt.destRule.TrafficPolicy.GetConnectionPool().GetHttp().GetUseClientProtocol() { 815 if ec.httpProtocolOptions == nil { 816 t.Errorf("Expected cluster %s to have http protocol options but not found", tt.cluster.Name) 817 } 818 if ec.httpProtocolOptions.UpstreamProtocolOptions == nil && 819 ec.httpProtocolOptions.GetUseDownstreamProtocolConfig() == nil { 820 t.Errorf("Expected cluster %s to have downstream protocol options but not found", tt.cluster.Name) 821 } 822 } 823 824 // Validate that max requests per connection configures cluster correctly. 825 if tt.destRule != nil && tt.destRule.TrafficPolicy != nil && tt.destRule.TrafficPolicy.GetConnectionPool().GetHttp().GetMaxRequestsPerConnection() > 0 { 826 if ec.httpProtocolOptions == nil { 827 t.Errorf("Expected cluster %s to have http protocol options but not found", tt.cluster.Name) 828 } 829 if ec.httpProtocolOptions.CommonHttpProtocolOptions == nil { 830 t.Errorf("Expected cluster %s to have common http protocol options but not found", tt.cluster.Name) 831 } 832 if ec.httpProtocolOptions.CommonHttpProtocolOptions.MaxRequestsPerConnection.GetValue() != 833 uint32(tt.destRule.TrafficPolicy.GetConnectionPool().GetHttp().MaxRequestsPerConnection) { 834 t.Errorf("Unexpected max_requests_per_connection found") 835 } 836 } 837 838 if tt.destRule.GetTrafficPolicy().GetConnectionPool().GetHttp().GetMaxConcurrentStreams() > 0 { 839 if ec.httpProtocolOptions == nil { 840 t.Errorf("Expected cluster %s to have http protocol options but not found", tt.cluster.Name) 841 } 842 if ec.httpProtocolOptions.GetExplicitHttpConfig() == nil { 843 t.Errorf("Expected cluster %s to have explicit http config but not found", tt.cluster.Name) 844 } 845 if ec.httpProtocolOptions.GetExplicitHttpConfig().GetHttp2ProtocolOptions() == nil { 846 t.Errorf("Expected cluster %s to have HTTP2 protocol options but not found", tt.cluster.Name) 847 } 848 if ec.httpProtocolOptions.GetExplicitHttpConfig().GetHttp2ProtocolOptions().GetMaxConcurrentStreams().GetValue() != 849 uint32(tt.destRule.TrafficPolicy.GetConnectionPool().GetHttp().MaxConcurrentStreams) { 850 t.Errorf("Unexpected max_concurrent_streams found") 851 } 852 } 853 854 // Validate that alpn_override is correctly configured on cluster given a TLS mode. 855 if tt.destRule.GetTrafficPolicy().GetTls() != nil { 856 verifyALPNOverride(t, tt.cluster.Metadata, tt.destRule.TrafficPolicy.Tls.Mode) 857 } 858 if len(tt.destRule.GetSubsets()) > 0 { 859 for _, c := range subsetClusters { 860 var subsetName string 861 if tt.clusterMode == DefaultClusterMode { 862 subsetName = strings.Split(c.Name, "|")[2] 863 } else { 864 subsetName = strings.Split(c.Name, ".")[2] 865 } 866 for _, subset := range tt.destRule.Subsets { 867 if subset.Name == subsetName { 868 if subset.GetTrafficPolicy().GetTls() != nil { 869 verifyALPNOverride(t, c.Metadata, subset.TrafficPolicy.Tls.Mode) 870 } 871 } 872 } 873 } 874 } 875 876 // Validate that ORIGINAL_DST cluster does not have load assignments 877 for _, subset := range subsetClusters { 878 if subset.GetType() == cluster.Cluster_ORIGINAL_DST && subset.GetLoadAssignment() != nil { 879 t.Errorf("Passthrough subsets should not have load assignments") 880 } 881 } 882 if ec.cluster.GetType() == cluster.Cluster_ORIGINAL_DST && ec.cluster.GetLoadAssignment() != nil { 883 t.Errorf("Passthrough should not have load assignments") 884 } 885 }) 886 } 887 } 888 889 func compareClusters(t *testing.T, ec *cluster.Cluster, gc *cluster.Cluster) { 890 // TODO(ramaraochavali): Expand the comparison to more fields. 891 t.Helper() 892 if ec.Name != gc.Name { 893 t.Errorf("Unexpected cluster name want %s, got %s", ec.Name, gc.Name) 894 } 895 if ec.GetType() != gc.GetType() { 896 t.Errorf("Unexpected cluster discovery type want %v, got %v", ec.GetType(), gc.GetType()) 897 } 898 if ec.GetType() == cluster.Cluster_EDS && ec.EdsClusterConfig.ServiceName != gc.EdsClusterConfig.ServiceName { 899 t.Errorf("Unexpected service name in EDS config want %v, got %v", ec.EdsClusterConfig.ServiceName, gc.EdsClusterConfig.ServiceName) 900 } 901 if ec.CircuitBreakers != nil { 902 if ec.CircuitBreakers.Thresholds[0].MaxRetries.Value != gc.CircuitBreakers.Thresholds[0].MaxRetries.Value { 903 t.Errorf("Unexpected circuit breaker thresholds want %v, got %v", ec.CircuitBreakers.Thresholds[0].MaxRetries, gc.CircuitBreakers.Thresholds[0].MaxRetries) 904 } 905 } 906 if ec.AltStatName != "" { 907 if ec.AltStatName != gc.AltStatName { 908 t.Errorf("Unexpected alt stat name want %s, got %s", ec.AltStatName, gc.AltStatName) 909 } 910 } 911 } 912 913 func verifyALPNOverride(t *testing.T, md *core.Metadata, tlsMode networking.ClientTLSSettings_TLSmode) { 914 istio, ok := md.FilterMetadata[util.IstioMetadataKey] 915 if tlsMode == networking.ClientTLSSettings_SIMPLE || tlsMode == networking.ClientTLSSettings_MUTUAL { 916 if !ok { 917 t.Errorf("Istio metadata not found") 918 } 919 alpnOverride, found := istio.Fields[util.AlpnOverrideMetadataKey] 920 if found { 921 if alpnOverride.GetStringValue() != "false" { 922 t.Errorf("alpn_override:%s tlsMode:%s, should be false for either TLS mode SIMPLE or MUTUAL", alpnOverride, tlsMode) 923 } 924 } else { 925 t.Errorf("alpn_override metadata should be written for either TLS mode SIMPLE or MUTUAL") 926 } 927 } else if ok { 928 alpnOverride, found := istio.Fields[util.AlpnOverrideMetadataKey] 929 if found { 930 t.Errorf("alpn_override:%s tlsMode:%s, alpn_override metadata should not be written if TLS mode is neither SIMPLE nor MUTUAL", 931 alpnOverride.GetStringValue(), tlsMode) 932 } 933 } 934 } 935 936 func TestApplyEdsConfig(t *testing.T) { 937 cases := []struct { 938 name string 939 cluster *cluster.Cluster 940 edsConfig *cluster.Cluster_EdsClusterConfig 941 }{ 942 { 943 name: "non eds type of cluster", 944 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_STRICT_DNS}}, 945 edsConfig: nil, 946 }, 947 { 948 name: "eds type of cluster", 949 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 950 edsConfig: &cluster.Cluster_EdsClusterConfig{ 951 ServiceName: "foo", 952 EdsConfig: &core.ConfigSource{ 953 ConfigSourceSpecifier: &core.ConfigSource_Ads{ 954 Ads: &core.AggregatedConfigSource{}, 955 }, 956 InitialFetchTimeout: durationpb.New(0), 957 ResourceApiVersion: core.ApiVersion_V3, 958 }, 959 }, 960 }, 961 } 962 963 for _, tt := range cases { 964 t.Run(tt.name, func(t *testing.T) { 965 maybeApplyEdsConfig(tt.cluster) 966 if !reflect.DeepEqual(tt.cluster.EdsClusterConfig, tt.edsConfig) { 967 t.Errorf("Unexpected Eds config in cluster. want %v, got %v", tt.edsConfig, tt.cluster.EdsClusterConfig) 968 } 969 }) 970 } 971 } 972 973 func TestBuildDefaultCluster(t *testing.T) { 974 servicePort := &model.Port{ 975 Name: "default", 976 Port: 8080, 977 Protocol: protocol.HTTP, 978 } 979 980 cases := []struct { 981 name string 982 clusterName string 983 discovery cluster.Cluster_DiscoveryType 984 endpoints []*endpoint.LocalityLbEndpoints 985 direction model.TrafficDirection 986 external bool 987 expectedCluster *cluster.Cluster 988 }{ 989 { 990 name: "default EDS cluster", 991 clusterName: "foo", 992 discovery: cluster.Cluster_EDS, 993 endpoints: nil, 994 direction: model.TrafficDirectionOutbound, 995 external: false, 996 expectedCluster: &cluster.Cluster{ 997 Name: "foo", 998 ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}, 999 CommonLbConfig: &cluster.Cluster_CommonLbConfig{}, 1000 ConnectTimeout: &durationpb.Duration{Seconds: 10, Nanos: 1}, 1001 CircuitBreakers: &cluster.CircuitBreakers{ 1002 Thresholds: []*cluster.CircuitBreakers_Thresholds{getDefaultCircuitBreakerThresholds()}, 1003 }, 1004 Filters: []*cluster.Filter{xdsfilters.TCPClusterMx}, 1005 LbPolicy: defaultLBAlgorithm(), 1006 Metadata: &core.Metadata{ 1007 FilterMetadata: map[string]*structpb.Struct{ 1008 util.IstioMetadataKey: { 1009 Fields: map[string]*structpb.Value{ 1010 "services": {Kind: &structpb.Value_ListValue{ListValue: &structpb.ListValue{Values: []*structpb.Value{ 1011 {Kind: &structpb.Value_StructValue{StructValue: &structpb.Struct{Fields: map[string]*structpb.Value{ 1012 "host": { 1013 Kind: &structpb.Value_StringValue{ 1014 StringValue: "host", 1015 }, 1016 }, 1017 "name": { 1018 Kind: &structpb.Value_StringValue{ 1019 StringValue: "svc", 1020 }, 1021 }, 1022 "namespace": { 1023 Kind: &structpb.Value_StringValue{ 1024 StringValue: "default", 1025 }, 1026 }, 1027 }}}}, 1028 }}}}, 1029 }, 1030 }, 1031 }, 1032 }, 1033 EdsClusterConfig: &cluster.Cluster_EdsClusterConfig{ 1034 ServiceName: "foo", 1035 EdsConfig: &core.ConfigSource{ 1036 ConfigSourceSpecifier: &core.ConfigSource_Ads{ 1037 Ads: &core.AggregatedConfigSource{}, 1038 }, 1039 InitialFetchTimeout: durationpb.New(0), 1040 ResourceApiVersion: core.ApiVersion_V3, 1041 }, 1042 }, 1043 }, 1044 }, 1045 { 1046 name: "static cluster with no endpoints", 1047 clusterName: "foo", 1048 discovery: cluster.Cluster_STATIC, 1049 endpoints: nil, 1050 direction: model.TrafficDirectionOutbound, 1051 external: false, 1052 expectedCluster: nil, 1053 }, 1054 { 1055 name: "strict DNS cluster with no endpoints", 1056 clusterName: "foo", 1057 discovery: cluster.Cluster_STRICT_DNS, 1058 endpoints: nil, 1059 direction: model.TrafficDirectionOutbound, 1060 external: false, 1061 expectedCluster: nil, 1062 }, 1063 { 1064 name: "static cluster with endpoints", 1065 clusterName: "foo", 1066 discovery: cluster.Cluster_STATIC, 1067 endpoints: []*endpoint.LocalityLbEndpoints{ 1068 { 1069 Locality: &core.Locality{ 1070 Region: "region1", 1071 Zone: "zone1", 1072 SubZone: "subzone1", 1073 }, 1074 LbEndpoints: []*endpoint.LbEndpoint{}, 1075 LoadBalancingWeight: &wrappers.UInt32Value{ 1076 Value: 1, 1077 }, 1078 Priority: 0, 1079 }, 1080 }, 1081 direction: model.TrafficDirectionOutbound, 1082 external: false, 1083 expectedCluster: &cluster.Cluster{ 1084 Name: "foo", 1085 ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_STATIC}, 1086 CommonLbConfig: &cluster.Cluster_CommonLbConfig{}, 1087 ConnectTimeout: &durationpb.Duration{Seconds: 10, Nanos: 1}, 1088 Filters: []*cluster.Filter{xdsfilters.TCPClusterMx}, 1089 LbPolicy: defaultLBAlgorithm(), 1090 LoadAssignment: &endpoint.ClusterLoadAssignment{ 1091 ClusterName: "foo", 1092 Endpoints: []*endpoint.LocalityLbEndpoints{ 1093 { 1094 Locality: &core.Locality{ 1095 Region: "region1", 1096 Zone: "zone1", 1097 SubZone: "subzone1", 1098 }, 1099 LbEndpoints: []*endpoint.LbEndpoint{}, 1100 LoadBalancingWeight: &wrappers.UInt32Value{ 1101 Value: 1, 1102 }, 1103 Priority: 0, 1104 }, 1105 }, 1106 }, 1107 CircuitBreakers: &cluster.CircuitBreakers{ 1108 Thresholds: []*cluster.CircuitBreakers_Thresholds{getDefaultCircuitBreakerThresholds()}, 1109 }, 1110 Metadata: &core.Metadata{ 1111 FilterMetadata: map[string]*structpb.Struct{ 1112 util.IstioMetadataKey: {Fields: map[string]*structpb.Value{ 1113 "services": {Kind: &structpb.Value_ListValue{ListValue: &structpb.ListValue{Values: []*structpb.Value{ 1114 {Kind: &structpb.Value_StructValue{StructValue: &structpb.Struct{Fields: map[string]*structpb.Value{ 1115 "host": { 1116 Kind: &structpb.Value_StringValue{ 1117 StringValue: "host", 1118 }, 1119 }, 1120 "name": { 1121 Kind: &structpb.Value_StringValue{ 1122 StringValue: "svc", 1123 }, 1124 }, 1125 "namespace": { 1126 Kind: &structpb.Value_StringValue{ 1127 StringValue: "default", 1128 }, 1129 }, 1130 }}}}, 1131 }}}}, 1132 }}, 1133 }, 1134 }, 1135 }, 1136 }, 1137 } 1138 1139 for _, tt := range cases { 1140 t.Run(tt.name, func(t *testing.T) { 1141 mesh := testMesh() 1142 cg := NewConfigGenTest(t, TestOptions{MeshConfig: mesh}) 1143 proxy := cg.SetupProxy(nil) 1144 cb := NewClusterBuilder(proxy, &model.PushRequest{Push: cg.PushContext()}, nil) 1145 service := &model.Service{ 1146 Ports: model.PortList{ 1147 servicePort, 1148 }, 1149 Hostname: "host", 1150 MeshExternal: false, 1151 Attributes: model.ServiceAttributes{Name: "svc", Namespace: "default"}, 1152 } 1153 defaultCluster := cb.buildCluster(tt.clusterName, tt.discovery, tt.endpoints, tt.direction, servicePort, service, nil, "") 1154 eb := endpoints.NewCDSEndpointBuilder(proxy, cb.req.Push, tt.clusterName, 1155 tt.direction, "", service.Hostname, servicePort.Port, 1156 service, nil) 1157 if defaultCluster != nil { 1158 _ = cb.applyDestinationRule(defaultCluster, DefaultClusterMode, service, servicePort, eb, nil, nil) 1159 } 1160 1161 if diff := cmp.Diff(defaultCluster.build(), tt.expectedCluster, protocmp.Transform()); diff != "" { 1162 t.Errorf("Unexpected default cluster, diff: %v", diff) 1163 } 1164 }) 1165 } 1166 } 1167 1168 func TestClusterDnsLookupFamily(t *testing.T) { 1169 servicePort := &model.Port{ 1170 Name: "default", 1171 Port: 8080, 1172 Protocol: protocol.HTTP, 1173 } 1174 1175 endpoints := []*endpoint.LocalityLbEndpoints{ 1176 { 1177 Locality: &core.Locality{ 1178 Region: "region1", 1179 Zone: "zone1", 1180 SubZone: "subzone1", 1181 }, 1182 LbEndpoints: []*endpoint.LbEndpoint{}, 1183 LoadBalancingWeight: &wrappers.UInt32Value{ 1184 Value: 1, 1185 }, 1186 Priority: 0, 1187 }, 1188 } 1189 1190 cases := []struct { 1191 name string 1192 clusterName string 1193 discovery cluster.Cluster_DiscoveryType 1194 proxy *model.Proxy 1195 dualStack bool 1196 expectedFamily cluster.Cluster_DnsLookupFamily 1197 }{ 1198 { 1199 name: "all ipv4, dual stack disabled", 1200 clusterName: "foo", 1201 discovery: cluster.Cluster_STRICT_DNS, 1202 proxy: getProxy(), 1203 dualStack: false, 1204 expectedFamily: cluster.Cluster_V4_ONLY, 1205 }, 1206 { 1207 name: "all ipv4, dual stack enabled", 1208 clusterName: "foo", 1209 discovery: cluster.Cluster_STRICT_DNS, 1210 proxy: getProxy(), 1211 dualStack: true, 1212 expectedFamily: cluster.Cluster_V4_ONLY, 1213 }, 1214 { 1215 name: "all ipv6, dual stack disabled", 1216 clusterName: "foo", 1217 discovery: cluster.Cluster_STRICT_DNS, 1218 proxy: getIPv6Proxy(), 1219 dualStack: false, 1220 expectedFamily: cluster.Cluster_V6_ONLY, 1221 }, 1222 { 1223 name: "all ipv6, dual stack enabled", 1224 clusterName: "foo", 1225 discovery: cluster.Cluster_STRICT_DNS, 1226 proxy: getIPv6Proxy(), 1227 dualStack: true, 1228 expectedFamily: cluster.Cluster_V6_ONLY, 1229 }, 1230 { 1231 name: "ipv4 and ipv6, dual stack disabled", 1232 clusterName: "foo", 1233 discovery: cluster.Cluster_STRICT_DNS, 1234 proxy: &dualStackProxy, 1235 dualStack: false, 1236 expectedFamily: cluster.Cluster_V4_ONLY, 1237 }, 1238 { 1239 name: "ipv4 and ipv6, dual stack enabled", 1240 clusterName: "foo", 1241 discovery: cluster.Cluster_STRICT_DNS, 1242 proxy: &dualStackProxy, 1243 dualStack: true, 1244 expectedFamily: cluster.Cluster_ALL, 1245 }, 1246 } 1247 for _, tt := range cases { 1248 t.Run(tt.name, func(t *testing.T) { 1249 test.SetForTest(t, &features.EnableDualStack, tt.dualStack) 1250 mesh := testMesh() 1251 cg := NewConfigGenTest(t, TestOptions{MeshConfig: mesh}) 1252 cb := NewClusterBuilder(cg.SetupProxy(tt.proxy), &model.PushRequest{Push: cg.PushContext()}, nil) 1253 service := &model.Service{ 1254 Ports: model.PortList{ 1255 servicePort, 1256 }, 1257 Hostname: "host", 1258 MeshExternal: false, 1259 Attributes: model.ServiceAttributes{Name: "svc", Namespace: "default"}, 1260 } 1261 defaultCluster := cb.buildCluster(tt.clusterName, tt.discovery, endpoints, model.TrafficDirectionOutbound, servicePort, service, nil, "") 1262 1263 if defaultCluster.build().DnsLookupFamily != tt.expectedFamily { 1264 t.Errorf("Unexpected DnsLookupFamily, got: %v, want: %v", defaultCluster.build().DnsLookupFamily, tt.expectedFamily) 1265 } 1266 }) 1267 } 1268 } 1269 1270 func TestBuildLocalityLbEndpoints(t *testing.T) { 1271 proxy := &model.Proxy{ 1272 Metadata: &model.NodeMetadata{ 1273 ClusterID: "cluster-1", 1274 RequestedNetworkView: []string{"nw-0", "nw-1"}, 1275 }, 1276 } 1277 servicePort := &model.Port{ 1278 Name: "default", 1279 Port: 8080, 1280 Protocol: protocol.HTTP, 1281 } 1282 service := &model.Service{ 1283 Hostname: host.Name("*.example.org"), 1284 Ports: model.PortList{servicePort}, 1285 Attributes: model.ServiceAttributes{ 1286 Name: "TestService", 1287 Namespace: "test-ns", 1288 }, 1289 } 1290 1291 buildMetadata := func(networkID network.ID, tlsMode, workloadname, namespace string, 1292 clusterID istiocluster.ID, lbls labels.Instance, 1293 ) *core.Metadata { 1294 newmeta := &core.Metadata{} 1295 util.AppendLbEndpointMetadata(&model.EndpointMetadata{ 1296 Network: networkID, 1297 TLSMode: tlsMode, 1298 WorkloadName: workloadname, 1299 Namespace: namespace, 1300 ClusterID: clusterID, 1301 Labels: lbls, 1302 }, newmeta) 1303 return newmeta 1304 } 1305 1306 cases := []struct { 1307 name string 1308 mesh *meshconfig.MeshConfig 1309 labels labels.Instance 1310 instances []*model.ServiceInstance 1311 expected []*endpoint.LocalityLbEndpoints 1312 }{ 1313 { 1314 name: "basics", 1315 mesh: testMesh(), 1316 instances: []*model.ServiceInstance{ 1317 { 1318 Service: service, 1319 ServicePort: servicePort, 1320 Endpoint: &model.IstioEndpoint{ 1321 Address: "192.168.1.1", 1322 EndpointPort: 10001, 1323 WorkloadName: "workload-1", 1324 Namespace: "namespace-1", 1325 Locality: model.Locality{ 1326 ClusterID: "cluster-1", 1327 Label: "region1/zone1/subzone1", 1328 }, 1329 LbWeight: 30, 1330 Network: "nw-0", 1331 }, 1332 }, 1333 { 1334 Service: service, 1335 ServicePort: servicePort, 1336 Endpoint: &model.IstioEndpoint{ 1337 Address: "192.168.1.2", 1338 EndpointPort: 10001, 1339 WorkloadName: "workload-2", 1340 Namespace: "namespace-2", 1341 Locality: model.Locality{ 1342 ClusterID: "cluster-2", 1343 Label: "region1/zone1/subzone1", 1344 }, 1345 LbWeight: 30, 1346 Network: "nw-1", 1347 }, 1348 }, 1349 { 1350 Service: service, 1351 ServicePort: servicePort, 1352 Endpoint: &model.IstioEndpoint{ 1353 Address: "192.168.1.3", 1354 EndpointPort: 10001, 1355 WorkloadName: "workload-3", 1356 Namespace: "namespace-3", 1357 Locality: model.Locality{ 1358 ClusterID: "cluster-3", 1359 Label: "region2/zone1/subzone1", 1360 }, 1361 LbWeight: 40, 1362 Network: "", 1363 }, 1364 }, 1365 { 1366 Service: service, 1367 ServicePort: servicePort, 1368 Endpoint: &model.IstioEndpoint{ 1369 Address: "192.168.1.4", 1370 EndpointPort: 10001, 1371 WorkloadName: "workload-1", 1372 Namespace: "namespace-1", 1373 Locality: model.Locality{ 1374 ClusterID: "cluster-1", 1375 Label: "region1/zone1/subzone1", 1376 }, 1377 LbWeight: 30, 1378 Network: "filtered-out", 1379 }, 1380 }, 1381 }, 1382 expected: []*endpoint.LocalityLbEndpoints{ 1383 { 1384 Locality: &core.Locality{ 1385 Region: "region1", 1386 Zone: "zone1", 1387 SubZone: "subzone1", 1388 }, 1389 LoadBalancingWeight: &wrappers.UInt32Value{ 1390 Value: 60, 1391 }, 1392 LbEndpoints: []*endpoint.LbEndpoint{ 1393 { 1394 HostIdentifier: &endpoint.LbEndpoint_Endpoint{ 1395 Endpoint: &endpoint.Endpoint{ 1396 Address: &core.Address{ 1397 Address: &core.Address_SocketAddress{ 1398 SocketAddress: &core.SocketAddress{ 1399 Address: "192.168.1.1", 1400 PortSpecifier: &core.SocketAddress_PortValue{ 1401 PortValue: 10001, 1402 }, 1403 }, 1404 }, 1405 }, 1406 }, 1407 }, 1408 Metadata: buildMetadata("nw-0", "", "workload-1", "namespace-1", "cluster-1", map[string]string{}), 1409 LoadBalancingWeight: &wrappers.UInt32Value{ 1410 Value: 30, 1411 }, 1412 }, 1413 { 1414 HostIdentifier: &endpoint.LbEndpoint_Endpoint{ 1415 Endpoint: &endpoint.Endpoint{ 1416 Address: &core.Address{ 1417 Address: &core.Address_SocketAddress{ 1418 SocketAddress: &core.SocketAddress{ 1419 Address: "192.168.1.2", 1420 PortSpecifier: &core.SocketAddress_PortValue{ 1421 PortValue: 10001, 1422 }, 1423 }, 1424 }, 1425 }, 1426 }, 1427 }, 1428 Metadata: buildMetadata("nw-1", "", "workload-2", "namespace-2", "cluster-2", map[string]string{}), 1429 LoadBalancingWeight: &wrappers.UInt32Value{ 1430 Value: 30, 1431 }, 1432 }, 1433 }, 1434 }, 1435 { 1436 Locality: &core.Locality{ 1437 Region: "region2", 1438 Zone: "zone1", 1439 SubZone: "subzone1", 1440 }, 1441 LoadBalancingWeight: &wrappers.UInt32Value{ 1442 Value: 40, 1443 }, 1444 LbEndpoints: []*endpoint.LbEndpoint{ 1445 { 1446 HostIdentifier: &endpoint.LbEndpoint_Endpoint{ 1447 Endpoint: &endpoint.Endpoint{ 1448 Address: &core.Address{ 1449 Address: &core.Address_SocketAddress{ 1450 SocketAddress: &core.SocketAddress{ 1451 Address: "192.168.1.3", 1452 PortSpecifier: &core.SocketAddress_PortValue{ 1453 PortValue: 10001, 1454 }, 1455 }, 1456 }, 1457 }, 1458 }, 1459 }, 1460 Metadata: buildMetadata("", "", "workload-3", "namespace-3", "cluster-3", map[string]string{}), 1461 LoadBalancingWeight: &wrappers.UInt32Value{ 1462 Value: 40, 1463 }, 1464 }, 1465 }, 1466 }, 1467 }, 1468 }, 1469 { 1470 name: "cluster local", 1471 mesh: withClusterLocalHosts(testMesh(), "*.example.org"), 1472 instances: []*model.ServiceInstance{ 1473 { 1474 Service: service, 1475 ServicePort: servicePort, 1476 Endpoint: &model.IstioEndpoint{ 1477 Address: "192.168.1.1", 1478 EndpointPort: 10001, 1479 Locality: model.Locality{ 1480 ClusterID: "cluster-1", 1481 Label: "region1/zone1/subzone1", 1482 }, 1483 LbWeight: 30, 1484 }, 1485 }, 1486 { 1487 Service: service, 1488 ServicePort: servicePort, 1489 Endpoint: &model.IstioEndpoint{ 1490 Address: "192.168.1.2", 1491 EndpointPort: 10001, 1492 Locality: model.Locality{ 1493 ClusterID: "cluster-2", 1494 Label: "region1/zone1/subzone1", 1495 }, 1496 LbWeight: 30, 1497 }, 1498 }, 1499 }, 1500 expected: []*endpoint.LocalityLbEndpoints{ 1501 { 1502 Locality: &core.Locality{ 1503 Region: "region1", 1504 Zone: "zone1", 1505 SubZone: "subzone1", 1506 }, 1507 LoadBalancingWeight: &wrappers.UInt32Value{ 1508 Value: 30, 1509 }, 1510 LbEndpoints: []*endpoint.LbEndpoint{ 1511 { 1512 HostIdentifier: &endpoint.LbEndpoint_Endpoint{ 1513 Endpoint: &endpoint.Endpoint{ 1514 Address: &core.Address{ 1515 Address: &core.Address_SocketAddress{ 1516 SocketAddress: &core.SocketAddress{ 1517 Address: "192.168.1.1", 1518 PortSpecifier: &core.SocketAddress_PortValue{ 1519 PortValue: 10001, 1520 }, 1521 }, 1522 }, 1523 }, 1524 }, 1525 }, 1526 Metadata: buildMetadata("", "", "", "", "cluster-1", map[string]string{}), 1527 LoadBalancingWeight: &wrappers.UInt32Value{ 1528 Value: 30, 1529 }, 1530 }, 1531 }, 1532 }, 1533 }, 1534 }, 1535 { 1536 name: "subset cluster endpoints with labels", 1537 mesh: testMesh(), 1538 labels: labels.Instance{"version": "v1"}, 1539 instances: []*model.ServiceInstance{ 1540 { 1541 Service: service, 1542 ServicePort: servicePort, 1543 Endpoint: &model.IstioEndpoint{ 1544 Address: "192.168.1.1", 1545 EndpointPort: 10001, 1546 WorkloadName: "workload-1", 1547 Namespace: "namespace-1", 1548 Labels: map[string]string{ 1549 "version": "v1", 1550 "app": "example", 1551 }, 1552 Locality: model.Locality{ 1553 ClusterID: "cluster-1", 1554 Label: "region1/zone1/subzone1", 1555 }, 1556 LbWeight: 30, 1557 Network: "nw-0", 1558 }, 1559 }, 1560 { 1561 Service: service, 1562 ServicePort: servicePort, 1563 Endpoint: &model.IstioEndpoint{ 1564 Address: "192.168.1.2", 1565 EndpointPort: 10001, 1566 WorkloadName: "workload-2", 1567 Namespace: "namespace-2", 1568 Labels: map[string]string{ 1569 "version": "v2", 1570 "app": "example", 1571 }, 1572 Locality: model.Locality{ 1573 ClusterID: "cluster-2", 1574 Label: "region1/zone1/subzone1", 1575 }, 1576 LbWeight: 30, 1577 Network: "nw-1", 1578 }, 1579 }, 1580 { 1581 Service: service, 1582 ServicePort: servicePort, 1583 Endpoint: &model.IstioEndpoint{ 1584 Address: "192.168.1.3", 1585 EndpointPort: 10001, 1586 WorkloadName: "workload-3", 1587 Namespace: "namespace-3", 1588 Labels: map[string]string{ 1589 "version": "v3", 1590 "app": "example", 1591 }, 1592 Locality: model.Locality{ 1593 ClusterID: "cluster-3", 1594 Label: "region2/zone1/subzone1", 1595 }, 1596 LbWeight: 40, 1597 Network: "", 1598 }, 1599 }, 1600 { 1601 Service: service, 1602 ServicePort: servicePort, 1603 Endpoint: &model.IstioEndpoint{ 1604 Address: "192.168.1.4", 1605 EndpointPort: 10001, 1606 WorkloadName: "workload-1", 1607 Namespace: "namespace-1", 1608 Labels: map[string]string{ 1609 "version": "v4", 1610 "app": "example", 1611 }, 1612 Locality: model.Locality{ 1613 ClusterID: "cluster-1", 1614 Label: "region1/zone1/subzone1", 1615 }, 1616 LbWeight: 30, 1617 Network: "filtered-out", 1618 }, 1619 }, 1620 }, 1621 expected: []*endpoint.LocalityLbEndpoints{ 1622 { 1623 Locality: &core.Locality{ 1624 Region: "region1", 1625 Zone: "zone1", 1626 SubZone: "subzone1", 1627 }, 1628 LoadBalancingWeight: &wrappers.UInt32Value{ 1629 Value: 30, 1630 }, 1631 LbEndpoints: []*endpoint.LbEndpoint{ 1632 { 1633 HostIdentifier: &endpoint.LbEndpoint_Endpoint{ 1634 Endpoint: &endpoint.Endpoint{ 1635 Address: &core.Address{ 1636 Address: &core.Address_SocketAddress{ 1637 SocketAddress: &core.SocketAddress{ 1638 Address: "192.168.1.1", 1639 PortSpecifier: &core.SocketAddress_PortValue{ 1640 PortValue: 10001, 1641 }, 1642 }, 1643 }, 1644 }, 1645 }, 1646 }, 1647 Metadata: buildMetadata("nw-0", "", "workload-1", "namespace-1", "cluster-1", map[string]string{ 1648 "version": "v1", 1649 "app": "example", 1650 }), 1651 LoadBalancingWeight: &wrappers.UInt32Value{ 1652 Value: 30, 1653 }, 1654 }, 1655 }, 1656 }, 1657 }, 1658 }, 1659 } 1660 1661 sortEndpoints := func(endpoints []*endpoint.LocalityLbEndpoints) { 1662 sort.SliceStable(endpoints, func(i, j int) bool { 1663 if strings.Compare(endpoints[i].Locality.Region, endpoints[j].Locality.Region) < 0 { 1664 return true 1665 } 1666 if strings.Compare(endpoints[i].Locality.Zone, endpoints[j].Locality.Zone) < 0 { 1667 return true 1668 } 1669 return strings.Compare(endpoints[i].Locality.SubZone, endpoints[j].Locality.SubZone) < 0 1670 }) 1671 } 1672 1673 for _, tt := range cases { 1674 for _, resolution := range []model.Resolution{model.DNSLB, model.DNSRoundRobinLB} { 1675 t.Run(fmt.Sprintf("%s_%s", tt.name, resolution), func(t *testing.T) { 1676 service.Resolution = resolution 1677 cg := NewConfigGenTest(t, TestOptions{ 1678 MeshConfig: tt.mesh, 1679 Services: []*model.Service{service}, 1680 Instances: tt.instances, 1681 }) 1682 1683 cb := NewClusterBuilder(cg.SetupProxy(proxy), &model.PushRequest{Push: cg.PushContext()}, nil) 1684 eb := endpoints.NewCDSEndpointBuilder( 1685 proxy, cb.req.Push, 1686 "outbound|8080|v1|foo.com", 1687 model.TrafficDirectionOutbound, "v1", "foo.com", 8080, 1688 service, drWithLabels(tt.labels), 1689 ) 1690 actual := eb.FromServiceEndpoints() 1691 sortEndpoints(actual) 1692 if v := cmp.Diff(tt.expected, actual, protocmp.Transform()); v != "" { 1693 t.Fatalf("Expected (-) != actual (+):\n%s", v) 1694 } 1695 }) 1696 } 1697 } 1698 } 1699 1700 func drWithLabels(lbls labels.Instance) *model.ConsolidatedDestRule { 1701 return model.ConvertConsolidatedDestRule(&config.Config{ 1702 Meta: config.Meta{}, 1703 Spec: &networking.DestinationRule{ 1704 Subsets: []*networking.Subset{{ 1705 Name: "v1", 1706 Labels: lbls, 1707 }}, 1708 }, 1709 }) 1710 } 1711 1712 func TestConcurrentBuildLocalityLbEndpoints(t *testing.T) { 1713 test.SetForTest(t, &features.CanonicalServiceForMeshExternalServiceEntry, true) 1714 proxy := &model.Proxy{ 1715 Metadata: &model.NodeMetadata{ 1716 ClusterID: "cluster-1", 1717 RequestedNetworkView: []string{"nw-0", "nw-1"}, 1718 }, 1719 } 1720 servicePort := &model.Port{ 1721 Name: "default", 1722 Port: 8080, 1723 Protocol: protocol.HTTP, 1724 } 1725 service := &model.Service{ 1726 Hostname: host.Name("*.example.org"), 1727 Ports: model.PortList{servicePort}, 1728 Attributes: model.ServiceAttributes{ 1729 Name: "TestService", 1730 Namespace: "test-ns", 1731 Labels: map[string]string{"service.istio.io/canonical-name": "example-service"}, 1732 }, 1733 MeshExternal: true, 1734 Resolution: model.DNSLB, 1735 } 1736 dr := drWithLabels(labels.Instance{"version": "v1"}) 1737 1738 buildMetadata := func(networkID network.ID, tlsMode, workloadname, namespace string, 1739 clusterID istiocluster.ID, lbls labels.Instance, 1740 ) *core.Metadata { 1741 newmeta := &core.Metadata{} 1742 util.AppendLbEndpointMetadata(&model.EndpointMetadata{ 1743 Network: networkID, 1744 TLSMode: tlsMode, 1745 WorkloadName: workloadname, 1746 Namespace: namespace, 1747 ClusterID: clusterID, 1748 Labels: lbls, 1749 }, newmeta) 1750 return newmeta 1751 } 1752 1753 instances := []*model.ServiceInstance{ 1754 { 1755 Service: service, 1756 ServicePort: servicePort, 1757 Endpoint: &model.IstioEndpoint{ 1758 Address: "192.168.1.1", 1759 EndpointPort: 10001, 1760 WorkloadName: "workload-1", 1761 Namespace: "namespace-1", 1762 Labels: map[string]string{ 1763 "version": "v1", 1764 "app": "example", 1765 }, 1766 Locality: model.Locality{ 1767 ClusterID: "cluster-1", 1768 Label: "region1/zone1/subzone1", 1769 }, 1770 LbWeight: 30, 1771 Network: "nw-0", 1772 }, 1773 }, 1774 { 1775 Service: service, 1776 ServicePort: servicePort, 1777 Endpoint: &model.IstioEndpoint{ 1778 Address: "192.168.1.2", 1779 EndpointPort: 10001, 1780 WorkloadName: "workload-2", 1781 Namespace: "namespace-2", 1782 Labels: map[string]string{ 1783 "version": "v2", 1784 "app": "example", 1785 }, 1786 Locality: model.Locality{ 1787 ClusterID: "cluster-2", 1788 Label: "region1/zone1/subzone1", 1789 }, 1790 LbWeight: 30, 1791 Network: "nw-1", 1792 }, 1793 }, 1794 { 1795 Service: service, 1796 ServicePort: servicePort, 1797 Endpoint: &model.IstioEndpoint{ 1798 Address: "192.168.1.3", 1799 EndpointPort: 10001, 1800 WorkloadName: "workload-3", 1801 Namespace: "namespace-3", 1802 Labels: map[string]string{ 1803 "version": "v3", 1804 "app": "example", 1805 }, 1806 Locality: model.Locality{ 1807 ClusterID: "cluster-3", 1808 Label: "region2/zone1/subzone1", 1809 }, 1810 LbWeight: 40, 1811 Network: "", 1812 }, 1813 }, 1814 { 1815 Service: service, 1816 ServicePort: servicePort, 1817 Endpoint: &model.IstioEndpoint{ 1818 Address: "192.168.1.4", 1819 EndpointPort: 10001, 1820 WorkloadName: "workload-1", 1821 Namespace: "namespace-1", 1822 Labels: map[string]string{ 1823 "version": "v4", 1824 "app": "example", 1825 }, 1826 Locality: model.Locality{ 1827 ClusterID: "cluster-1", 1828 Label: "region1/zone1/subzone1", 1829 }, 1830 LbWeight: 30, 1831 Network: "filtered-out", 1832 }, 1833 }, 1834 } 1835 1836 updatedLbls := labels.Instance{ 1837 "app": "example", 1838 model.IstioCanonicalServiceLabelName: "example-service", 1839 } 1840 expected := []*endpoint.LocalityLbEndpoints{ 1841 { 1842 Locality: &core.Locality{ 1843 Region: "region1", 1844 Zone: "zone1", 1845 SubZone: "subzone1", 1846 }, 1847 LoadBalancingWeight: &wrappers.UInt32Value{ 1848 Value: 30, 1849 }, 1850 LbEndpoints: []*endpoint.LbEndpoint{ 1851 { 1852 HostIdentifier: &endpoint.LbEndpoint_Endpoint{ 1853 Endpoint: &endpoint.Endpoint{ 1854 Address: &core.Address{ 1855 Address: &core.Address_SocketAddress{ 1856 SocketAddress: &core.SocketAddress{ 1857 Address: "192.168.1.1", 1858 PortSpecifier: &core.SocketAddress_PortValue{ 1859 PortValue: 10001, 1860 }, 1861 }, 1862 }, 1863 }, 1864 }, 1865 }, 1866 Metadata: buildMetadata("nw-0", "", "workload-1", "test-ns", "cluster-1", updatedLbls), 1867 LoadBalancingWeight: &wrappers.UInt32Value{ 1868 Value: 30, 1869 }, 1870 }, 1871 }, 1872 }, 1873 } 1874 1875 sortEndpoints := func(endpoints []*endpoint.LocalityLbEndpoints) { 1876 sort.SliceStable(endpoints, func(i, j int) bool { 1877 if strings.Compare(endpoints[i].Locality.Region, endpoints[j].Locality.Region) < 0 { 1878 return true 1879 } 1880 if strings.Compare(endpoints[i].Locality.Zone, endpoints[j].Locality.Zone) < 0 { 1881 return true 1882 } 1883 return strings.Compare(endpoints[i].Locality.SubZone, endpoints[j].Locality.SubZone) < 0 1884 }) 1885 } 1886 1887 cg := NewConfigGenTest(t, TestOptions{ 1888 MeshConfig: testMesh(), 1889 Services: []*model.Service{service}, 1890 Instances: instances, 1891 }) 1892 1893 cb := NewClusterBuilder(cg.SetupProxy(proxy), &model.PushRequest{Push: cg.PushContext()}, nil) 1894 wg := sync.WaitGroup{} 1895 wg.Add(5) 1896 var actual []*endpoint.LocalityLbEndpoints 1897 mu := sync.Mutex{} 1898 for i := 0; i < 5; i++ { 1899 go func() { 1900 eb := endpoints.NewCDSEndpointBuilder( 1901 proxy, cb.req.Push, 1902 "outbound|8080|v1|foo.com", 1903 model.TrafficDirectionOutbound, "v1", "foo.com", 8080, 1904 service, dr, 1905 ) 1906 eps := eb.FromServiceEndpoints() 1907 mu.Lock() 1908 actual = eps 1909 mu.Unlock() 1910 wg.Done() 1911 }() 1912 } 1913 wg.Wait() 1914 sortEndpoints(actual) 1915 if v := cmp.Diff(expected, actual, protocmp.Transform()); v != "" { 1916 t.Fatalf("Expected (-) != actual (+):\n%s", v) 1917 } 1918 } 1919 1920 func TestBuildPassthroughClusters(t *testing.T) { 1921 cases := []struct { 1922 name string 1923 ips []string 1924 ipv4Expected bool 1925 ipv6Expected bool 1926 }{ 1927 { 1928 name: "both ipv4 and ipv6", 1929 ips: []string{"6.6.6.6", "::1"}, 1930 ipv4Expected: true, 1931 ipv6Expected: true, 1932 }, 1933 { 1934 name: "ipv4 only", 1935 ips: []string{"6.6.6.6"}, 1936 ipv4Expected: true, 1937 ipv6Expected: false, 1938 }, 1939 { 1940 name: "ipv6 only", 1941 ips: []string{"::1"}, 1942 ipv4Expected: false, 1943 ipv6Expected: true, 1944 }, 1945 } 1946 for _, tt := range cases { 1947 t.Run(tt.name, func(t *testing.T) { 1948 proxy := &model.Proxy{IPAddresses: tt.ips} 1949 cg := NewConfigGenTest(t, TestOptions{}) 1950 1951 cb := NewClusterBuilder(cg.SetupProxy(proxy), &model.PushRequest{Push: cg.PushContext()}, nil) 1952 clusters := cb.buildInboundPassthroughClusters() 1953 1954 var hasIpv4, hasIpv6 bool 1955 for _, c := range clusters { 1956 hasIpv4 = hasIpv4 || c.Name == util.InboundPassthroughClusterIpv4 1957 hasIpv6 = hasIpv6 || c.Name == util.InboundPassthroughClusterIpv6 1958 } 1959 if hasIpv4 != tt.ipv4Expected { 1960 t.Errorf("Unexpected Ipv4 Passthrough Cluster, want %v got %v", tt.ipv4Expected, hasIpv4) 1961 } 1962 if hasIpv6 != tt.ipv6Expected { 1963 t.Errorf("Unexpected Ipv6 Passthrough Cluster, want %v got %v", tt.ipv6Expected, hasIpv6) 1964 } 1965 1966 passthrough := xdstest.ExtractCluster(util.InboundPassthroughClusterIpv4, clusters) 1967 if passthrough == nil { 1968 passthrough = xdstest.ExtractCluster(util.InboundPassthroughClusterIpv6, clusters) 1969 } 1970 // Validate that Passthrough Cluster LB Policy is set correctly. 1971 if passthrough.GetType() != cluster.Cluster_ORIGINAL_DST || passthrough.GetLbPolicy() != cluster.Cluster_CLUSTER_PROVIDED { 1972 t.Errorf("Unexpected Discovery type or Lb policy, got Discovery type: %v, Lb Policy: %v", passthrough.GetType(), passthrough.GetLbPolicy()) 1973 } 1974 }) 1975 } 1976 } 1977 1978 func newTestCluster() *clusterWrapper { 1979 return newClusterWrapper(&cluster.Cluster{ 1980 Name: "test-cluster", 1981 }) 1982 } 1983 1984 func newH2TestCluster() *clusterWrapper { 1985 mc := newClusterWrapper(&cluster.Cluster{ 1986 Name: "test-cluster", 1987 }) 1988 setH2Options(mc) 1989 return mc 1990 } 1991 1992 func newDownstreamTestCluster() *clusterWrapper { 1993 cb := NewClusterBuilder(newSidecarProxy(), nil, model.DisabledCache{}) 1994 mc := newClusterWrapper(&cluster.Cluster{ 1995 Name: "test-cluster", 1996 }) 1997 cb.setUseDownstreamProtocol(mc) 1998 return mc 1999 } 2000 2001 func newSidecarProxy() *model.Proxy { 2002 return &model.Proxy{Type: model.SidecarProxy, Metadata: &model.NodeMetadata{}} 2003 } 2004 2005 func newGatewayProxy() *model.Proxy { 2006 return &model.Proxy{Type: model.Router, Metadata: &model.NodeMetadata{}} 2007 } 2008 2009 // Helper function to extract TLS context from a cluster 2010 func getTLSContext(t *testing.T, c *cluster.Cluster) *tls.UpstreamTlsContext { 2011 t.Helper() 2012 if c.TransportSocket == nil { 2013 return nil 2014 } 2015 tlsContext := &tls.UpstreamTlsContext{} 2016 err := c.TransportSocket.GetTypedConfig().UnmarshalTo(tlsContext) 2017 if err != nil { 2018 t.Fatalf("Failed to unmarshall tls context: %v", err) 2019 } 2020 return tlsContext 2021 } 2022 2023 func TestShouldH2Upgrade(t *testing.T) { 2024 tests := []struct { 2025 name string 2026 clusterName string 2027 port *model.Port 2028 mesh *meshconfig.MeshConfig 2029 connectionPool *networking.ConnectionPoolSettings 2030 2031 upgrade bool 2032 }{ 2033 { 2034 name: "mesh upgrade - dr default", 2035 clusterName: "bar", 2036 port: &model.Port{Protocol: protocol.HTTP}, 2037 mesh: &meshconfig.MeshConfig{H2UpgradePolicy: meshconfig.MeshConfig_UPGRADE}, 2038 connectionPool: &networking.ConnectionPoolSettings{ 2039 Http: &networking.ConnectionPoolSettings_HTTPSettings{ 2040 H2UpgradePolicy: networking.ConnectionPoolSettings_HTTPSettings_DEFAULT, 2041 }, 2042 }, 2043 upgrade: true, 2044 }, 2045 { 2046 name: "mesh default - dr upgrade non http port", 2047 clusterName: "bar", 2048 port: &model.Port{Protocol: protocol.Unsupported}, 2049 mesh: &meshconfig.MeshConfig{}, 2050 connectionPool: &networking.ConnectionPoolSettings{ 2051 Http: &networking.ConnectionPoolSettings_HTTPSettings{ 2052 H2UpgradePolicy: networking.ConnectionPoolSettings_HTTPSettings_UPGRADE, 2053 }, 2054 }, 2055 upgrade: true, 2056 }, 2057 { 2058 name: "mesh no_upgrade - dr default", 2059 clusterName: "bar", 2060 port: &model.Port{Protocol: protocol.HTTP}, 2061 mesh: &meshconfig.MeshConfig{H2UpgradePolicy: meshconfig.MeshConfig_DO_NOT_UPGRADE}, 2062 connectionPool: &networking.ConnectionPoolSettings{ 2063 Http: &networking.ConnectionPoolSettings_HTTPSettings{ 2064 H2UpgradePolicy: networking.ConnectionPoolSettings_HTTPSettings_DEFAULT, 2065 }, 2066 }, 2067 upgrade: false, 2068 }, 2069 { 2070 name: "mesh no_upgrade - dr upgrade", 2071 clusterName: "bar", 2072 port: &model.Port{Protocol: protocol.HTTP}, 2073 mesh: &meshconfig.MeshConfig{H2UpgradePolicy: meshconfig.MeshConfig_DO_NOT_UPGRADE}, 2074 connectionPool: &networking.ConnectionPoolSettings{ 2075 Http: &networking.ConnectionPoolSettings_HTTPSettings{ 2076 H2UpgradePolicy: networking.ConnectionPoolSettings_HTTPSettings_UPGRADE, 2077 }, 2078 }, 2079 upgrade: true, 2080 }, 2081 { 2082 name: "mesh upgrade - dr no_upgrade", 2083 clusterName: "bar", 2084 port: &model.Port{Protocol: protocol.HTTP}, 2085 mesh: &meshconfig.MeshConfig{H2UpgradePolicy: meshconfig.MeshConfig_UPGRADE}, 2086 connectionPool: &networking.ConnectionPoolSettings{ 2087 Http: &networking.ConnectionPoolSettings_HTTPSettings{ 2088 H2UpgradePolicy: networking.ConnectionPoolSettings_HTTPSettings_DO_NOT_UPGRADE, 2089 }, 2090 }, 2091 upgrade: false, 2092 }, 2093 { 2094 name: "non-http", 2095 clusterName: "bar", 2096 port: &model.Port{Protocol: protocol.Unsupported}, 2097 mesh: &meshconfig.MeshConfig{H2UpgradePolicy: meshconfig.MeshConfig_UPGRADE}, 2098 connectionPool: &networking.ConnectionPoolSettings{ 2099 Http: &networking.ConnectionPoolSettings_HTTPSettings{ 2100 H2UpgradePolicy: networking.ConnectionPoolSettings_HTTPSettings_DEFAULT, 2101 }, 2102 }, 2103 upgrade: false, 2104 }, 2105 } 2106 2107 for _, test := range tests { 2108 t.Run(test.name, func(t *testing.T) { 2109 upgrade := shouldH2Upgrade(test.clusterName, test.port, test.mesh, test.connectionPool) 2110 2111 if upgrade != test.upgrade { 2112 t.Fatalf("got: %t, want: %t (%v, %v)", upgrade, test.upgrade, test.mesh.H2UpgradePolicy, test.connectionPool.Http.H2UpgradePolicy) 2113 } 2114 }) 2115 } 2116 } 2117 2118 // nolint 2119 func TestIsHttp2Cluster(t *testing.T) { 2120 tests := []struct { 2121 name string 2122 cluster *clusterWrapper 2123 isHttp2Cluster bool // revive:disable-line 2124 }{ 2125 { 2126 name: "with no h2 options", 2127 cluster: newTestCluster(), 2128 isHttp2Cluster: false, 2129 }, 2130 { 2131 name: "with h2 options", 2132 cluster: newH2TestCluster(), 2133 isHttp2Cluster: true, 2134 }, 2135 { 2136 name: "with downstream config and h2 options", 2137 cluster: newDownstreamTestCluster(), 2138 isHttp2Cluster: false, 2139 }, 2140 } 2141 2142 cb := NewClusterBuilder(newSidecarProxy(), nil, model.DisabledCache{}) 2143 2144 for _, test := range tests { 2145 t.Run(test.name, func(t *testing.T) { 2146 isHttp2Cluster := cb.isHttp2Cluster(test.cluster) // revive:disable-line 2147 if isHttp2Cluster != test.isHttp2Cluster { 2148 t.Errorf("got: %t, want: %t", isHttp2Cluster, test.isHttp2Cluster) 2149 } 2150 }) 2151 } 2152 } 2153 2154 func TestApplyDestinationRuleOSCACert(t *testing.T) { 2155 servicePort := model.PortList{ 2156 &model.Port{ 2157 Name: "default", 2158 Port: 8080, 2159 Protocol: protocol.HTTP, 2160 }, 2161 &model.Port{ 2162 Name: "auto", 2163 Port: 9090, 2164 Protocol: protocol.Unsupported, 2165 }, 2166 } 2167 service := &model.Service{ 2168 Hostname: host.Name("foo.default.svc.cluster.local"), 2169 Ports: servicePort, 2170 Resolution: model.ClientSideLB, 2171 Attributes: model.ServiceAttributes{ 2172 Namespace: TestServiceNamespace, 2173 }, 2174 } 2175 2176 cases := []struct { 2177 name string 2178 cluster *cluster.Cluster 2179 clusterMode ClusterMode 2180 service *model.Service 2181 port *model.Port 2182 proxyView model.ProxyView 2183 destRule *networking.DestinationRule 2184 expectedCaCertificateName string 2185 enableVerifyCertAtClient bool 2186 }{ 2187 { 2188 name: "VerifyCertAtClient set and destination rule with empty string CaCertificates", 2189 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 2190 clusterMode: DefaultClusterMode, 2191 service: service, 2192 port: servicePort[0], 2193 proxyView: model.ProxyViewAll, 2194 destRule: &networking.DestinationRule{ 2195 Host: "foo.default.svc.cluster.local", 2196 TrafficPolicy: &networking.TrafficPolicy{ 2197 ConnectionPool: &networking.ConnectionPoolSettings{ 2198 Http: &networking.ConnectionPoolSettings_HTTPSettings{ 2199 MaxRetries: 10, 2200 UseClientProtocol: true, 2201 }, 2202 }, 2203 Tls: &networking.ClientTLSSettings{ 2204 CaCertificates: "", 2205 Mode: networking.ClientTLSSettings_SIMPLE, 2206 }, 2207 }, 2208 }, 2209 expectedCaCertificateName: "system", 2210 enableVerifyCertAtClient: true, 2211 }, 2212 { 2213 name: "VerifyCertAtClient set and destination rule with CaCertificates", 2214 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 2215 clusterMode: DefaultClusterMode, 2216 service: service, 2217 port: servicePort[0], 2218 proxyView: model.ProxyViewAll, 2219 destRule: &networking.DestinationRule{ 2220 Host: "foo.default.svc.cluster.local", 2221 TrafficPolicy: &networking.TrafficPolicy{ 2222 ConnectionPool: &networking.ConnectionPoolSettings{ 2223 Http: &networking.ConnectionPoolSettings_HTTPSettings{ 2224 MaxRetries: 10, 2225 UseClientProtocol: true, 2226 }, 2227 }, 2228 Tls: &networking.ClientTLSSettings{ 2229 CaCertificates: "root-cert.pem", 2230 Mode: networking.ClientTLSSettings_SIMPLE, 2231 }, 2232 }, 2233 }, 2234 expectedCaCertificateName: "root-cert.pem", 2235 enableVerifyCertAtClient: true, 2236 }, 2237 { 2238 name: "VerifyCertAtClient set and destination rule without CaCertificates", 2239 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 2240 clusterMode: DefaultClusterMode, 2241 service: service, 2242 port: servicePort[0], 2243 proxyView: model.ProxyViewAll, 2244 destRule: &networking.DestinationRule{ 2245 Host: "foo.default.svc.cluster.local", 2246 TrafficPolicy: &networking.TrafficPolicy{ 2247 ConnectionPool: &networking.ConnectionPoolSettings{ 2248 Http: &networking.ConnectionPoolSettings_HTTPSettings{ 2249 MaxRetries: 10, 2250 UseClientProtocol: true, 2251 }, 2252 }, 2253 Tls: &networking.ClientTLSSettings{ 2254 Mode: networking.ClientTLSSettings_SIMPLE, 2255 }, 2256 }, 2257 }, 2258 expectedCaCertificateName: "system", 2259 enableVerifyCertAtClient: true, 2260 }, 2261 { 2262 name: "VerifyCertAtClient false and destination rule without CaCertificates", 2263 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 2264 clusterMode: DefaultClusterMode, 2265 service: service, 2266 port: servicePort[0], 2267 proxyView: model.ProxyViewAll, 2268 destRule: &networking.DestinationRule{ 2269 Host: "foo.default.svc.cluster.local", 2270 TrafficPolicy: &networking.TrafficPolicy{ 2271 ConnectionPool: &networking.ConnectionPoolSettings{ 2272 Http: &networking.ConnectionPoolSettings_HTTPSettings{ 2273 MaxRetries: 10, 2274 UseClientProtocol: true, 2275 }, 2276 }, 2277 Tls: &networking.ClientTLSSettings{ 2278 Mode: networking.ClientTLSSettings_SIMPLE, 2279 }, 2280 }, 2281 }, 2282 expectedCaCertificateName: "", 2283 enableVerifyCertAtClient: false, 2284 }, 2285 { 2286 name: "VerifyCertAtClient false and destination rule with CaCertificates", 2287 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 2288 clusterMode: DefaultClusterMode, 2289 service: service, 2290 port: servicePort[0], 2291 proxyView: model.ProxyViewAll, 2292 destRule: &networking.DestinationRule{ 2293 Host: "foo.default.svc.cluster.local", 2294 TrafficPolicy: &networking.TrafficPolicy{ 2295 ConnectionPool: &networking.ConnectionPoolSettings{ 2296 Http: &networking.ConnectionPoolSettings_HTTPSettings{ 2297 MaxRetries: 10, 2298 UseClientProtocol: true, 2299 }, 2300 }, 2301 Tls: &networking.ClientTLSSettings{ 2302 CaCertificates: "root-cert.pem", 2303 Mode: networking.ClientTLSSettings_SIMPLE, 2304 }, 2305 }, 2306 }, 2307 expectedCaCertificateName: "root-cert.pem", 2308 enableVerifyCertAtClient: false, 2309 }, 2310 } 2311 2312 for _, tt := range cases { 2313 t.Run(tt.name, func(t *testing.T) { 2314 test.SetForTest(t, &features.VerifyCertAtClient, tt.enableVerifyCertAtClient) 2315 2316 var cfg *config.Config 2317 if tt.destRule != nil { 2318 cfg = &config.Config{ 2319 Meta: config.Meta{ 2320 GroupVersionKind: gvk.DestinationRule, 2321 Name: "acme", 2322 Namespace: "default", 2323 }, 2324 Spec: tt.destRule, 2325 } 2326 } 2327 cg := NewConfigGenTest(t, TestOptions{ 2328 ConfigPointers: []*config.Config{cfg}, 2329 Services: []*model.Service{tt.service}, 2330 }) 2331 proxy := cg.SetupProxy(nil) 2332 cb := NewClusterBuilder(proxy, &model.PushRequest{Push: cg.PushContext()}, nil) 2333 2334 tt.cluster.CommonLbConfig = &cluster.Cluster_CommonLbConfig{} 2335 2336 ec := newClusterWrapper(tt.cluster) 2337 destRule := proxy.SidecarScope.DestinationRule(model.TrafficDirectionOutbound, proxy, tt.service.Hostname) 2338 2339 eb := endpoints.NewCDSEndpointBuilder(proxy, cb.req.Push, tt.cluster.Name, 2340 model.TrafficDirectionOutbound, "", service.Hostname, tt.port.Port, 2341 service, destRule) 2342 2343 // ACT 2344 _ = cb.applyDestinationRule(ec, tt.clusterMode, tt.service, tt.port, eb, destRule.GetRule(), nil) 2345 2346 byteArray, err := config.ToJSON(destRule.GetRule().Spec) 2347 if err != nil { 2348 t.Errorf("Could not parse destination rule: %v", err) 2349 } 2350 dr := &networking.DestinationRule{} 2351 err = json.Unmarshal(byteArray, dr) 2352 if err != nil { 2353 t.Errorf("Could not unmarshal destination rule: %v", err) 2354 } 2355 ca := dr.TrafficPolicy.Tls.CaCertificates 2356 if ca != tt.expectedCaCertificateName { 2357 t.Errorf("%v: got unexpected caCertitifcates field. Expected (%v), received (%v)", tt.name, tt.expectedCaCertificateName, ca) 2358 } 2359 }) 2360 } 2361 } 2362 2363 func TestApplyTCPKeepalive(t *testing.T) { 2364 cases := []struct { 2365 name string 2366 mesh *meshconfig.MeshConfig 2367 connectionPool *networking.ConnectionPoolSettings 2368 wantConnOpts *cluster.UpstreamConnectionOptions 2369 }{ 2370 { 2371 name: "no tcp alive", 2372 mesh: &meshconfig.MeshConfig{}, 2373 connectionPool: &networking.ConnectionPoolSettings{}, 2374 wantConnOpts: nil, 2375 }, 2376 { 2377 name: "destination rule tcp alive", 2378 mesh: &meshconfig.MeshConfig{}, 2379 connectionPool: &networking.ConnectionPoolSettings{ 2380 Tcp: &networking.ConnectionPoolSettings_TCPSettings{ 2381 TcpKeepalive: &networking.ConnectionPoolSettings_TCPSettings_TcpKeepalive{ 2382 Time: &durationpb.Duration{Seconds: 10}, 2383 }, 2384 }, 2385 }, 2386 wantConnOpts: &cluster.UpstreamConnectionOptions{ 2387 TcpKeepalive: &core.TcpKeepalive{ 2388 KeepaliveTime: &wrappers.UInt32Value{Value: uint32(10)}, 2389 }, 2390 }, 2391 }, 2392 { 2393 name: "mesh tcp alive", 2394 mesh: &meshconfig.MeshConfig{ 2395 TcpKeepalive: &networking.ConnectionPoolSettings_TCPSettings_TcpKeepalive{ 2396 Time: &durationpb.Duration{Seconds: 10}, 2397 }, 2398 }, 2399 connectionPool: &networking.ConnectionPoolSettings{}, 2400 wantConnOpts: &cluster.UpstreamConnectionOptions{ 2401 TcpKeepalive: &core.TcpKeepalive{ 2402 KeepaliveTime: &wrappers.UInt32Value{Value: uint32(10)}, 2403 }, 2404 }, 2405 }, 2406 } 2407 2408 for _, tt := range cases { 2409 t.Run(tt.name, func(t *testing.T) { 2410 cg := NewConfigGenTest(t, TestOptions{}) 2411 proxy := cg.SetupProxy(nil) 2412 cb := NewClusterBuilder(proxy, &model.PushRequest{Push: cg.PushContext()}, nil) 2413 mc := &clusterWrapper{ 2414 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 2415 } 2416 2417 cb.applyConnectionPool(tt.mesh, mc, tt.connectionPool) 2418 2419 if !reflect.DeepEqual(tt.wantConnOpts, mc.cluster.UpstreamConnectionOptions) { 2420 t.Errorf("unexpected tcp keepalive settings, want %v, got %v", tt.wantConnOpts, 2421 mc.cluster.UpstreamConnectionOptions) 2422 } 2423 }) 2424 } 2425 } 2426 2427 func TestApplyConnectionPool(t *testing.T) { 2428 cases := []struct { 2429 name string 2430 cluster *cluster.Cluster 2431 httpProtocolOptions *http.HttpProtocolOptions 2432 connectionPool *networking.ConnectionPoolSettings 2433 expectedHTTPPOpt *http.HttpProtocolOptions 2434 }{ 2435 { 2436 name: "only update IdleTimeout", 2437 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 2438 httpProtocolOptions: &http.HttpProtocolOptions{ 2439 CommonHttpProtocolOptions: &core.HttpProtocolOptions{ 2440 IdleTimeout: &durationpb.Duration{ 2441 Seconds: 10, 2442 }, 2443 MaxRequestsPerConnection: &wrappers.UInt32Value{Value: 10}, 2444 }, 2445 }, 2446 connectionPool: &networking.ConnectionPoolSettings{ 2447 Http: &networking.ConnectionPoolSettings_HTTPSettings{ 2448 IdleTimeout: &durationpb.Duration{ 2449 Seconds: 22, 2450 }, 2451 }, 2452 }, 2453 expectedHTTPPOpt: &http.HttpProtocolOptions{ 2454 CommonHttpProtocolOptions: &core.HttpProtocolOptions{ 2455 IdleTimeout: &durationpb.Duration{ 2456 Seconds: 22, 2457 }, 2458 MaxRequestsPerConnection: &wrappers.UInt32Value{Value: 10}, 2459 }, 2460 }, 2461 }, 2462 { 2463 name: "set TCP idle timeout", 2464 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 2465 httpProtocolOptions: &http.HttpProtocolOptions{ 2466 CommonHttpProtocolOptions: &core.HttpProtocolOptions{ 2467 MaxRequestsPerConnection: &wrappers.UInt32Value{Value: 10}, 2468 }, 2469 }, 2470 connectionPool: &networking.ConnectionPoolSettings{ 2471 Tcp: &networking.ConnectionPoolSettings_TCPSettings{ 2472 IdleTimeout: &durationpb.Duration{ 2473 Seconds: 10, 2474 }, 2475 }, 2476 Http: &networking.ConnectionPoolSettings_HTTPSettings{ 2477 IdleTimeout: nil, 2478 }, 2479 }, 2480 expectedHTTPPOpt: &http.HttpProtocolOptions{ 2481 CommonHttpProtocolOptions: &core.HttpProtocolOptions{ 2482 IdleTimeout: &durationpb.Duration{ 2483 Seconds: 10, 2484 }, 2485 MaxRequestsPerConnection: &wrappers.UInt32Value{Value: 10}, 2486 }, 2487 }, 2488 }, 2489 { 2490 name: "ignore TCP idle timeout when HTTP idle timeout is specified", 2491 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 2492 httpProtocolOptions: &http.HttpProtocolOptions{ 2493 CommonHttpProtocolOptions: &core.HttpProtocolOptions{ 2494 MaxRequestsPerConnection: &wrappers.UInt32Value{Value: 10}, 2495 }, 2496 }, 2497 connectionPool: &networking.ConnectionPoolSettings{ 2498 Tcp: &networking.ConnectionPoolSettings_TCPSettings{ 2499 IdleTimeout: &durationpb.Duration{ 2500 Seconds: 10, 2501 }, 2502 }, 2503 Http: &networking.ConnectionPoolSettings_HTTPSettings{ 2504 IdleTimeout: &durationpb.Duration{ 2505 Seconds: 20, 2506 }, 2507 }, 2508 }, 2509 expectedHTTPPOpt: &http.HttpProtocolOptions{ 2510 CommonHttpProtocolOptions: &core.HttpProtocolOptions{ 2511 IdleTimeout: &durationpb.Duration{ 2512 Seconds: 20, 2513 }, 2514 MaxRequestsPerConnection: &wrappers.UInt32Value{Value: 10}, 2515 }, 2516 }, 2517 }, 2518 { 2519 name: "only update MaxRequestsPerConnection ", 2520 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 2521 httpProtocolOptions: &http.HttpProtocolOptions{ 2522 CommonHttpProtocolOptions: &core.HttpProtocolOptions{ 2523 IdleTimeout: &durationpb.Duration{ 2524 Seconds: 10, 2525 }, 2526 MaxRequestsPerConnection: &wrappers.UInt32Value{Value: 10}, 2527 }, 2528 }, 2529 connectionPool: &networking.ConnectionPoolSettings{ 2530 Http: &networking.ConnectionPoolSettings_HTTPSettings{ 2531 MaxRequestsPerConnection: 22, 2532 }, 2533 }, 2534 expectedHTTPPOpt: &http.HttpProtocolOptions{ 2535 CommonHttpProtocolOptions: &core.HttpProtocolOptions{ 2536 IdleTimeout: &durationpb.Duration{ 2537 Seconds: 10, 2538 }, 2539 MaxRequestsPerConnection: &wrappers.UInt32Value{Value: 22}, 2540 }, 2541 }, 2542 }, 2543 { 2544 name: "update multiple fields", 2545 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 2546 httpProtocolOptions: &http.HttpProtocolOptions{ 2547 CommonHttpProtocolOptions: &core.HttpProtocolOptions{ 2548 IdleTimeout: &durationpb.Duration{ 2549 Seconds: 10, 2550 }, 2551 MaxRequestsPerConnection: &wrappers.UInt32Value{Value: 10}, 2552 }, 2553 }, 2554 connectionPool: &networking.ConnectionPoolSettings{ 2555 Http: &networking.ConnectionPoolSettings_HTTPSettings{ 2556 IdleTimeout: &durationpb.Duration{ 2557 Seconds: 22, 2558 }, 2559 MaxRequestsPerConnection: 22, 2560 }, 2561 Tcp: &networking.ConnectionPoolSettings_TCPSettings{ 2562 MaxConnectionDuration: &durationpb.Duration{ 2563 Seconds: 500, 2564 }, 2565 }, 2566 }, 2567 expectedHTTPPOpt: &http.HttpProtocolOptions{ 2568 CommonHttpProtocolOptions: &core.HttpProtocolOptions{ 2569 IdleTimeout: &durationpb.Duration{ 2570 Seconds: 22, 2571 }, 2572 MaxRequestsPerConnection: &wrappers.UInt32Value{Value: 22}, 2573 MaxConnectionDuration: &durationpb.Duration{ 2574 Seconds: 500, 2575 }, 2576 }, 2577 }, 2578 }, 2579 } 2580 2581 for _, tt := range cases { 2582 t.Run(tt.name, func(t *testing.T) { 2583 cg := NewConfigGenTest(t, TestOptions{}) 2584 proxy := cg.SetupProxy(nil) 2585 cb := NewClusterBuilder(proxy, &model.PushRequest{Push: cg.PushContext()}, nil) 2586 mc := &clusterWrapper{ 2587 cluster: tt.cluster, 2588 httpProtocolOptions: tt.httpProtocolOptions, 2589 } 2590 2591 opts := buildClusterOpts{ 2592 mesh: cb.req.Push.Mesh, 2593 mutable: mc, 2594 } 2595 cb.applyConnectionPool(opts.mesh, opts.mutable, tt.connectionPool) 2596 // assert httpProtocolOptions 2597 assert.Equal(t, opts.mutable.httpProtocolOptions.CommonHttpProtocolOptions.IdleTimeout, 2598 tt.expectedHTTPPOpt.CommonHttpProtocolOptions.IdleTimeout) 2599 assert.Equal(t, opts.mutable.httpProtocolOptions.CommonHttpProtocolOptions.MaxRequestsPerConnection, 2600 tt.expectedHTTPPOpt.CommonHttpProtocolOptions.MaxRequestsPerConnection) 2601 assert.Equal(t, opts.mutable.httpProtocolOptions.CommonHttpProtocolOptions.MaxConnectionDuration, 2602 tt.expectedHTTPPOpt.CommonHttpProtocolOptions.MaxConnectionDuration) 2603 }) 2604 } 2605 } 2606 2607 func TestBuildExternalSDSClusters(t *testing.T) { 2608 proxy := &model.Proxy{ 2609 Metadata: &model.NodeMetadata{ 2610 Raw: map[string]any{ 2611 security.CredentialMetaDataName: "true", 2612 }, 2613 }, 2614 } 2615 2616 cases := []struct { 2617 name string 2618 expectedName string 2619 expectedPath string 2620 }{ 2621 { 2622 name: "uds", 2623 expectedName: security.SDSExternalClusterName, 2624 expectedPath: security.CredentialNameSocketPath, 2625 }, 2626 } 2627 for _, tt := range cases { 2628 t.Run(tt.name, func(t *testing.T) { 2629 cg := NewConfigGenTest(t, TestOptions{}) 2630 cb := NewClusterBuilder(cg.SetupProxy(proxy), &model.PushRequest{Push: cg.PushContext()}, nil) 2631 cluster := cb.buildExternalSDSCluster(security.CredentialNameSocketPath) 2632 path := cluster.LoadAssignment.Endpoints[0].LbEndpoints[0].GetEndpoint().Address.GetPipe().Path 2633 anyOptions := cluster.TypedExtensionProtocolOptions[v3.HttpProtocolOptionsType] 2634 if anyOptions == nil { 2635 t.Errorf("cluster has no httpProtocolOptions") 2636 } 2637 if cluster.Name != tt.expectedName { 2638 t.Errorf("Unexpected cluster name, got: %v, want: %v", cluster.Name, tt.expectedName) 2639 } 2640 if path != tt.expectedPath { 2641 t.Errorf("Unexpected path, got: %v, want: %v", path, tt.expectedPath) 2642 } 2643 }) 2644 } 2645 } 2646 2647 func TestInsecureSkipVerify(t *testing.T) { 2648 servicePort := model.PortList{ 2649 &model.Port{ 2650 Name: "default", 2651 Port: 8080, 2652 Protocol: protocol.HTTP, 2653 }, 2654 &model.Port{ 2655 Name: "auto", 2656 Port: 9090, 2657 Protocol: protocol.Unsupported, 2658 }, 2659 } 2660 2661 service := &model.Service{ 2662 Hostname: host.Name("foo.default.svc.cluster.local"), 2663 Ports: servicePort, 2664 Resolution: model.ClientSideLB, 2665 Attributes: model.ServiceAttributes{ 2666 Namespace: TestServiceNamespace, 2667 ServiceRegistry: provider.External, 2668 }, 2669 } 2670 2671 cases := []struct { 2672 name string 2673 cluster *cluster.Cluster 2674 clusterMode ClusterMode 2675 service *model.Service 2676 port *model.Port 2677 proxyView model.ProxyView 2678 destRule *networking.DestinationRule 2679 serviceAcct []string // SE SAN values 2680 enableAutoSni bool 2681 enableVerifyCertAtClient bool 2682 expectTLSContext *tls.UpstreamTlsContext 2683 }{ 2684 { 2685 name: "With tls mode simple, InsecureSkipVerify is not specified and ca cert is supplied", 2686 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 2687 clusterMode: DefaultClusterMode, 2688 service: service, 2689 port: servicePort[0], 2690 proxyView: model.ProxyViewAll, 2691 destRule: &networking.DestinationRule{ 2692 Host: "foo.default.svc.cluster.local", 2693 TrafficPolicy: &networking.TrafficPolicy{ 2694 Tls: &networking.ClientTLSSettings{ 2695 Mode: networking.ClientTLSSettings_SIMPLE, 2696 CaCertificates: constants.RootCertFilename, 2697 Sni: "foo.default.svc.cluster.local", 2698 SubjectAltNames: []string{"foo.default.svc.cluster.local"}, 2699 }, 2700 }, 2701 }, 2702 enableAutoSni: false, 2703 enableVerifyCertAtClient: false, 2704 expectTLSContext: &tls.UpstreamTlsContext{ 2705 CommonTlsContext: &tls.CommonTlsContext{ 2706 TlsParams: &tls.TlsParameters{ 2707 // if not specified, envoy use TLSv1_2 as default for client. 2708 TlsMaximumProtocolVersion: tls.TlsParameters_TLSv1_3, 2709 TlsMinimumProtocolVersion: tls.TlsParameters_TLSv1_2, 2710 }, 2711 ValidationContextType: &tls.CommonTlsContext_CombinedValidationContext{ 2712 CombinedValidationContext: &tls.CommonTlsContext_CombinedCertificateValidationContext{ 2713 DefaultValidationContext: &tls.CertificateValidationContext{MatchSubjectAltNames: util.StringToExactMatch([]string{"foo.default.svc.cluster.local"})}, 2714 ValidationContextSdsSecretConfig: &tls.SdsSecretConfig{ 2715 Name: "file-root:" + constants.RootCertFilename, 2716 SdsConfig: &core.ConfigSource{ 2717 ConfigSourceSpecifier: &core.ConfigSource_ApiConfigSource{ 2718 ApiConfigSource: &core.ApiConfigSource{ 2719 ApiType: core.ApiConfigSource_GRPC, 2720 SetNodeOnFirstMessageOnly: true, 2721 TransportApiVersion: core.ApiVersion_V3, 2722 GrpcServices: []*core.GrpcService{ 2723 { 2724 TargetSpecifier: &core.GrpcService_EnvoyGrpc_{ 2725 EnvoyGrpc: &core.GrpcService_EnvoyGrpc{ClusterName: "sds-grpc"}, 2726 }, 2727 }, 2728 }, 2729 }, 2730 }, 2731 ResourceApiVersion: core.ApiVersion_V3, 2732 }, 2733 }, 2734 }, 2735 }, 2736 }, 2737 Sni: "foo.default.svc.cluster.local", 2738 }, 2739 }, 2740 { 2741 name: "With tls mode simple, InsecureSkipVerify is set false and ca cert is supplied", 2742 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 2743 clusterMode: DefaultClusterMode, 2744 service: service, 2745 port: servicePort[0], 2746 proxyView: model.ProxyViewAll, 2747 destRule: &networking.DestinationRule{ 2748 Host: "foo.default.svc.cluster.local", 2749 TrafficPolicy: &networking.TrafficPolicy{ 2750 Tls: &networking.ClientTLSSettings{ 2751 Mode: networking.ClientTLSSettings_SIMPLE, 2752 CaCertificates: constants.RootCertFilename, 2753 Sni: "foo.default.svc.cluster.local", 2754 SubjectAltNames: []string{"foo.default.svc.cluster.local"}, 2755 InsecureSkipVerify: &wrappers.BoolValue{Value: false}, 2756 }, 2757 }, 2758 }, 2759 enableAutoSni: false, 2760 enableVerifyCertAtClient: false, 2761 expectTLSContext: &tls.UpstreamTlsContext{ 2762 CommonTlsContext: &tls.CommonTlsContext{ 2763 TlsParams: &tls.TlsParameters{ 2764 // if not specified, envoy use TLSv1_2 as default for client. 2765 TlsMaximumProtocolVersion: tls.TlsParameters_TLSv1_3, 2766 TlsMinimumProtocolVersion: tls.TlsParameters_TLSv1_2, 2767 }, 2768 ValidationContextType: &tls.CommonTlsContext_CombinedValidationContext{ 2769 CombinedValidationContext: &tls.CommonTlsContext_CombinedCertificateValidationContext{ 2770 DefaultValidationContext: &tls.CertificateValidationContext{MatchSubjectAltNames: util.StringToExactMatch([]string{"foo.default.svc.cluster.local"})}, 2771 ValidationContextSdsSecretConfig: &tls.SdsSecretConfig{ 2772 Name: "file-root:" + constants.RootCertFilename, 2773 SdsConfig: &core.ConfigSource{ 2774 ConfigSourceSpecifier: &core.ConfigSource_ApiConfigSource{ 2775 ApiConfigSource: &core.ApiConfigSource{ 2776 ApiType: core.ApiConfigSource_GRPC, 2777 SetNodeOnFirstMessageOnly: true, 2778 TransportApiVersion: core.ApiVersion_V3, 2779 GrpcServices: []*core.GrpcService{ 2780 { 2781 TargetSpecifier: &core.GrpcService_EnvoyGrpc_{ 2782 EnvoyGrpc: &core.GrpcService_EnvoyGrpc{ClusterName: "sds-grpc"}, 2783 }, 2784 }, 2785 }, 2786 }, 2787 }, 2788 ResourceApiVersion: core.ApiVersion_V3, 2789 }, 2790 }, 2791 }, 2792 }, 2793 }, 2794 Sni: "foo.default.svc.cluster.local", 2795 }, 2796 }, 2797 { 2798 name: "With tls mode simple, InsecureSkipVerify is set true and env VERIFY_CERTIFICATE_AT_CLIENT is true", 2799 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 2800 clusterMode: DefaultClusterMode, 2801 service: service, 2802 port: servicePort[0], 2803 proxyView: model.ProxyViewAll, 2804 destRule: &networking.DestinationRule{ 2805 Host: "foo.default.svc.cluster.local", 2806 TrafficPolicy: &networking.TrafficPolicy{ 2807 Tls: &networking.ClientTLSSettings{ 2808 Mode: networking.ClientTLSSettings_SIMPLE, 2809 Sni: "foo.default.svc.cluster.local", 2810 SubjectAltNames: []string{"foo.default.svc.cluster.local"}, 2811 InsecureSkipVerify: &wrappers.BoolValue{Value: true}, 2812 }, 2813 }, 2814 }, 2815 enableAutoSni: false, 2816 enableVerifyCertAtClient: true, 2817 expectTLSContext: &tls.UpstreamTlsContext{ 2818 CommonTlsContext: &tls.CommonTlsContext{ 2819 TlsParams: &tls.TlsParameters{ 2820 // if not specified, envoy use TLSv1_2 as default for client. 2821 TlsMaximumProtocolVersion: tls.TlsParameters_TLSv1_3, 2822 TlsMinimumProtocolVersion: tls.TlsParameters_TLSv1_2, 2823 }, 2824 ValidationContextType: &tls.CommonTlsContext_ValidationContext{}, 2825 }, 2826 Sni: "foo.default.svc.cluster.local", 2827 }, 2828 }, 2829 { 2830 name: "With tls mode simple, InsecureSkipVerify is set true and env VERIFY_CERTIFICATE_AT_CLIENT is true and AUTO_SNI is true", 2831 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 2832 clusterMode: DefaultClusterMode, 2833 service: service, 2834 port: servicePort[0], 2835 proxyView: model.ProxyViewAll, 2836 destRule: &networking.DestinationRule{ 2837 Host: "foo.default.svc.cluster.local", 2838 TrafficPolicy: &networking.TrafficPolicy{ 2839 Tls: &networking.ClientTLSSettings{ 2840 Mode: networking.ClientTLSSettings_SIMPLE, 2841 SubjectAltNames: []string{"foo.default.svc.cluster.local"}, 2842 InsecureSkipVerify: &wrappers.BoolValue{Value: true}, 2843 }, 2844 }, 2845 }, 2846 enableAutoSni: true, 2847 enableVerifyCertAtClient: true, 2848 expectTLSContext: &tls.UpstreamTlsContext{ 2849 CommonTlsContext: &tls.CommonTlsContext{ 2850 TlsParams: &tls.TlsParameters{ 2851 // if not specified, envoy use TLSv1_2 as default for client. 2852 TlsMaximumProtocolVersion: tls.TlsParameters_TLSv1_3, 2853 TlsMinimumProtocolVersion: tls.TlsParameters_TLSv1_2, 2854 }, 2855 ValidationContextType: &tls.CommonTlsContext_ValidationContext{}, 2856 }, 2857 }, 2858 }, 2859 { 2860 name: "With tls mode simple and CredentialName, InsecureSkipVerify is set true and env VERIFY_CERTIFICATE_AT_CLIENT is true", 2861 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 2862 clusterMode: DefaultClusterMode, 2863 service: service, 2864 port: servicePort[0], 2865 proxyView: model.ProxyViewAll, 2866 destRule: &networking.DestinationRule{ 2867 Host: "foo.default.svc.cluster.local", 2868 TrafficPolicy: &networking.TrafficPolicy{ 2869 Tls: &networking.ClientTLSSettings{ 2870 Mode: networking.ClientTLSSettings_SIMPLE, 2871 CredentialName: "ca-cert", 2872 Sni: "foo.default.svc.cluster.local", 2873 SubjectAltNames: []string{"foo.default.svc.cluster.local"}, 2874 InsecureSkipVerify: &wrappers.BoolValue{Value: true}, 2875 }, 2876 }, 2877 WorkloadSelector: &v1beta1.WorkloadSelector{}, 2878 }, 2879 enableAutoSni: false, 2880 enableVerifyCertAtClient: true, 2881 expectTLSContext: &tls.UpstreamTlsContext{ 2882 CommonTlsContext: &tls.CommonTlsContext{ 2883 TlsParams: &tls.TlsParameters{ 2884 // if not specified, envoy use TLSv1_2 as default for client. 2885 TlsMaximumProtocolVersion: tls.TlsParameters_TLSv1_3, 2886 TlsMinimumProtocolVersion: tls.TlsParameters_TLSv1_2, 2887 }, 2888 }, 2889 Sni: "foo.default.svc.cluster.local", 2890 }, 2891 }, 2892 { 2893 name: "With tls mode mutual, InsecureSkipVerify is not specified and ca cert is supplied", 2894 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 2895 clusterMode: DefaultClusterMode, 2896 service: service, 2897 port: servicePort[0], 2898 proxyView: model.ProxyViewAll, 2899 destRule: &networking.DestinationRule{ 2900 Host: "foo.default.svc.cluster.local", 2901 TrafficPolicy: &networking.TrafficPolicy{ 2902 Tls: &networking.ClientTLSSettings{ 2903 Mode: networking.ClientTLSSettings_MUTUAL, 2904 ClientCertificate: "cert", 2905 PrivateKey: "key", 2906 CaCertificates: constants.RootCertFilename, 2907 Sni: "foo.default.svc.cluster.local", 2908 SubjectAltNames: []string{"foo.default.svc.cluster.local"}, 2909 }, 2910 }, 2911 }, 2912 enableAutoSni: false, 2913 enableVerifyCertAtClient: false, 2914 expectTLSContext: &tls.UpstreamTlsContext{ 2915 CommonTlsContext: &tls.CommonTlsContext{ 2916 TlsParams: &tls.TlsParameters{ 2917 // if not specified, envoy use TLSv1_2 as default for client. 2918 TlsMaximumProtocolVersion: tls.TlsParameters_TLSv1_3, 2919 TlsMinimumProtocolVersion: tls.TlsParameters_TLSv1_2, 2920 }, 2921 TlsCertificateSdsSecretConfigs: []*tls.SdsSecretConfig{ 2922 { 2923 Name: "file-cert:cert~key", 2924 SdsConfig: &core.ConfigSource{ 2925 ConfigSourceSpecifier: &core.ConfigSource_ApiConfigSource{ 2926 ApiConfigSource: &core.ApiConfigSource{ 2927 ApiType: core.ApiConfigSource_GRPC, 2928 SetNodeOnFirstMessageOnly: true, 2929 TransportApiVersion: core.ApiVersion_V3, 2930 GrpcServices: []*core.GrpcService{ 2931 { 2932 TargetSpecifier: &core.GrpcService_EnvoyGrpc_{ 2933 EnvoyGrpc: &core.GrpcService_EnvoyGrpc{ClusterName: "sds-grpc"}, 2934 }, 2935 }, 2936 }, 2937 }, 2938 }, 2939 ResourceApiVersion: core.ApiVersion_V3, 2940 }, 2941 }, 2942 }, 2943 ValidationContextType: &tls.CommonTlsContext_CombinedValidationContext{ 2944 CombinedValidationContext: &tls.CommonTlsContext_CombinedCertificateValidationContext{ 2945 DefaultValidationContext: &tls.CertificateValidationContext{MatchSubjectAltNames: util.StringToExactMatch([]string{"foo.default.svc.cluster.local"})}, 2946 ValidationContextSdsSecretConfig: &tls.SdsSecretConfig{ 2947 Name: "file-root:" + constants.RootCertFilename, 2948 SdsConfig: &core.ConfigSource{ 2949 ConfigSourceSpecifier: &core.ConfigSource_ApiConfigSource{ 2950 ApiConfigSource: &core.ApiConfigSource{ 2951 ApiType: core.ApiConfigSource_GRPC, 2952 SetNodeOnFirstMessageOnly: true, 2953 TransportApiVersion: core.ApiVersion_V3, 2954 GrpcServices: []*core.GrpcService{ 2955 { 2956 TargetSpecifier: &core.GrpcService_EnvoyGrpc_{ 2957 EnvoyGrpc: &core.GrpcService_EnvoyGrpc{ClusterName: "sds-grpc"}, 2958 }, 2959 }, 2960 }, 2961 }, 2962 }, 2963 ResourceApiVersion: core.ApiVersion_V3, 2964 }, 2965 }, 2966 }, 2967 }, 2968 }, 2969 Sni: "foo.default.svc.cluster.local", 2970 }, 2971 }, 2972 { 2973 name: "With tls mode mutual, InsecureSkipVerify is set false and ca cert is supplied", 2974 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 2975 clusterMode: DefaultClusterMode, 2976 service: service, 2977 port: servicePort[0], 2978 proxyView: model.ProxyViewAll, 2979 destRule: &networking.DestinationRule{ 2980 Host: "foo.default.svc.cluster.local", 2981 TrafficPolicy: &networking.TrafficPolicy{ 2982 Tls: &networking.ClientTLSSettings{ 2983 Mode: networking.ClientTLSSettings_MUTUAL, 2984 ClientCertificate: "cert", 2985 PrivateKey: "key", 2986 CaCertificates: constants.RootCertFilename, 2987 Sni: "foo.default.svc.cluster.local", 2988 SubjectAltNames: []string{"foo.default.svc.cluster.local"}, 2989 InsecureSkipVerify: &wrappers.BoolValue{Value: false}, 2990 }, 2991 }, 2992 }, 2993 enableAutoSni: false, 2994 enableVerifyCertAtClient: false, 2995 expectTLSContext: &tls.UpstreamTlsContext{ 2996 CommonTlsContext: &tls.CommonTlsContext{ 2997 TlsParams: &tls.TlsParameters{ 2998 // if not specified, envoy use TLSv1_2 as default for client. 2999 TlsMaximumProtocolVersion: tls.TlsParameters_TLSv1_3, 3000 TlsMinimumProtocolVersion: tls.TlsParameters_TLSv1_2, 3001 }, 3002 TlsCertificateSdsSecretConfigs: []*tls.SdsSecretConfig{ 3003 { 3004 Name: "file-cert:cert~key", 3005 SdsConfig: &core.ConfigSource{ 3006 ConfigSourceSpecifier: &core.ConfigSource_ApiConfigSource{ 3007 ApiConfigSource: &core.ApiConfigSource{ 3008 ApiType: core.ApiConfigSource_GRPC, 3009 SetNodeOnFirstMessageOnly: true, 3010 TransportApiVersion: core.ApiVersion_V3, 3011 GrpcServices: []*core.GrpcService{ 3012 { 3013 TargetSpecifier: &core.GrpcService_EnvoyGrpc_{ 3014 EnvoyGrpc: &core.GrpcService_EnvoyGrpc{ClusterName: "sds-grpc"}, 3015 }, 3016 }, 3017 }, 3018 }, 3019 }, 3020 ResourceApiVersion: core.ApiVersion_V3, 3021 }, 3022 }, 3023 }, 3024 ValidationContextType: &tls.CommonTlsContext_CombinedValidationContext{ 3025 CombinedValidationContext: &tls.CommonTlsContext_CombinedCertificateValidationContext{ 3026 DefaultValidationContext: &tls.CertificateValidationContext{MatchSubjectAltNames: util.StringToExactMatch([]string{"foo.default.svc.cluster.local"})}, 3027 ValidationContextSdsSecretConfig: &tls.SdsSecretConfig{ 3028 Name: "file-root:" + constants.RootCertFilename, 3029 SdsConfig: &core.ConfigSource{ 3030 ConfigSourceSpecifier: &core.ConfigSource_ApiConfigSource{ 3031 ApiConfigSource: &core.ApiConfigSource{ 3032 ApiType: core.ApiConfigSource_GRPC, 3033 SetNodeOnFirstMessageOnly: true, 3034 TransportApiVersion: core.ApiVersion_V3, 3035 GrpcServices: []*core.GrpcService{ 3036 { 3037 TargetSpecifier: &core.GrpcService_EnvoyGrpc_{ 3038 EnvoyGrpc: &core.GrpcService_EnvoyGrpc{ClusterName: "sds-grpc"}, 3039 }, 3040 }, 3041 }, 3042 }, 3043 }, 3044 ResourceApiVersion: core.ApiVersion_V3, 3045 }, 3046 }, 3047 }, 3048 }, 3049 }, 3050 Sni: "foo.default.svc.cluster.local", 3051 }, 3052 }, 3053 { 3054 name: "With tls mode mutual, InsecureSkipVerify is set true and env VERIFY_CERTIFICATE_AT_CLIENT is true", 3055 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 3056 clusterMode: DefaultClusterMode, 3057 service: service, 3058 port: servicePort[0], 3059 proxyView: model.ProxyViewAll, 3060 destRule: &networking.DestinationRule{ 3061 Host: "foo.default.svc.cluster.local", 3062 TrafficPolicy: &networking.TrafficPolicy{ 3063 Tls: &networking.ClientTLSSettings{ 3064 Mode: networking.ClientTLSSettings_MUTUAL, 3065 ClientCertificate: "cert", 3066 PrivateKey: "key", 3067 Sni: "foo.default.svc.cluster.local", 3068 SubjectAltNames: []string{"foo.default.svc.cluster.local"}, 3069 InsecureSkipVerify: &wrappers.BoolValue{Value: true}, 3070 }, 3071 }, 3072 }, 3073 enableAutoSni: false, 3074 enableVerifyCertAtClient: true, 3075 expectTLSContext: &tls.UpstreamTlsContext{ 3076 CommonTlsContext: &tls.CommonTlsContext{ 3077 TlsParams: &tls.TlsParameters{ 3078 // if not specified, envoy use TLSv1_2 as default for client. 3079 TlsMaximumProtocolVersion: tls.TlsParameters_TLSv1_3, 3080 TlsMinimumProtocolVersion: tls.TlsParameters_TLSv1_2, 3081 }, 3082 TlsCertificateSdsSecretConfigs: []*tls.SdsSecretConfig{ 3083 { 3084 Name: "file-cert:cert~key", 3085 SdsConfig: &core.ConfigSource{ 3086 ConfigSourceSpecifier: &core.ConfigSource_ApiConfigSource{ 3087 ApiConfigSource: &core.ApiConfigSource{ 3088 ApiType: core.ApiConfigSource_GRPC, 3089 SetNodeOnFirstMessageOnly: true, 3090 TransportApiVersion: core.ApiVersion_V3, 3091 GrpcServices: []*core.GrpcService{ 3092 { 3093 TargetSpecifier: &core.GrpcService_EnvoyGrpc_{ 3094 EnvoyGrpc: &core.GrpcService_EnvoyGrpc{ClusterName: "sds-grpc"}, 3095 }, 3096 }, 3097 }, 3098 }, 3099 }, 3100 ResourceApiVersion: core.ApiVersion_V3, 3101 }, 3102 }, 3103 }, 3104 ValidationContextType: &tls.CommonTlsContext_ValidationContext{}, 3105 }, 3106 Sni: "foo.default.svc.cluster.local", 3107 }, 3108 }, 3109 { 3110 name: "With tls mode mutual, InsecureSkipVerify is set true and env VERIFY_CERTIFICATE_AT_CLIENT is true and AUTO_SNI is true", 3111 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 3112 clusterMode: DefaultClusterMode, 3113 service: service, 3114 port: servicePort[0], 3115 proxyView: model.ProxyViewAll, 3116 destRule: &networking.DestinationRule{ 3117 Host: "foo.default.svc.cluster.local", 3118 TrafficPolicy: &networking.TrafficPolicy{ 3119 Tls: &networking.ClientTLSSettings{ 3120 Mode: networking.ClientTLSSettings_MUTUAL, 3121 ClientCertificate: "cert", 3122 PrivateKey: "key", 3123 SubjectAltNames: []string{"foo.default.svc.cluster.local"}, 3124 InsecureSkipVerify: &wrappers.BoolValue{Value: true}, 3125 }, 3126 }, 3127 }, 3128 enableAutoSni: true, 3129 enableVerifyCertAtClient: true, 3130 expectTLSContext: &tls.UpstreamTlsContext{ 3131 CommonTlsContext: &tls.CommonTlsContext{ 3132 TlsParams: &tls.TlsParameters{ 3133 // if not specified, envoy use TLSv1_2 as default for client. 3134 TlsMaximumProtocolVersion: tls.TlsParameters_TLSv1_3, 3135 TlsMinimumProtocolVersion: tls.TlsParameters_TLSv1_2, 3136 }, 3137 TlsCertificateSdsSecretConfigs: []*tls.SdsSecretConfig{ 3138 { 3139 Name: "file-cert:cert~key", 3140 SdsConfig: &core.ConfigSource{ 3141 ConfigSourceSpecifier: &core.ConfigSource_ApiConfigSource{ 3142 ApiConfigSource: &core.ApiConfigSource{ 3143 ApiType: core.ApiConfigSource_GRPC, 3144 SetNodeOnFirstMessageOnly: true, 3145 TransportApiVersion: core.ApiVersion_V3, 3146 GrpcServices: []*core.GrpcService{ 3147 { 3148 TargetSpecifier: &core.GrpcService_EnvoyGrpc_{ 3149 EnvoyGrpc: &core.GrpcService_EnvoyGrpc{ClusterName: "sds-grpc"}, 3150 }, 3151 }, 3152 }, 3153 }, 3154 }, 3155 ResourceApiVersion: core.ApiVersion_V3, 3156 }, 3157 }, 3158 }, 3159 ValidationContextType: &tls.CommonTlsContext_ValidationContext{}, 3160 }, 3161 }, 3162 }, 3163 { 3164 name: "With tls mode mutual and CredentialName, InsecureSkipVerify is set true and env VERIFY_CERTIFICATE_AT_CLIENT is true", 3165 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 3166 clusterMode: DefaultClusterMode, 3167 service: service, 3168 port: servicePort[0], 3169 proxyView: model.ProxyViewAll, 3170 destRule: &networking.DestinationRule{ 3171 Host: "foo.default.svc.cluster.local", 3172 TrafficPolicy: &networking.TrafficPolicy{ 3173 Tls: &networking.ClientTLSSettings{ 3174 Mode: networking.ClientTLSSettings_MUTUAL, 3175 CredentialName: "server-cert", 3176 Sni: "foo.default.svc.cluster.local", 3177 SubjectAltNames: []string{"foo.default.svc.cluster.local"}, 3178 InsecureSkipVerify: &wrappers.BoolValue{Value: true}, 3179 }, 3180 }, 3181 WorkloadSelector: &v1beta1.WorkloadSelector{}, 3182 }, 3183 enableAutoSni: false, 3184 enableVerifyCertAtClient: true, 3185 expectTLSContext: &tls.UpstreamTlsContext{ 3186 CommonTlsContext: &tls.CommonTlsContext{ 3187 TlsParams: &tls.TlsParameters{ 3188 // if not specified, envoy use TLSv1_2 as default for client. 3189 TlsMaximumProtocolVersion: tls.TlsParameters_TLSv1_3, 3190 TlsMinimumProtocolVersion: tls.TlsParameters_TLSv1_2, 3191 }, 3192 TlsCertificateSdsSecretConfigs: []*tls.SdsSecretConfig{ 3193 { 3194 Name: "kubernetes://server-cert", 3195 SdsConfig: &core.ConfigSource{ 3196 ConfigSourceSpecifier: &core.ConfigSource_Ads{ 3197 Ads: &core.AggregatedConfigSource{}, 3198 }, 3199 ResourceApiVersion: core.ApiVersion_V3, 3200 }, 3201 }, 3202 }, 3203 }, 3204 Sni: "foo.default.svc.cluster.local", 3205 }, 3206 }, 3207 { 3208 name: "With tls mode istio mutual, InsecureSkipVerify is set true", 3209 cluster: &cluster.Cluster{Name: "foo", ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_EDS}}, 3210 clusterMode: DefaultClusterMode, 3211 service: service, 3212 port: servicePort[0], 3213 proxyView: model.ProxyViewAll, 3214 destRule: &networking.DestinationRule{ 3215 Host: "foo.default.svc.cluster.local", 3216 TrafficPolicy: &networking.TrafficPolicy{ 3217 Tls: &networking.ClientTLSSettings{ 3218 Mode: networking.ClientTLSSettings_ISTIO_MUTUAL, 3219 Sni: "foo.default.svc.cluster.local", 3220 SubjectAltNames: []string{"foo.default.svc.cluster.local"}, 3221 InsecureSkipVerify: &wrappers.BoolValue{Value: true}, 3222 }, 3223 }, 3224 }, 3225 enableAutoSni: false, 3226 enableVerifyCertAtClient: true, 3227 expectTLSContext: &tls.UpstreamTlsContext{ 3228 CommonTlsContext: &tls.CommonTlsContext{ 3229 TlsParams: &tls.TlsParameters{ 3230 // if not specified, envoy use TLSv1_2 as default for client. 3231 TlsMaximumProtocolVersion: tls.TlsParameters_TLSv1_3, 3232 TlsMinimumProtocolVersion: tls.TlsParameters_TLSv1_2, 3233 }, 3234 TlsCertificateSdsSecretConfigs: []*tls.SdsSecretConfig{ 3235 { 3236 Name: authn_model.SDSDefaultResourceName, 3237 SdsConfig: &core.ConfigSource{ 3238 ConfigSourceSpecifier: &core.ConfigSource_ApiConfigSource{ 3239 ApiConfigSource: &core.ApiConfigSource{ 3240 ApiType: core.ApiConfigSource_GRPC, 3241 SetNodeOnFirstMessageOnly: true, 3242 TransportApiVersion: core.ApiVersion_V3, 3243 GrpcServices: []*core.GrpcService{ 3244 { 3245 TargetSpecifier: &core.GrpcService_EnvoyGrpc_{ 3246 EnvoyGrpc: &core.GrpcService_EnvoyGrpc{ClusterName: "sds-grpc"}, 3247 }, 3248 }, 3249 }, 3250 }, 3251 }, 3252 ResourceApiVersion: core.ApiVersion_V3, 3253 InitialFetchTimeout: durationpb.New(time.Second * 0), 3254 }, 3255 }, 3256 }, 3257 ValidationContextType: &tls.CommonTlsContext_CombinedValidationContext{ 3258 CombinedValidationContext: &tls.CommonTlsContext_CombinedCertificateValidationContext{ 3259 DefaultValidationContext: &tls.CertificateValidationContext{MatchSubjectAltNames: util.StringToExactMatch([]string{"foo.default.svc.cluster.local"})}, 3260 ValidationContextSdsSecretConfig: &tls.SdsSecretConfig{ 3261 Name: authn_model.SDSRootResourceName, 3262 SdsConfig: &core.ConfigSource{ 3263 ConfigSourceSpecifier: &core.ConfigSource_ApiConfigSource{ 3264 ApiConfigSource: &core.ApiConfigSource{ 3265 ApiType: core.ApiConfigSource_GRPC, 3266 SetNodeOnFirstMessageOnly: true, 3267 TransportApiVersion: core.ApiVersion_V3, 3268 GrpcServices: []*core.GrpcService{ 3269 { 3270 TargetSpecifier: &core.GrpcService_EnvoyGrpc_{ 3271 EnvoyGrpc: &core.GrpcService_EnvoyGrpc{ClusterName: "sds-grpc"}, 3272 }, 3273 }, 3274 }, 3275 }, 3276 }, 3277 ResourceApiVersion: core.ApiVersion_V3, 3278 InitialFetchTimeout: durationpb.New(time.Second * 0), 3279 }, 3280 }, 3281 }, 3282 }, 3283 AlpnProtocols: util.ALPNInMeshWithMxc, 3284 }, 3285 Sni: "foo.default.svc.cluster.local", 3286 }, 3287 }, 3288 } 3289 3290 for _, tc := range cases { 3291 t.Run(tc.name, func(t *testing.T) { 3292 test.SetForTest(t, &features.EnableAutoSni, tc.enableAutoSni) 3293 test.SetForTest(t, &features.VerifyCertAtClient, tc.enableVerifyCertAtClient) 3294 3295 targets := []model.ServiceTarget{ 3296 { 3297 Service: tc.service, 3298 Port: model.ServiceInstancePort{ 3299 ServicePort: tc.port, 3300 TargetPort: 10001, 3301 }, 3302 }, 3303 } 3304 3305 var cfg *config.Config 3306 if tc.destRule != nil { 3307 cfg = &config.Config{ 3308 Meta: config.Meta{ 3309 GroupVersionKind: gvk.DestinationRule, 3310 Name: "acme", 3311 Namespace: "default", 3312 }, 3313 Spec: tc.destRule, 3314 } 3315 } 3316 3317 cg := NewConfigGenTest(t, TestOptions{ 3318 ConfigPointers: []*config.Config{cfg}, 3319 Services: []*model.Service{tc.service}, 3320 }) 3321 3322 cg.MemRegistry.WantGetProxyServiceTargets = targets 3323 proxy := cg.SetupProxy(nil) 3324 cb := NewClusterBuilder(proxy, &model.PushRequest{Push: cg.PushContext()}, nil) 3325 ec := newClusterWrapper(tc.cluster) 3326 tc.cluster.CommonLbConfig = &cluster.Cluster_CommonLbConfig{} 3327 destRule := proxy.SidecarScope.DestinationRule(model.TrafficDirectionOutbound, proxy, tc.service.Hostname) 3328 eb := endpoints.NewCDSEndpointBuilder(proxy, cb.req.Push, tc.cluster.Name, 3329 model.TrafficDirectionOutbound, "", service.Hostname, tc.port.Port, 3330 service, destRule) 3331 3332 _ = cb.applyDestinationRule(ec, tc.clusterMode, tc.service, tc.port, eb, destRule.GetRule(), tc.serviceAcct) 3333 3334 result := getTLSContext(t, ec.cluster) 3335 if diff := cmp.Diff(result, tc.expectTLSContext, protocmp.Transform()); diff != "" { 3336 t.Errorf("got diff: `%v", diff) 3337 } 3338 3339 if tc.enableAutoSni { 3340 if tc.destRule.GetTrafficPolicy().GetTls().Sni == "" { 3341 assert.Equal(t, ec.httpProtocolOptions.UpstreamHttpProtocolOptions.AutoSni, true) 3342 } 3343 3344 if tc.destRule.GetTrafficPolicy().GetTls().GetInsecureSkipVerify().GetValue() { 3345 assert.Equal(t, ec.httpProtocolOptions.UpstreamHttpProtocolOptions.AutoSanValidation, false) 3346 } else if tc.enableVerifyCertAtClient && len(tc.destRule.GetTrafficPolicy().GetTls().SubjectAltNames) == 0 { 3347 assert.Equal(t, ec.httpProtocolOptions.UpstreamHttpProtocolOptions.AutoSanValidation, true) 3348 } 3349 } 3350 }) 3351 } 3352 }