istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/model/push_context_test.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package model 16 17 import ( 18 "fmt" 19 "reflect" 20 "regexp" 21 "sort" 22 "sync" 23 "testing" 24 "time" 25 26 "github.com/google/go-cmp/cmp" 27 "github.com/google/go-cmp/cmp/cmpopts" 28 . "github.com/onsi/gomega" 29 "go.uber.org/atomic" 30 "google.golang.org/protobuf/testing/protocmp" 31 "google.golang.org/protobuf/types/known/durationpb" 32 "google.golang.org/protobuf/types/known/structpb" 33 "google.golang.org/protobuf/types/known/wrapperspb" 34 "k8s.io/apimachinery/pkg/types" 35 36 extensions "istio.io/api/extensions/v1alpha1" 37 meshconfig "istio.io/api/mesh/v1alpha1" 38 networking "istio.io/api/networking/v1alpha3" 39 securityBeta "istio.io/api/security/v1beta1" 40 selectorpb "istio.io/api/type/v1beta1" 41 "istio.io/istio/pilot/pkg/features" 42 istionetworking "istio.io/istio/pilot/pkg/networking" 43 "istio.io/istio/pilot/pkg/serviceregistry/provider" 44 "istio.io/istio/pkg/config" 45 "istio.io/istio/pkg/config/constants" 46 "istio.io/istio/pkg/config/host" 47 "istio.io/istio/pkg/config/labels" 48 "istio.io/istio/pkg/config/mesh" 49 "istio.io/istio/pkg/config/schema/gvk" 50 "istio.io/istio/pkg/config/schema/kind" 51 "istio.io/istio/pkg/config/visibility" 52 "istio.io/istio/pkg/maps" 53 "istio.io/istio/pkg/slices" 54 "istio.io/istio/pkg/test" 55 "istio.io/istio/pkg/test/util/assert" 56 "istio.io/istio/pkg/util/protomarshal" 57 "istio.io/istio/pkg/util/sets" 58 ) 59 60 func TestMergeUpdateRequest(t *testing.T) { 61 push0 := &PushContext{} 62 // trivially different push contexts just for testing 63 push1 := &PushContext{ProxyStatus: make(map[string]map[string]ProxyPushStatus)} 64 65 var t0 time.Time 66 t1 := t0.Add(time.Minute) 67 68 cases := []struct { 69 name string 70 left *PushRequest 71 right *PushRequest 72 merged PushRequest 73 }{ 74 { 75 "left nil", 76 nil, 77 &PushRequest{Full: true}, 78 PushRequest{Full: true}, 79 }, 80 { 81 "right nil", 82 &PushRequest{Full: true}, 83 nil, 84 PushRequest{Full: true}, 85 }, 86 { 87 "simple merge", 88 &PushRequest{ 89 Full: true, 90 Push: push0, 91 Start: t0, 92 ConfigsUpdated: sets.Set[ConfigKey]{ 93 {Kind: kind.Kind(1), Namespace: "ns1"}: {}, 94 }, 95 Reason: NewReasonStats(ServiceUpdate, ServiceUpdate), 96 }, 97 &PushRequest{ 98 Full: false, 99 Push: push1, 100 Start: t1, 101 ConfigsUpdated: sets.Set[ConfigKey]{ 102 {Kind: kind.Kind(2), Namespace: "ns2"}: {}, 103 }, 104 Reason: NewReasonStats(EndpointUpdate), 105 }, 106 PushRequest{ 107 Full: true, 108 Push: push1, 109 Start: t0, 110 ConfigsUpdated: sets.Set[ConfigKey]{ 111 {Kind: kind.Kind(1), Namespace: "ns1"}: {}, 112 {Kind: kind.Kind(2), Namespace: "ns2"}: {}, 113 }, 114 Reason: NewReasonStats(ServiceUpdate, ServiceUpdate, EndpointUpdate), 115 }, 116 }, 117 { 118 "skip config type merge: one empty", 119 &PushRequest{Full: true, ConfigsUpdated: nil}, 120 &PushRequest{Full: true, ConfigsUpdated: sets.Set[ConfigKey]{{ 121 Kind: kind.Kind(2), 122 }: {}}}, 123 PushRequest{Full: true, ConfigsUpdated: nil, Reason: nil}, 124 }, 125 } 126 127 for _, tt := range cases { 128 t.Run(tt.name, func(t *testing.T) { 129 got := tt.left.CopyMerge(tt.right) 130 if !reflect.DeepEqual(&tt.merged, got) { 131 t.Fatalf("expected %v, got %v", &tt.merged, got) 132 } 133 got = tt.left.Merge(tt.right) 134 if !reflect.DeepEqual(&tt.merged, got) { 135 t.Fatalf("expected %v, got %v", &tt.merged, got) 136 } 137 }) 138 } 139 } 140 141 func TestConcurrentMerge(t *testing.T) { 142 reqA := &PushRequest{Reason: make(ReasonStats)} 143 reqB := &PushRequest{Reason: NewReasonStats(ServiceUpdate, ProxyUpdate)} 144 for i := 0; i < 50; i++ { 145 go func() { 146 reqA.CopyMerge(reqB) 147 }() 148 } 149 if reqA.Reason.Count() != 0 { 150 t.Fatalf("reqA modified: %v", reqA.Reason) 151 } 152 if reqB.Reason.Count() != 2 { 153 t.Fatalf("reqB modified: %v", reqB.Reason) 154 } 155 } 156 157 func TestEnvoyFilters(t *testing.T) { 158 proxyVersionRegex := regexp.MustCompile(`1\.4.*`) 159 envoyFilters := []*EnvoyFilterWrapper{ 160 { 161 Name: "ef1", 162 workloadSelector: map[string]string{"app": "v1"}, 163 Patches: map[networking.EnvoyFilter_ApplyTo][]*EnvoyFilterConfigPatchWrapper{ 164 networking.EnvoyFilter_LISTENER: { 165 { 166 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 167 Proxy: &networking.EnvoyFilter_ProxyMatch{ 168 ProxyVersion: "1\\.4.*", 169 }, 170 }, 171 ProxyVersionRegex: proxyVersionRegex, 172 }, 173 }, 174 }, 175 }, 176 { 177 Name: "ef2", 178 workloadSelector: map[string]string{"app": "v1"}, 179 Patches: map[networking.EnvoyFilter_ApplyTo][]*EnvoyFilterConfigPatchWrapper{ 180 networking.EnvoyFilter_CLUSTER: { 181 { 182 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 183 Proxy: &networking.EnvoyFilter_ProxyMatch{ 184 ProxyVersion: `1\\.4.*`, 185 }, 186 }, 187 ProxyVersionRegex: proxyVersionRegex, 188 }, 189 }, 190 }, 191 }, 192 } 193 194 push := &PushContext{ 195 Mesh: &meshconfig.MeshConfig{ 196 RootNamespace: "istio-system", 197 }, 198 envoyFiltersByNamespace: map[string][]*EnvoyFilterWrapper{ 199 "istio-system": envoyFilters, 200 "test-ns": envoyFilters, 201 }, 202 } 203 204 if !push.HasEnvoyFilters("ef1", "test-ns") { 205 t.Errorf("Check presence of EnvoyFilter ef1 at test-ns got false, want true") 206 } 207 if push.HasEnvoyFilters("ef3", "test-ns") { 208 t.Errorf("Check presence of EnvoyFilter ef3 at test-ns got true, want false") 209 } 210 211 cases := []struct { 212 name string 213 proxy *Proxy 214 expectedListenerPatches int 215 expectedClusterPatches int 216 }{ 217 { 218 name: "proxy matches two envoyfilters", 219 proxy: &Proxy{ 220 Labels: map[string]string{"app": "v1"}, 221 Metadata: &NodeMetadata{IstioVersion: "1.4.0", Labels: map[string]string{"app": "v1"}}, 222 ConfigNamespace: "test-ns", 223 }, 224 expectedListenerPatches: 2, 225 expectedClusterPatches: 2, 226 }, 227 { 228 name: "proxy in root namespace matches an envoyfilter", 229 proxy: &Proxy{ 230 Labels: map[string]string{"app": "v1"}, 231 Metadata: &NodeMetadata{IstioVersion: "1.4.0", Labels: map[string]string{"app": "v1"}}, 232 ConfigNamespace: "istio-system", 233 }, 234 expectedListenerPatches: 1, 235 expectedClusterPatches: 1, 236 }, 237 238 { 239 name: "proxy matches no envoyfilter", 240 proxy: &Proxy{ 241 Labels: map[string]string{"app": "v2"}, 242 Metadata: &NodeMetadata{IstioVersion: "1.4.0", Labels: map[string]string{"app": "v2"}}, 243 ConfigNamespace: "test-ns", 244 }, 245 expectedListenerPatches: 0, 246 expectedClusterPatches: 0, 247 }, 248 { 249 name: "proxy matches envoyfilter in root ns", 250 proxy: &Proxy{ 251 Labels: map[string]string{"app": "v1"}, 252 Metadata: &NodeMetadata{IstioVersion: "1.4.0", Labels: map[string]string{"app": "v1"}}, 253 ConfigNamespace: "test-n2", 254 }, 255 expectedListenerPatches: 1, 256 expectedClusterPatches: 1, 257 }, 258 { 259 name: "proxy version matches no envoyfilters", 260 proxy: &Proxy{ 261 Labels: map[string]string{"app": "v1"}, 262 Metadata: &NodeMetadata{IstioVersion: "1.3.0", Labels: map[string]string{"app": "v1"}}, 263 ConfigNamespace: "test-ns", 264 }, 265 expectedListenerPatches: 0, 266 expectedClusterPatches: 0, 267 }, 268 } 269 270 for _, tt := range cases { 271 t.Run(tt.name, func(t *testing.T) { 272 filter := push.EnvoyFilters(tt.proxy) 273 if filter == nil { 274 if tt.expectedClusterPatches != 0 || tt.expectedListenerPatches != 0 { 275 t.Errorf("Got no envoy filter") 276 } 277 return 278 } 279 if len(filter.Patches[networking.EnvoyFilter_CLUSTER]) != tt.expectedClusterPatches { 280 t.Errorf("Expect %d envoy filter cluster patches, but got %d", tt.expectedClusterPatches, len(filter.Patches[networking.EnvoyFilter_CLUSTER])) 281 } 282 if len(filter.Patches[networking.EnvoyFilter_LISTENER]) != tt.expectedListenerPatches { 283 t.Errorf("Expect %d envoy filter listener patches, but got %d", tt.expectedListenerPatches, len(filter.Patches[networking.EnvoyFilter_LISTENER])) 284 } 285 }) 286 } 287 } 288 289 func TestEnvoyFilterOrder(t *testing.T) { 290 env := &Environment{} 291 store := NewFakeStore() 292 293 ctime := time.Now() 294 295 envoyFilters := []config.Config{ 296 { 297 Meta: config.Meta{Name: "default-priority", Namespace: "testns-1", GroupVersionKind: gvk.EnvoyFilter}, 298 Spec: &networking.EnvoyFilter{ 299 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 300 { 301 Patch: &networking.EnvoyFilter_Patch{}, 302 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 303 Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`}, 304 }, 305 }, 306 }, 307 }, 308 }, 309 { 310 Meta: config.Meta{Name: "default-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter}, 311 Spec: &networking.EnvoyFilter{ 312 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 313 { 314 Patch: &networking.EnvoyFilter_Patch{}, 315 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 316 Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`}, 317 }, 318 }, 319 }, 320 }, 321 }, 322 { 323 Meta: config.Meta{Name: "b-medium-priority", Namespace: "testns-1", GroupVersionKind: gvk.EnvoyFilter, CreationTimestamp: ctime}, 324 Spec: &networking.EnvoyFilter{ 325 Priority: 10, 326 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 327 { 328 Patch: &networking.EnvoyFilter_Patch{}, 329 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 330 Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`}, 331 }, 332 }, 333 }, 334 }, 335 }, 336 { 337 Meta: config.Meta{Name: "a-medium-priority", Namespace: "testns-1", GroupVersionKind: gvk.EnvoyFilter, CreationTimestamp: ctime}, 338 Spec: &networking.EnvoyFilter{ 339 Priority: 10, 340 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 341 { 342 Patch: &networking.EnvoyFilter_Patch{}, 343 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 344 Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`}, 345 }, 346 }, 347 }, 348 }, 349 }, 350 { 351 Meta: config.Meta{Name: "b-medium-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter, CreationTimestamp: ctime}, 352 Spec: &networking.EnvoyFilter{ 353 Priority: 10, 354 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 355 { 356 Patch: &networking.EnvoyFilter_Patch{}, 357 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 358 Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`}, 359 }, 360 }, 361 }, 362 }, 363 }, 364 { 365 Meta: config.Meta{Name: "a-medium-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter, CreationTimestamp: ctime}, 366 Spec: &networking.EnvoyFilter{ 367 Priority: 10, 368 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 369 { 370 Patch: &networking.EnvoyFilter_Patch{}, 371 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 372 Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`}, 373 }, 374 }, 375 }, 376 }, 377 }, 378 { 379 Meta: config.Meta{Name: "a-low-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter, CreationTimestamp: time.Now()}, 380 Spec: &networking.EnvoyFilter{ 381 Priority: 20, 382 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 383 { 384 Patch: &networking.EnvoyFilter_Patch{}, 385 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 386 Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`}, 387 }, 388 }, 389 }, 390 }, 391 }, 392 { 393 Meta: config.Meta{Name: "b-low-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter, CreationTimestamp: ctime}, 394 Spec: &networking.EnvoyFilter{ 395 Priority: 20, 396 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 397 { 398 Patch: &networking.EnvoyFilter_Patch{}, 399 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 400 Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`}, 401 }, 402 }, 403 }, 404 }, 405 }, 406 { 407 Meta: config.Meta{Name: "high-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter}, 408 Spec: &networking.EnvoyFilter{ 409 Priority: -1, 410 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 411 { 412 Patch: &networking.EnvoyFilter_Patch{}, 413 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 414 Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`}, 415 }, 416 }, 417 }, 418 }, 419 }, 420 { 421 Meta: config.Meta{Name: "super-high-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter}, 422 Spec: &networking.EnvoyFilter{ 423 Priority: -10, 424 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 425 { 426 Patch: &networking.EnvoyFilter_Patch{}, 427 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 428 Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`}, 429 }, 430 }, 431 }, 432 }, 433 }, 434 } 435 436 expectedns := []string{ 437 "testns/super-high-priority", "testns/high-priority", "testns/default-priority", "testns/a-medium-priority", 438 "testns/b-medium-priority", "testns/b-low-priority", "testns/a-low-priority", 439 } 440 441 expectedns1 := []string{"testns-1/default-priority", "testns-1/a-medium-priority", "testns-1/b-medium-priority"} 442 443 for _, cfg := range envoyFilters { 444 _, _ = store.Create(cfg) 445 } 446 env.ConfigStore = store 447 m := mesh.DefaultMeshConfig() 448 env.Watcher = mesh.NewFixedWatcher(m) 449 env.Init() 450 451 // Init a new push context 452 pc := NewPushContext() 453 pc.initEnvoyFilters(env, nil, nil) 454 gotns := make([]string, 0) 455 for _, filter := range pc.envoyFiltersByNamespace["testns"] { 456 gotns = append(gotns, filter.Keys()...) 457 } 458 gotns1 := make([]string, 0) 459 for _, filter := range pc.envoyFiltersByNamespace["testns-1"] { 460 gotns1 = append(gotns1, filter.Keys()...) 461 } 462 if !reflect.DeepEqual(expectedns, gotns) { 463 t.Errorf("Envoy filters are not ordered as expected. expected: %v got: %v", expectedns, gotns) 464 } 465 if !reflect.DeepEqual(expectedns1, gotns1) { 466 t.Errorf("Envoy filters are not ordered as expected. expected: %v got: %v", expectedns1, gotns1) 467 } 468 } 469 470 func buildPatchStruct(config string) *structpb.Struct { 471 val := &structpb.Struct{} 472 _ = protomarshal.UnmarshalString(config, val) 473 return val 474 } 475 476 func TestEnvoyFilterOrderAcrossNamespaces(t *testing.T) { 477 env := &Environment{} 478 store := NewFakeStore() 479 480 proxy := &Proxy{ 481 Metadata: &NodeMetadata{IstioVersion: "foobar"}, 482 Labels: map[string]string{"app": "v1"}, 483 ConfigNamespace: "test-ns", 484 } 485 486 envoyFilters := []config.Config{ 487 { 488 Meta: config.Meta{Name: "filter-1", Namespace: "test-ns", GroupVersionKind: gvk.EnvoyFilter}, 489 Spec: &networking.EnvoyFilter{ 490 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 491 { 492 ApplyTo: networking.EnvoyFilter_HTTP_FILTER, 493 Patch: &networking.EnvoyFilter_Patch{ 494 Operation: networking.EnvoyFilter_Patch_INSERT_BEFORE, 495 Value: buildPatchStruct(`{"name": "filter-1"}`), 496 }, 497 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 498 Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`}, 499 }, 500 }, 501 }, 502 Priority: -5, 503 }, 504 }, 505 { 506 Meta: config.Meta{Name: "filter-2", Namespace: "istio-system", GroupVersionKind: gvk.EnvoyFilter}, 507 Spec: &networking.EnvoyFilter{ 508 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 509 { 510 ApplyTo: networking.EnvoyFilter_HTTP_FILTER, 511 Patch: &networking.EnvoyFilter_Patch{ 512 Operation: networking.EnvoyFilter_Patch_INSERT_BEFORE, 513 Value: buildPatchStruct(`{"name": "filter-2"}`), 514 }, 515 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 516 Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`}, 517 }, 518 }, 519 }, 520 Priority: -1, 521 }, 522 }, 523 } 524 525 expectedFilterOrder := []string{"test-ns/filter-1", "istio-system/filter-2"} 526 for _, cfg := range envoyFilters { 527 _, _ = store.Create(cfg) 528 } 529 env.ConfigStore = store 530 m := mesh.DefaultMeshConfig() 531 m.RootNamespace = "istio-system" 532 env.Watcher = mesh.NewFixedWatcher(m) 533 env.Init() 534 535 // Init a new push context 536 pc := NewPushContext() 537 pc.Mesh = m 538 pc.initEnvoyFilters(env, nil, nil) 539 got := make([]string, 0) 540 efs := pc.EnvoyFilters(proxy) 541 for _, filter := range efs.Patches[networking.EnvoyFilter_HTTP_FILTER] { 542 got = append(got, filter.Namespace+"/"+filter.Name) 543 } 544 if !slices.Equal(expectedFilterOrder, got) { 545 t.Errorf("Envoy filters are not ordered as expected. expected: %v got: %v", expectedFilterOrder, got) 546 } 547 } 548 549 func TestEnvoyFilterUpdate(t *testing.T) { 550 ctime := time.Now() 551 552 initialEnvoyFilters := []config.Config{ 553 { 554 Meta: config.Meta{Name: "default-priority", Namespace: "testns-1", GroupVersionKind: gvk.EnvoyFilter}, 555 Spec: &networking.EnvoyFilter{ 556 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 557 { 558 Patch: &networking.EnvoyFilter_Patch{}, 559 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 560 Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`}, 561 }, 562 }, 563 }, 564 }, 565 }, 566 { 567 Meta: config.Meta{Name: "default-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter}, 568 Spec: &networking.EnvoyFilter{ 569 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 570 { 571 Patch: &networking.EnvoyFilter_Patch{}, 572 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 573 Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`}, 574 }, 575 }, 576 }, 577 }, 578 }, 579 { 580 Meta: config.Meta{Name: "b-medium-priority", Namespace: "testns-1", GroupVersionKind: gvk.EnvoyFilter, CreationTimestamp: ctime}, 581 Spec: &networking.EnvoyFilter{ 582 Priority: 10, 583 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 584 { 585 Patch: &networking.EnvoyFilter_Patch{}, 586 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 587 Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`}, 588 }, 589 }, 590 }, 591 }, 592 }, 593 { 594 Meta: config.Meta{Name: "a-medium-priority", Namespace: "testns-1", GroupVersionKind: gvk.EnvoyFilter, CreationTimestamp: ctime}, 595 Spec: &networking.EnvoyFilter{ 596 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 597 { 598 Patch: &networking.EnvoyFilter_Patch{}, 599 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 600 Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`}, 601 }, 602 }, 603 }, 604 }, 605 }, 606 { 607 Meta: config.Meta{Name: "b-medium-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter, CreationTimestamp: ctime}, 608 Spec: &networking.EnvoyFilter{ 609 Priority: 10, 610 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 611 { 612 Patch: &networking.EnvoyFilter_Patch{}, 613 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 614 Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`}, 615 }, 616 }, 617 }, 618 }, 619 }, 620 { 621 Meta: config.Meta{Name: "a-medium-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter, CreationTimestamp: ctime}, 622 Spec: &networking.EnvoyFilter{ 623 Priority: 10, 624 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 625 { 626 Patch: &networking.EnvoyFilter_Patch{}, 627 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 628 Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`}, 629 }, 630 }, 631 }, 632 }, 633 }, 634 { 635 Meta: config.Meta{Name: "a-low-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter, CreationTimestamp: time.Now()}, 636 Spec: &networking.EnvoyFilter{ 637 Priority: 20, 638 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 639 { 640 Patch: &networking.EnvoyFilter_Patch{}, 641 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 642 Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`}, 643 }, 644 }, 645 }, 646 }, 647 }, 648 { 649 Meta: config.Meta{Name: "b-low-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter, CreationTimestamp: ctime}, 650 Spec: &networking.EnvoyFilter{ 651 Priority: 20, 652 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 653 { 654 Patch: &networking.EnvoyFilter_Patch{}, 655 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 656 Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`}, 657 }, 658 }, 659 }, 660 }, 661 }, 662 { 663 Meta: config.Meta{Name: "high-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter}, 664 Spec: &networking.EnvoyFilter{ 665 Priority: -1, 666 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 667 { 668 Patch: &networking.EnvoyFilter_Patch{}, 669 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 670 Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`}, 671 }, 672 }, 673 }, 674 }, 675 }, 676 { 677 Meta: config.Meta{Name: "super-high-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter}, 678 Spec: &networking.EnvoyFilter{ 679 Priority: -10, 680 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 681 { 682 Patch: &networking.EnvoyFilter_Patch{}, 683 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 684 Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobar`}, 685 }, 686 }, 687 }, 688 }, 689 }, 690 } 691 692 cases := []struct { 693 name string 694 creates []config.Config 695 updates []config.Config 696 deletes []ConfigKey 697 }{ 698 { 699 name: "create one", 700 creates: []config.Config{ 701 { 702 Meta: config.Meta{Name: "default-priority-2", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter}, 703 Spec: &networking.EnvoyFilter{ 704 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 705 { 706 Patch: &networking.EnvoyFilter_Patch{}, 707 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 708 Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobaz`}, 709 }, 710 }, 711 }, 712 }, 713 }, 714 }, 715 updates: []config.Config{}, 716 deletes: []ConfigKey{}, 717 }, 718 { 719 name: "update one", 720 creates: []config.Config{}, 721 updates: []config.Config{ 722 { 723 Meta: config.Meta{Name: "default-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter}, 724 Spec: &networking.EnvoyFilter{ 725 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 726 { 727 Patch: &networking.EnvoyFilter_Patch{}, 728 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 729 Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobaz`}, 730 }, 731 }, 732 }, 733 }, 734 }, 735 }, 736 deletes: []ConfigKey{}, 737 }, 738 { 739 name: "delete one", 740 creates: []config.Config{}, 741 updates: []config.Config{}, 742 deletes: []ConfigKey{{Kind: kind.EnvoyFilter, Name: "default-priority", Namespace: "testns"}}, 743 }, 744 { 745 name: "create and delete one same namespace", 746 creates: []config.Config{ 747 { 748 Meta: config.Meta{Name: "default-priority-2", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter}, 749 Spec: &networking.EnvoyFilter{ 750 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 751 { 752 Patch: &networking.EnvoyFilter_Patch{}, 753 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 754 Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobaz`}, 755 }, 756 }, 757 }, 758 }, 759 }, 760 }, 761 updates: []config.Config{}, 762 deletes: []ConfigKey{{Kind: kind.EnvoyFilter, Name: "default-priority", Namespace: "testns"}}, 763 }, 764 { 765 name: "create and delete one different namespace", 766 creates: []config.Config{ 767 { 768 Meta: config.Meta{Name: "default-priority-2", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter}, 769 Spec: &networking.EnvoyFilter{ 770 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 771 { 772 Patch: &networking.EnvoyFilter_Patch{}, 773 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 774 Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobaz`}, 775 }, 776 }, 777 }, 778 }, 779 }, 780 }, 781 updates: []config.Config{}, 782 deletes: []ConfigKey{{Kind: kind.EnvoyFilter, Name: "default-priority", Namespace: "testns-1"}}, 783 }, 784 { 785 name: "create, update delete", 786 creates: []config.Config{ 787 { 788 Meta: config.Meta{Name: "default-priority-2", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter}, 789 Spec: &networking.EnvoyFilter{ 790 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 791 { 792 Patch: &networking.EnvoyFilter_Patch{}, 793 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 794 Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobaz`}, 795 }, 796 }, 797 }, 798 }, 799 }, 800 { 801 Meta: config.Meta{Name: "default-priority-3", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter}, 802 Spec: &networking.EnvoyFilter{ 803 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 804 { 805 Patch: &networking.EnvoyFilter_Patch{}, 806 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 807 Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobaz`}, 808 }, 809 }, 810 }, 811 }, 812 }, 813 }, 814 updates: []config.Config{ 815 { 816 Meta: config.Meta{Name: "default-priority", Namespace: "testns", GroupVersionKind: gvk.EnvoyFilter}, 817 Spec: &networking.EnvoyFilter{ 818 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 819 { 820 Patch: &networking.EnvoyFilter_Patch{}, 821 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 822 Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobaz`}, 823 }, 824 }, 825 }, 826 }, 827 }, 828 { 829 Meta: config.Meta{Name: "default-priority", Namespace: "testns-1", GroupVersionKind: gvk.EnvoyFilter}, 830 Spec: &networking.EnvoyFilter{ 831 ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ 832 { 833 Patch: &networking.EnvoyFilter_Patch{}, 834 Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ 835 Proxy: &networking.EnvoyFilter_ProxyMatch{ProxyVersion: `foobaz`}, 836 }, 837 }, 838 }, 839 }, 840 }, 841 }, 842 deletes: []ConfigKey{{Kind: kind.EnvoyFilter, Name: "b-medium-priority", Namespace: "testns-1"}}, 843 }, 844 { 845 name: "delete entire namespace", 846 creates: []config.Config{}, 847 updates: []config.Config{}, 848 deletes: []ConfigKey{ 849 {Kind: kind.EnvoyFilter, Name: "default-priority", Namespace: "testns-1"}, 850 {Kind: kind.EnvoyFilter, Name: "b-medium-priority", Namespace: "testns-1"}, 851 {Kind: kind.EnvoyFilter, Name: "a-medium-priority", Namespace: "testns-1"}, 852 }, 853 }, 854 } 855 856 OptimizedConfigRebuildModes := []bool{true, false} 857 for _, tt := range cases { 858 for _, mode := range OptimizedConfigRebuildModes { 859 t.Run(tt.name, func(t *testing.T) { 860 test.SetForTest(t, &features.OptimizedConfigRebuild, mode) 861 env := &Environment{} 862 store := NewFakeStore() 863 for _, cfg := range initialEnvoyFilters { 864 _, _ = store.Create(cfg) 865 } 866 env.ConfigStore = store 867 m := mesh.DefaultMeshConfig() 868 env.Watcher = mesh.NewFixedWatcher(m) 869 env.Init() 870 871 // Init a new push context 872 pc1 := NewPushContext() 873 pc1.initEnvoyFilters(env, nil, nil) 874 875 // Update store with incoming changes 876 creates := map[ConfigKey]config.Config{} 877 for _, cfg := range tt.creates { 878 if _, err := store.Create(cfg); err != nil { 879 t.Errorf("Error creating config %s/%s", cfg.Namespace, cfg.Name) 880 } 881 creates[ConfigKey{Name: cfg.Name, Namespace: cfg.Namespace, Kind: kind.EnvoyFilter}] = cfg 882 } 883 updates := map[ConfigKey]config.Config{} 884 for _, cfg := range tt.updates { 885 if _, err := store.Update(cfg); err != nil { 886 t.Errorf("Error updating config %s/%s", cfg.Namespace, cfg.Name) 887 } 888 updates[ConfigKey{Name: cfg.Name, Namespace: cfg.Namespace, Kind: kind.EnvoyFilter}] = cfg 889 } 890 deletes := sets.Set[ConfigKey]{} 891 for _, key := range tt.deletes { 892 store.Delete(gvk.EnvoyFilter, key.Name, key.Namespace, nil) 893 deletes.Insert(key) 894 } 895 896 createSet := sets.New(maps.Keys(creates)...) 897 updateSet := sets.New(maps.Keys(updates)...) 898 changes := deletes.Union(createSet).Union(updateSet) 899 900 pc2 := NewPushContext() 901 pc2.initEnvoyFilters(env, changes, pc1.envoyFiltersByNamespace) 902 903 total2 := 0 904 for ns, envoyFilters := range pc2.envoyFiltersByNamespace { 905 total2 += len(envoyFilters) 906 for _, ef := range envoyFilters { 907 key := ConfigKey{Kind: kind.EnvoyFilter, Namespace: ns, Name: ef.Name} 908 previousVersion := slices.FindFunc(pc1.envoyFiltersByNamespace[ns], func(e *EnvoyFilterWrapper) bool { 909 return e.Name == ef.Name 910 }) 911 switch { 912 // Newly created Envoy filter. 913 case createSet.Contains(key): 914 cfg := creates[key] 915 // If the filter is newly created, it should not have a previous version. 916 if previousVersion != nil { 917 t.Errorf("Created Envoy filter %s/%s already existed", ns, ef.Name) 918 } 919 // Validate that the generated filter is the same as the one created. 920 if !reflect.DeepEqual(ef, convertToEnvoyFilterWrapper(&cfg)) { 921 t.Errorf("Unexpected envoy filter generated %s/%s", ns, ef.Name) 922 } 923 // Updated Envoy filter. 924 case updateSet.Contains(key): 925 cfg := updates[key] 926 // If the filter is updated, it should have a previous version. 927 if previousVersion == nil { 928 t.Errorf("Updated Envoy filter %s/%s did not exist", ns, ef.Name) 929 } else if reflect.DeepEqual(*previousVersion, ef) { 930 // Validate that the generated filter is different from the previous version. 931 t.Errorf("Envoy filter %s/%s was not updated", ns, ef.Name) 932 } 933 // Validate that the generated filter is the same as the one updated. 934 if !reflect.DeepEqual(ef, convertToEnvoyFilterWrapper(&cfg)) { 935 t.Errorf("Unexpected envoy filter generated %s/%s", ns, ef.Name) 936 } 937 // Deleted Envoy filter. 938 case deletes.Contains(key): 939 t.Errorf("Found deleted EnvoyFilter %s/%s", ns, ef.Name) 940 // Unchanged Envoy filter. 941 default: 942 if previousVersion == nil { 943 t.Errorf("Unchanged EnvoyFilter was not previously found %s/%s", ns, ef.Name) 944 } else { 945 if mode && *previousVersion != ef { 946 // Validate that Unchanged filter is not regenerated when config optimization is enabled. 947 t.Errorf("Unchanged EnvoyFilter is different from original %s/%s", ns, ef.Name) 948 } else if !mode && *previousVersion == ef { 949 // Validate that Unchanged filter is regenerated when config optimization is disabled. 950 t.Errorf("Unchanged EnvoyFilter is not regenerated from original %s/%s", ns, ef.Name) 951 } 952 if !reflect.DeepEqual(*previousVersion, ef) { 953 t.Errorf("Envoy filter %s/%s has unexpected change", ns, ef.Name) 954 } 955 } 956 } 957 } 958 } 959 960 total1 := 0 961 // Validate that empty namespace is deleted when all filters in that namespace are deleted. 962 for ns, envoyFilters := range pc1.envoyFiltersByNamespace { 963 total1 += len(envoyFilters) 964 deleted := 0 965 for _, ef := range envoyFilters { 966 key := ConfigKey{Kind: kind.EnvoyFilter, Namespace: ns, Name: ef.Name} 967 if deletes.Contains(key) { 968 deleted++ 969 } 970 } 971 972 if deleted == len(envoyFilters) { 973 if _, ok := pc2.envoyFiltersByNamespace[ns]; ok { 974 t.Errorf("Empty Namespace %s was not deleted", ns) 975 } 976 } 977 } 978 979 if total2 != total1+len(tt.creates)-len(tt.deletes) { 980 t.Errorf("Expected %d envoy filters, found %d", total1+len(tt.creates)-len(tt.deletes), total2) 981 } 982 }) 983 } 984 } 985 } 986 987 func TestWasmPlugins(t *testing.T) { 988 env := &Environment{} 989 store := NewFakeStore() 990 991 wasmPlugins := map[string]config.Config{ 992 "invalid-type": { 993 Meta: config.Meta{Name: "invalid-type", Namespace: constants.IstioSystemNamespace, GroupVersionKind: gvk.WasmPlugin}, 994 Spec: &networking.DestinationRule{}, 995 }, 996 "invalid-url": { 997 Meta: config.Meta{Name: "invalid-url", Namespace: constants.IstioSystemNamespace, GroupVersionKind: gvk.WasmPlugin}, 998 Spec: &extensions.WasmPlugin{ 999 Phase: extensions.PluginPhase_AUTHN, 1000 Priority: &wrapperspb.Int32Value{Value: 5}, 1001 Url: "notavalid%%Url;", 1002 }, 1003 }, 1004 "authn-low-prio-all": { 1005 Meta: config.Meta{Name: "authn-low-prio-all", Namespace: "testns-1", GroupVersionKind: gvk.WasmPlugin}, 1006 Spec: &extensions.WasmPlugin{ 1007 Phase: extensions.PluginPhase_AUTHN, 1008 Priority: &wrapperspb.Int32Value{Value: 10}, 1009 Url: "file:///etc/istio/filters/authn.wasm", 1010 PluginConfig: &structpb.Struct{ 1011 Fields: map[string]*structpb.Value{ 1012 "test": { 1013 Kind: &structpb.Value_StringValue{StringValue: "test"}, 1014 }, 1015 }, 1016 }, 1017 Sha256: "f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2", 1018 }, 1019 }, 1020 "authn-low-prio-all-network": { 1021 Meta: config.Meta{Name: "authn-low-prio-all-network", Namespace: "testns-1", GroupVersionKind: gvk.WasmPlugin}, 1022 Spec: &extensions.WasmPlugin{ 1023 Type: extensions.PluginType_NETWORK, 1024 Phase: extensions.PluginPhase_AUTHN, 1025 Priority: &wrapperspb.Int32Value{Value: 10}, 1026 Url: "file:///etc/istio/filters/authn.wasm", 1027 PluginConfig: &structpb.Struct{ 1028 Fields: map[string]*structpb.Value{ 1029 "test": { 1030 Kind: &structpb.Value_StringValue{StringValue: "test"}, 1031 }, 1032 }, 1033 }, 1034 Sha256: "f2ca1bb6c7e907d06dafe4687e579fce76b37e4e93b7605022da52e6ccc26fd2", 1035 }, 1036 }, 1037 "global-authn-low-prio-ingress": { 1038 Meta: config.Meta{Name: "global-authn-low-prio-ingress", Namespace: constants.IstioSystemNamespace, GroupVersionKind: gvk.WasmPlugin}, 1039 Spec: &extensions.WasmPlugin{ 1040 Phase: extensions.PluginPhase_AUTHN, 1041 Priority: &wrapperspb.Int32Value{Value: 5}, 1042 Selector: &selectorpb.WorkloadSelector{ 1043 MatchLabels: map[string]string{ 1044 "istio": "ingressgateway", 1045 }, 1046 }, 1047 }, 1048 }, 1049 "authn-med-prio-all": { 1050 Meta: config.Meta{Name: "authn-med-prio-all", Namespace: "testns-1", GroupVersionKind: gvk.WasmPlugin}, 1051 Spec: &extensions.WasmPlugin{ 1052 Phase: extensions.PluginPhase_AUTHN, 1053 Priority: &wrapperspb.Int32Value{Value: 50}, 1054 }, 1055 }, 1056 "global-authn-high-prio-app": { 1057 Meta: config.Meta{Name: "global-authn-high-prio-app", Namespace: constants.IstioSystemNamespace, GroupVersionKind: gvk.WasmPlugin}, 1058 Spec: &extensions.WasmPlugin{ 1059 Phase: extensions.PluginPhase_AUTHN, 1060 Priority: &wrapperspb.Int32Value{Value: 1000}, 1061 Selector: &selectorpb.WorkloadSelector{ 1062 MatchLabels: map[string]string{ 1063 "app": "productpage", 1064 }, 1065 }, 1066 Match: []*extensions.WasmPlugin_TrafficSelector{ 1067 { 1068 Mode: selectorpb.WorkloadMode_SERVER, 1069 Ports: []*selectorpb.PortSelector{{Number: 1234}}, 1070 }, 1071 }, 1072 }, 1073 }, 1074 "global-authz-med-prio-app": { 1075 Meta: config.Meta{Name: "global-authz-med-prio-app", Namespace: constants.IstioSystemNamespace, GroupVersionKind: gvk.WasmPlugin}, 1076 Spec: &extensions.WasmPlugin{ 1077 Phase: extensions.PluginPhase_AUTHZ, 1078 Priority: &wrapperspb.Int32Value{Value: 50}, 1079 Selector: &selectorpb.WorkloadSelector{ 1080 MatchLabels: map[string]string{ 1081 "app": "productpage", 1082 }, 1083 }, 1084 Match: []*extensions.WasmPlugin_TrafficSelector{ 1085 { 1086 Mode: selectorpb.WorkloadMode_SERVER, 1087 Ports: []*selectorpb.PortSelector{{Number: 1235}}, 1088 }, 1089 }, 1090 }, 1091 }, 1092 "authz-high-prio-ingress": { 1093 Meta: config.Meta{Name: "authz-high-prio-ingress", Namespace: "testns-2", GroupVersionKind: gvk.WasmPlugin}, 1094 Spec: &extensions.WasmPlugin{ 1095 Phase: extensions.PluginPhase_AUTHZ, 1096 Priority: &wrapperspb.Int32Value{Value: 1000}, 1097 }, 1098 }, 1099 } 1100 1101 testCases := []struct { 1102 name string 1103 node *Proxy 1104 listenerInfo WasmPluginListenerInfo 1105 pluginType WasmPluginType 1106 expectedExtensions map[extensions.PluginPhase][]*WasmPluginWrapper 1107 }{ 1108 { 1109 name: "nil proxy", 1110 node: nil, 1111 listenerInfo: anyListener, 1112 pluginType: WasmPluginTypeHTTP, 1113 expectedExtensions: nil, 1114 }, 1115 { 1116 name: "nomatch", 1117 node: &Proxy{ 1118 ConfigNamespace: "other", 1119 Metadata: &NodeMetadata{}, 1120 }, 1121 listenerInfo: anyListener, 1122 pluginType: WasmPluginTypeHTTP, 1123 expectedExtensions: map[extensions.PluginPhase][]*WasmPluginWrapper{}, 1124 }, 1125 { 1126 name: "ingress", 1127 node: &Proxy{ 1128 ConfigNamespace: "other", 1129 Labels: map[string]string{ 1130 "istio": "ingressgateway", 1131 }, 1132 Metadata: &NodeMetadata{ 1133 Labels: map[string]string{ 1134 "istio": "ingressgateway", 1135 }, 1136 }, 1137 }, 1138 listenerInfo: anyListener, 1139 pluginType: WasmPluginTypeHTTP, 1140 expectedExtensions: map[extensions.PluginPhase][]*WasmPluginWrapper{ 1141 extensions.PluginPhase_AUTHN: { 1142 convertToWasmPluginWrapper(wasmPlugins["global-authn-low-prio-ingress"]), 1143 }, 1144 }, 1145 }, 1146 { 1147 name: "ingress-testns-1", 1148 node: &Proxy{ 1149 ConfigNamespace: "testns-1", 1150 Labels: map[string]string{ 1151 "istio": "ingressgateway", 1152 }, 1153 Metadata: &NodeMetadata{ 1154 Labels: map[string]string{ 1155 "istio": "ingressgateway", 1156 }, 1157 }, 1158 }, 1159 listenerInfo: anyListener, 1160 pluginType: WasmPluginTypeHTTP, 1161 expectedExtensions: map[extensions.PluginPhase][]*WasmPluginWrapper{ 1162 extensions.PluginPhase_AUTHN: { 1163 convertToWasmPluginWrapper(wasmPlugins["authn-med-prio-all"]), 1164 convertToWasmPluginWrapper(wasmPlugins["authn-low-prio-all"]), 1165 convertToWasmPluginWrapper(wasmPlugins["global-authn-low-prio-ingress"]), 1166 }, 1167 }, 1168 }, 1169 { 1170 name: "ingress-testns-1-network", 1171 node: &Proxy{ 1172 ConfigNamespace: "testns-1", 1173 Labels: map[string]string{ 1174 "istio": "ingressgateway", 1175 }, 1176 Metadata: &NodeMetadata{ 1177 Labels: map[string]string{ 1178 "istio": "ingressgateway", 1179 }, 1180 }, 1181 }, 1182 listenerInfo: anyListener, 1183 pluginType: WasmPluginTypeNetwork, 1184 expectedExtensions: map[extensions.PluginPhase][]*WasmPluginWrapper{ 1185 extensions.PluginPhase_AUTHN: { 1186 convertToWasmPluginWrapper(wasmPlugins["authn-low-prio-all-network"]), 1187 }, 1188 }, 1189 }, 1190 { 1191 name: "ingress-testns-1-any", 1192 node: &Proxy{ 1193 ConfigNamespace: "testns-1", 1194 Labels: map[string]string{ 1195 "istio": "ingressgateway", 1196 }, 1197 Metadata: &NodeMetadata{ 1198 Labels: map[string]string{ 1199 "istio": "ingressgateway", 1200 }, 1201 }, 1202 }, 1203 listenerInfo: anyListener, 1204 pluginType: WasmPluginTypeAny, 1205 expectedExtensions: map[extensions.PluginPhase][]*WasmPluginWrapper{ 1206 extensions.PluginPhase_AUTHN: { 1207 convertToWasmPluginWrapper(wasmPlugins["authn-med-prio-all"]), 1208 convertToWasmPluginWrapper(wasmPlugins["authn-low-prio-all-network"]), 1209 convertToWasmPluginWrapper(wasmPlugins["authn-low-prio-all"]), 1210 convertToWasmPluginWrapper(wasmPlugins["global-authn-low-prio-ingress"]), 1211 }, 1212 }, 1213 }, 1214 { 1215 name: "testns-2", 1216 node: &Proxy{ 1217 ConfigNamespace: "testns-2", 1218 Labels: map[string]string{ 1219 "app": "productpage", 1220 }, 1221 Metadata: &NodeMetadata{ 1222 Labels: map[string]string{ 1223 "app": "productpage", 1224 }, 1225 }, 1226 }, 1227 listenerInfo: anyListener, 1228 pluginType: WasmPluginTypeHTTP, 1229 expectedExtensions: map[extensions.PluginPhase][]*WasmPluginWrapper{ 1230 extensions.PluginPhase_AUTHN: { 1231 convertToWasmPluginWrapper(wasmPlugins["global-authn-high-prio-app"]), 1232 }, 1233 extensions.PluginPhase_AUTHZ: { 1234 convertToWasmPluginWrapper(wasmPlugins["authz-high-prio-ingress"]), 1235 convertToWasmPluginWrapper(wasmPlugins["global-authz-med-prio-app"]), 1236 }, 1237 }, 1238 }, 1239 { 1240 // Detailed tests regarding TrafficSelector are in extension_test.go 1241 // Just test the integrity here. 1242 // This testcase is identical with "testns-2", but `listenerInfo`` is specified. 1243 // 1. `global-authn-high-prio-app` matched, because it has a port matching clause with "1234" 1244 // 2. `authz-high-prio-ingress` matched, because it does not have any `match` clause 1245 // 3. `global-authz-med-prio-app` not matched, because it has a port matching clause with "1235" 1246 name: "testns-2-with-port-match", 1247 node: &Proxy{ 1248 ConfigNamespace: "testns-2", 1249 Labels: map[string]string{ 1250 "app": "productpage", 1251 }, 1252 Metadata: &NodeMetadata{ 1253 Labels: map[string]string{ 1254 "app": "productpage", 1255 }, 1256 }, 1257 }, 1258 listenerInfo: WasmPluginListenerInfo{ 1259 Port: 1234, 1260 Class: istionetworking.ListenerClassSidecarInbound, 1261 }, 1262 pluginType: WasmPluginTypeHTTP, 1263 expectedExtensions: map[extensions.PluginPhase][]*WasmPluginWrapper{ 1264 extensions.PluginPhase_AUTHN: { 1265 convertToWasmPluginWrapper(wasmPlugins["global-authn-high-prio-app"]), 1266 }, 1267 extensions.PluginPhase_AUTHZ: { 1268 convertToWasmPluginWrapper(wasmPlugins["authz-high-prio-ingress"]), 1269 }, 1270 }, 1271 }, 1272 } 1273 1274 for _, config := range wasmPlugins { 1275 store.Create(config) 1276 } 1277 env.ConfigStore = store 1278 m := mesh.DefaultMeshConfig() 1279 env.Watcher = mesh.NewFixedWatcher(m) 1280 env.Init() 1281 1282 // Init a new push context 1283 pc := NewPushContext() 1284 pc.Mesh = m 1285 pc.initWasmPlugins(env) 1286 1287 for _, tc := range testCases { 1288 t.Run(tc.name, func(t *testing.T) { 1289 result := pc.WasmPluginsByListenerInfo(tc.node, tc.listenerInfo, tc.pluginType) 1290 if !reflect.DeepEqual(tc.expectedExtensions, result) { 1291 t.Errorf("WasmPlugins did not match expectations\n\ngot: %v\n\nexpected: %v", result, tc.expectedExtensions) 1292 } 1293 }) 1294 } 1295 } 1296 1297 func TestServiceIndex(t *testing.T) { 1298 g := NewWithT(t) 1299 env := NewEnvironment() 1300 env.ConfigStore = NewFakeStore() 1301 env.ServiceDiscovery = &localServiceDiscovery{ 1302 services: []*Service{ 1303 { 1304 Hostname: "svc-unset", 1305 Ports: allPorts, 1306 Attributes: ServiceAttributes{ 1307 Namespace: "test1", 1308 }, 1309 }, 1310 { 1311 Hostname: "svc-public", 1312 Ports: allPorts, 1313 Attributes: ServiceAttributes{ 1314 Namespace: "test1", 1315 ExportTo: sets.New(visibility.Public), 1316 }, 1317 }, 1318 { 1319 Hostname: "svc-private", 1320 Ports: allPorts, 1321 Attributes: ServiceAttributes{ 1322 Namespace: "test1", 1323 ExportTo: sets.New(visibility.Private), 1324 }, 1325 }, 1326 { 1327 Hostname: "svc-none", 1328 Ports: allPorts, 1329 Attributes: ServiceAttributes{ 1330 Namespace: "test1", 1331 ExportTo: sets.New(visibility.None), 1332 }, 1333 }, 1334 { 1335 Hostname: "svc-namespace", 1336 Ports: allPorts, 1337 Attributes: ServiceAttributes{ 1338 Namespace: "test1", 1339 ExportTo: sets.New(visibility.Instance("namespace")), 1340 }, 1341 }, 1342 }, 1343 serviceInstances: []*ServiceInstance{{ 1344 Endpoint: &IstioEndpoint{ 1345 Address: "192.168.1.2", 1346 EndpointPort: 8000, 1347 TLSMode: DisabledTLSModeLabel, 1348 }, 1349 }}, 1350 } 1351 m := mesh.DefaultMeshConfig() 1352 env.Watcher = mesh.NewFixedWatcher(m) 1353 env.Init() 1354 1355 // Init a new push context 1356 pc := NewPushContext() 1357 if err := pc.InitContext(env, nil, nil); err != nil { 1358 t.Fatal(err) 1359 } 1360 si := pc.ServiceIndex 1361 1362 // Should have all 5 services 1363 g.Expect(si.instancesByPort).To(HaveLen(5)) 1364 g.Expect(si.HostnameAndNamespace).To(HaveLen(5)) 1365 1366 // Should just have "namespace" 1367 g.Expect(si.exportedToNamespace).To(HaveLen(1)) 1368 g.Expect(serviceNames(si.exportedToNamespace["namespace"])).To(Equal([]string{"svc-namespace"})) 1369 1370 g.Expect(serviceNames(si.public)).To(Equal([]string{"svc-public", "svc-unset"})) 1371 1372 // Should just have "test1" 1373 g.Expect(si.privateByNamespace).To(HaveLen(1)) 1374 g.Expect(serviceNames(si.privateByNamespace["test1"])).To(Equal([]string{"svc-private"})) 1375 } 1376 1377 func TestIsServiceVisible(t *testing.T) { 1378 targetNamespace := "foo" 1379 cases := []struct { 1380 name string 1381 pushContext *PushContext 1382 service *Service 1383 expect bool 1384 }{ 1385 { 1386 name: "service whose namespace is foo has no exportTo map with global private", 1387 pushContext: &PushContext{ 1388 exportToDefaults: exportToDefaults{ 1389 service: sets.New(visibility.Private), 1390 }, 1391 }, 1392 service: &Service{ 1393 Attributes: ServiceAttributes{ 1394 Namespace: "foo", 1395 }, 1396 }, 1397 expect: true, 1398 }, 1399 { 1400 name: "service whose namespace is bar has no exportTo map with global private", 1401 pushContext: &PushContext{ 1402 exportToDefaults: exportToDefaults{ 1403 service: sets.New(visibility.Private), 1404 }, 1405 }, 1406 service: &Service{ 1407 Attributes: ServiceAttributes{ 1408 Namespace: "bar", 1409 }, 1410 }, 1411 expect: false, 1412 }, 1413 { 1414 name: "service whose namespace is bar has no exportTo map with global public", 1415 pushContext: &PushContext{ 1416 exportToDefaults: exportToDefaults{ 1417 service: sets.New(visibility.Public), 1418 }, 1419 }, 1420 service: &Service{ 1421 Attributes: ServiceAttributes{ 1422 Namespace: "bar", 1423 }, 1424 }, 1425 expect: true, 1426 }, 1427 { 1428 name: "service whose namespace is foo has exportTo map with private", 1429 pushContext: &PushContext{}, 1430 service: &Service{ 1431 Attributes: ServiceAttributes{ 1432 Namespace: "foo", 1433 ExportTo: sets.New(visibility.Private), 1434 }, 1435 }, 1436 expect: true, 1437 }, 1438 { 1439 name: "service whose namespace is bar has exportTo map with private", 1440 pushContext: &PushContext{}, 1441 service: &Service{ 1442 Attributes: ServiceAttributes{ 1443 Namespace: "bar", 1444 ExportTo: sets.New(visibility.Private), 1445 }, 1446 }, 1447 expect: false, 1448 }, 1449 { 1450 name: "service whose namespace is bar has exportTo map with public", 1451 pushContext: &PushContext{}, 1452 service: &Service{ 1453 Attributes: ServiceAttributes{ 1454 Namespace: "bar", 1455 ExportTo: sets.New(visibility.Public), 1456 }, 1457 }, 1458 expect: true, 1459 }, 1460 { 1461 name: "service whose namespace is bar has exportTo map with specific namespace foo", 1462 pushContext: &PushContext{}, 1463 service: &Service{ 1464 Attributes: ServiceAttributes{ 1465 Namespace: "bar", 1466 ExportTo: sets.New(visibility.Instance("foo")), 1467 }, 1468 }, 1469 expect: true, 1470 }, 1471 { 1472 name: "service whose namespace is bar has exportTo map with specific namespace baz", 1473 pushContext: &PushContext{}, 1474 service: &Service{ 1475 Attributes: ServiceAttributes{ 1476 Namespace: "bar", 1477 ExportTo: sets.New(visibility.Instance("baz")), 1478 }, 1479 }, 1480 expect: false, 1481 }, 1482 { 1483 name: "service visible to none", 1484 pushContext: &PushContext{}, 1485 service: &Service{ 1486 Attributes: ServiceAttributes{ 1487 Namespace: "bar", 1488 ExportTo: sets.New(visibility.None), 1489 }, 1490 }, 1491 expect: false, 1492 }, 1493 { 1494 name: "service has both public visibility and none visibility", 1495 pushContext: &PushContext{}, 1496 service: &Service{ 1497 Attributes: ServiceAttributes{ 1498 Namespace: "bar", 1499 ExportTo: sets.New( 1500 visibility.Public, 1501 visibility.None, 1502 ), 1503 }, 1504 }, 1505 expect: true, 1506 }, 1507 { 1508 name: "service has both none visibility and private visibility", 1509 pushContext: &PushContext{}, 1510 service: &Service{ 1511 Attributes: ServiceAttributes{ 1512 Namespace: "bar", 1513 ExportTo: sets.New( 1514 visibility.Private, 1515 visibility.None, 1516 ), 1517 }, 1518 }, 1519 expect: false, 1520 }, 1521 } 1522 1523 for _, c := range cases { 1524 t.Run(c.name, func(t *testing.T) { 1525 isVisible := c.pushContext.IsServiceVisible(c.service, targetNamespace) 1526 1527 g := NewWithT(t) 1528 g.Expect(isVisible).To(Equal(c.expect)) 1529 }) 1530 } 1531 } 1532 1533 func serviceNames(svcs []*Service) []string { 1534 var s []string 1535 for _, ss := range svcs { 1536 s = append(s, string(ss.Hostname)) 1537 } 1538 sort.Strings(s) 1539 return s 1540 } 1541 1542 func TestInitPushContext(t *testing.T) { 1543 env := NewEnvironment() 1544 configStore := NewFakeStore() 1545 _, _ = configStore.Create(config.Config{ 1546 Meta: config.Meta{ 1547 Name: "rule1", 1548 Namespace: "test1", 1549 GroupVersionKind: gvk.VirtualService, 1550 }, 1551 Spec: &networking.VirtualService{ 1552 Hosts: []string{"rule1.com"}, 1553 ExportTo: []string{".", "ns1"}, 1554 }, 1555 }) 1556 _, _ = configStore.Create(config.Config{ 1557 Meta: config.Meta{ 1558 Name: "rule1", 1559 Namespace: "test1", 1560 GroupVersionKind: gvk.DestinationRule, 1561 }, 1562 Spec: &networking.DestinationRule{ 1563 ExportTo: []string{".", "ns1"}, 1564 }, 1565 }) 1566 _, _ = configStore.Create(config.Config{ 1567 Meta: config.Meta{ 1568 Name: "default", 1569 Namespace: "istio-system", 1570 GroupVersionKind: gvk.Sidecar, 1571 }, 1572 Spec: &networking.Sidecar{ 1573 Egress: []*networking.IstioEgressListener{ 1574 {Hosts: []string{"test1/*"}}, 1575 }, 1576 }, 1577 }) 1578 1579 env.ConfigStore = configStore 1580 env.ServiceDiscovery = &localServiceDiscovery{ 1581 services: []*Service{ 1582 { 1583 Hostname: "svc1", 1584 Ports: allPorts, 1585 Attributes: ServiceAttributes{ 1586 Namespace: "test1", 1587 }, 1588 }, 1589 { 1590 Hostname: "svc2", 1591 Ports: allPorts, 1592 Attributes: ServiceAttributes{ 1593 Namespace: "test1", 1594 ExportTo: sets.New(visibility.Public), 1595 }, 1596 }, 1597 }, 1598 serviceInstances: []*ServiceInstance{{ 1599 Endpoint: &IstioEndpoint{ 1600 Address: "192.168.1.2", 1601 EndpointPort: 8000, 1602 TLSMode: DisabledTLSModeLabel, 1603 }, 1604 }}, 1605 } 1606 m := mesh.DefaultMeshConfig() 1607 env.Watcher = mesh.NewFixedWatcher(m) 1608 env.Init() 1609 1610 // Init a new push context 1611 old := NewPushContext() 1612 if err := old.InitContext(env, nil, nil); err != nil { 1613 t.Fatal(err) 1614 } 1615 1616 // Create a new one, copying from the old one 1617 // Pass a ConfigsUpdated otherwise we would just copy it directly 1618 newPush := NewPushContext() 1619 if err := newPush.InitContext(env, old, &PushRequest{ 1620 ConfigsUpdated: sets.Set[ConfigKey]{ 1621 {Kind: kind.Secret}: {}, 1622 }, 1623 }); err != nil { 1624 t.Fatal(err) 1625 } 1626 1627 // Check to ensure the update is identical to the old one 1628 // There is probably a better way to do this. 1629 diff := cmp.Diff(old, newPush, 1630 // Allow looking into exported fields for parts of push context 1631 cmp.AllowUnexported(PushContext{}, exportToDefaults{}, serviceIndex{}, virtualServiceIndex{}, 1632 destinationRuleIndex{}, gatewayIndex{}, consolidatedDestRules{}, IstioEgressListenerWrapper{}, SidecarScope{}, 1633 AuthenticationPolicies{}, NetworkManager{}, sidecarIndex{}, Telemetries{}, ProxyConfigs{}, ConsolidatedDestRule{}, 1634 ClusterLocalHosts{}), 1635 // These are not feasible/worth comparing 1636 cmpopts.IgnoreTypes(sync.RWMutex{}, localServiceDiscovery{}, FakeStore{}, atomic.Bool{}, sync.Mutex{}), 1637 cmpopts.IgnoreUnexported(IstioEndpoint{}), 1638 cmpopts.IgnoreInterfaces(struct{ mesh.Holder }{}), 1639 protocmp.Transform(), 1640 ) 1641 if diff != "" { 1642 t.Fatalf("Push context had a diff after update: %v", diff) 1643 } 1644 } 1645 1646 func TestSidecarScope(t *testing.T) { 1647 test.SetForTest(t, &features.ConvertSidecarScopeConcurrency, 10) 1648 ps := NewPushContext() 1649 env := &Environment{Watcher: mesh.NewFixedWatcher(&meshconfig.MeshConfig{RootNamespace: "istio-system"})} 1650 ps.Mesh = env.Mesh() 1651 ps.ServiceIndex.HostnameAndNamespace["svc1.default.cluster.local"] = map[string]*Service{"default": nil} 1652 ps.ServiceIndex.HostnameAndNamespace["svc2.nosidecar.cluster.local"] = map[string]*Service{"nosidecar": nil} 1653 ps.ServiceIndex.HostnameAndNamespace["svc3.istio-system.cluster.local"] = map[string]*Service{"istio-system": nil} 1654 1655 configStore := NewFakeStore() 1656 sidecarWithWorkloadSelector := &networking.Sidecar{ 1657 WorkloadSelector: &networking.WorkloadSelector{ 1658 Labels: map[string]string{"app": "foo"}, 1659 }, 1660 Egress: []*networking.IstioEgressListener{ 1661 { 1662 Hosts: []string{"default/*"}, 1663 }, 1664 }, 1665 OutboundTrafficPolicy: &networking.OutboundTrafficPolicy{}, 1666 } 1667 sidecarWithoutWorkloadSelector := &networking.Sidecar{ 1668 Egress: []*networking.IstioEgressListener{ 1669 { 1670 Hosts: []string{"default/*"}, 1671 }, 1672 }, 1673 OutboundTrafficPolicy: &networking.OutboundTrafficPolicy{}, 1674 } 1675 configWithWorkloadSelector := config.Config{ 1676 Meta: config.Meta{ 1677 GroupVersionKind: gvk.Sidecar, 1678 Name: "foo", 1679 Namespace: "default", 1680 }, 1681 Spec: sidecarWithWorkloadSelector, 1682 } 1683 rootConfig := config.Config{ 1684 Meta: config.Meta{ 1685 GroupVersionKind: gvk.Sidecar, 1686 Name: "global", 1687 Namespace: constants.IstioSystemNamespace, 1688 }, 1689 Spec: sidecarWithoutWorkloadSelector, 1690 } 1691 _, _ = configStore.Create(configWithWorkloadSelector) 1692 _, _ = configStore.Create(rootConfig) 1693 1694 env.ConfigStore = configStore 1695 ps.initSidecarScopes(env) 1696 cases := []struct { 1697 proxy *Proxy 1698 labels labels.Instance 1699 sidecar string 1700 describe string 1701 }{ 1702 { 1703 proxy: &Proxy{Type: SidecarProxy, ConfigNamespace: "default"}, 1704 labels: labels.Instance{"app": "foo"}, 1705 sidecar: "default/foo", 1706 describe: "match local sidecar", 1707 }, 1708 { 1709 proxy: &Proxy{Type: SidecarProxy, ConfigNamespace: "default"}, 1710 labels: labels.Instance{"app": "bar"}, 1711 sidecar: "default/global", 1712 describe: "no match local sidecar", 1713 }, 1714 { 1715 proxy: &Proxy{Type: SidecarProxy, ConfigNamespace: "nosidecar"}, 1716 labels: labels.Instance{"app": "bar"}, 1717 sidecar: "nosidecar/global", 1718 describe: "no sidecar", 1719 }, 1720 { 1721 proxy: &Proxy{Type: Router, ConfigNamespace: "istio-system"}, 1722 labels: labels.Instance{"app": "istio-gateway"}, 1723 sidecar: "istio-system/default-sidecar", 1724 describe: "gateway sidecar scope", 1725 }, 1726 } 1727 for _, c := range cases { 1728 t.Run(c.describe, func(t *testing.T) { 1729 scope := ps.getSidecarScope(c.proxy, c.labels) 1730 if c.sidecar != scopeToSidecar(scope) { 1731 t.Errorf("should get sidecar %s but got %s", c.sidecar, scopeToSidecar(scope)) 1732 } 1733 }) 1734 } 1735 } 1736 1737 func TestRootSidecarScopePropagation(t *testing.T) { 1738 rootNS := "istio-system" 1739 defaultNS := "default" 1740 otherNS := "foo" 1741 1742 verifyServices := func(sidecarScopeEnabled bool, desc string, ns string, push *PushContext) { 1743 t.Run(desc, func(t *testing.T) { 1744 scScope := push.getSidecarScope(&Proxy{Type: SidecarProxy, ConfigNamespace: ns}, nil) 1745 services := scScope.Services() 1746 numSvc := 0 1747 svcList := []string{} 1748 for _, service := range services { 1749 svcName := service.Attributes.Name 1750 svcNS := service.Attributes.Namespace 1751 if svcNS != ns && svcNS != rootNS { 1752 numSvc++ 1753 } 1754 svcList = append(svcList, fmt.Sprintf("%v.%v.cluster.local", svcName, svcNS)) 1755 } 1756 if sidecarScopeEnabled && numSvc > 0 { 1757 t.Fatalf("For namespace:%v, should not see services from other ns. Instead got these services: %v", ns, svcList) 1758 } 1759 }) 1760 } 1761 1762 env := NewEnvironment() 1763 configStore := NewFakeStore() 1764 1765 m := mesh.DefaultMeshConfig() 1766 env.Watcher = mesh.NewFixedWatcher(m) 1767 1768 env.ServiceDiscovery = &localServiceDiscovery{ 1769 services: []*Service{ 1770 { 1771 Hostname: "svc1", 1772 Ports: allPorts, 1773 Attributes: ServiceAttributes{ 1774 Name: "svc1", 1775 Namespace: defaultNS, 1776 }, 1777 }, 1778 { 1779 Hostname: "svc2", 1780 Ports: allPorts, 1781 Attributes: ServiceAttributes{ 1782 Name: "svc2", 1783 Namespace: otherNS, 1784 }, 1785 }, 1786 { 1787 Hostname: "svc3", 1788 Ports: allPorts, 1789 Attributes: ServiceAttributes{ 1790 Name: "svc3", 1791 Namespace: otherNS, 1792 }, 1793 }, 1794 { 1795 Hostname: "svc4", 1796 Ports: allPorts, 1797 Attributes: ServiceAttributes{ 1798 Name: "svc4", 1799 Namespace: rootNS, 1800 }, 1801 }, 1802 }, 1803 } 1804 env.Init() 1805 globalSidecar := &networking.Sidecar{ 1806 Egress: []*networking.IstioEgressListener{ 1807 { 1808 Hosts: []string{"./*", fmt.Sprintf("%v/*", rootNS)}, 1809 }, 1810 }, 1811 OutboundTrafficPolicy: &networking.OutboundTrafficPolicy{}, 1812 } 1813 rootConfig := config.Config{ 1814 Meta: config.Meta{ 1815 GroupVersionKind: gvk.Sidecar, 1816 Name: "global", 1817 Namespace: constants.IstioSystemNamespace, 1818 }, 1819 Spec: globalSidecar, 1820 } 1821 1822 _, _ = configStore.Create(rootConfig) 1823 env.ConfigStore = configStore 1824 1825 testDesc := "Testing root SidecarScope for ns:%v enabled when %v is called." 1826 when := "createNewContext" 1827 newPush := NewPushContext() 1828 newPush.Mesh = env.Mesh() 1829 newPush.InitContext(env, nil, nil) 1830 verifyServices(true, fmt.Sprintf(testDesc, otherNS, when), otherNS, newPush) 1831 verifyServices(true, fmt.Sprintf(testDesc, defaultNS, when), defaultNS, newPush) 1832 1833 oldPush := newPush 1834 newPush = NewPushContext() 1835 newPush.Mesh = env.Mesh() 1836 svcName := "svc6.foo.cluster.local" 1837 if err := newPush.InitContext(env, oldPush, &PushRequest{ 1838 ConfigsUpdated: sets.Set[ConfigKey]{ 1839 {Kind: kind.Service, Name: svcName, Namespace: "foo"}: {}, 1840 }, 1841 Reason: nil, 1842 Full: true, 1843 }); err != nil { 1844 t.Fatal(err) 1845 } 1846 when = "updateContext(with no changes)" 1847 verifyServices(true, fmt.Sprintf(testDesc, otherNS, when), otherNS, newPush) 1848 verifyServices(true, fmt.Sprintf(testDesc, defaultNS, when), defaultNS, newPush) 1849 } 1850 1851 func TestBestEffortInferServiceMTLSMode(t *testing.T) { 1852 const partialNS string = "partial" 1853 const wholeNS string = "whole" 1854 ps := NewPushContext() 1855 env := &Environment{Watcher: mesh.NewFixedWatcher(&meshconfig.MeshConfig{RootNamespace: "istio-system"})} 1856 sd := &localServiceDiscovery{} 1857 env.ServiceDiscovery = sd 1858 ps.Mesh = env.Mesh() 1859 1860 configStore := NewFakeStore() 1861 1862 // Add beta policies 1863 _, _ = configStore.Create(*createTestPeerAuthenticationResource("default", wholeNS, time.Now(), nil, securityBeta.PeerAuthentication_MutualTLS_STRICT)) 1864 // workload level beta policy. 1865 _, _ = configStore.Create(*createTestPeerAuthenticationResource("workload-beta-policy", partialNS, time.Now(), &selectorpb.WorkloadSelector{ 1866 MatchLabels: map[string]string{ 1867 "app": "httpbin", 1868 "version": "v1", 1869 }, 1870 }, securityBeta.PeerAuthentication_MutualTLS_DISABLE)) 1871 1872 env.ConfigStore = configStore 1873 ps.initAuthnPolicies(env) 1874 1875 instancePlainText := &ServiceInstance{ 1876 Endpoint: &IstioEndpoint{ 1877 Address: "192.168.1.2", 1878 EndpointPort: 1000000, 1879 TLSMode: DisabledTLSModeLabel, 1880 }, 1881 } 1882 1883 cases := []struct { 1884 name string 1885 serviceNamespace string 1886 servicePort int 1887 serviceResolution Resolution 1888 serviceInstances []*ServiceInstance 1889 wanted MutualTLSMode 1890 }{ 1891 { 1892 name: "from namespace policy", 1893 serviceNamespace: wholeNS, 1894 servicePort: 80, 1895 serviceResolution: ClientSideLB, 1896 wanted: MTLSStrict, 1897 }, 1898 { 1899 name: "from mesh default", 1900 serviceNamespace: partialNS, 1901 servicePort: 80, 1902 serviceResolution: ClientSideLB, 1903 wanted: MTLSPermissive, 1904 }, 1905 { 1906 name: "headless service with no instances found yet", 1907 serviceNamespace: partialNS, 1908 servicePort: 80, 1909 serviceResolution: Passthrough, 1910 wanted: MTLSDisable, 1911 }, 1912 { 1913 name: "headless service with instances", 1914 serviceNamespace: partialNS, 1915 servicePort: 80, 1916 serviceResolution: Passthrough, 1917 serviceInstances: []*ServiceInstance{instancePlainText}, 1918 wanted: MTLSDisable, 1919 }, 1920 } 1921 1922 serviceName := host.Name("some-service") 1923 for _, tc := range cases { 1924 t.Run(tc.name, func(t *testing.T) { 1925 service := &Service{ 1926 Hostname: host.Name(fmt.Sprintf("%s.%s.svc.cluster.local", serviceName, tc.serviceNamespace)), 1927 Resolution: tc.serviceResolution, 1928 Attributes: ServiceAttributes{Namespace: tc.serviceNamespace}, 1929 } 1930 // Intentionally use the externalService with the same name and namespace for test, though 1931 // these attributes don't matter. 1932 externalService := &Service{ 1933 Hostname: host.Name(fmt.Sprintf("%s.%s.svc.cluster.local", serviceName, tc.serviceNamespace)), 1934 Resolution: tc.serviceResolution, 1935 Attributes: ServiceAttributes{Namespace: tc.serviceNamespace}, 1936 MeshExternal: true, 1937 } 1938 1939 sd.serviceInstances = tc.serviceInstances 1940 port := &Port{ 1941 Port: tc.servicePort, 1942 } 1943 if got := ps.BestEffortInferServiceMTLSMode(nil, service, port); got != tc.wanted { 1944 t.Fatalf("want %s, but got %s", tc.wanted, got) 1945 } 1946 if got := ps.BestEffortInferServiceMTLSMode(nil, externalService, port); got != MTLSUnknown { 1947 t.Fatalf("MTLS mode for external service should always be %s, but got %s", MTLSUnknown, got) 1948 } 1949 }) 1950 } 1951 } 1952 1953 func scopeToSidecar(scope *SidecarScope) string { 1954 if scope == nil { 1955 return "" 1956 } 1957 return scope.Namespace + "/" + scope.Name 1958 } 1959 1960 func TestSetDestinationRuleWithWorkloadSelector(t *testing.T) { 1961 now := time.Now() 1962 ps := NewPushContext() 1963 ps.Mesh = &meshconfig.MeshConfig{RootNamespace: "istio-system"} 1964 testhost := "httpbin.org" 1965 app1DestinationRule := config.Config{ 1966 Meta: config.Meta{ 1967 Name: "nsRule1", 1968 Namespace: "test", 1969 CreationTimestamp: now, 1970 }, 1971 Spec: &networking.DestinationRule{ 1972 Host: testhost, 1973 ExportTo: []string{"test2", "."}, 1974 WorkloadSelector: &selectorpb.WorkloadSelector{ 1975 MatchLabels: map[string]string{"app": "app1"}, 1976 }, 1977 TrafficPolicy: &networking.TrafficPolicy{ 1978 ConnectionPool: &networking.ConnectionPoolSettings{ 1979 Tcp: &networking.ConnectionPoolSettings_TCPSettings{ 1980 ConnectTimeout: &durationpb.Duration{Seconds: 1}, 1981 MaxConnections: 111, 1982 }, 1983 }, 1984 Tls: &networking.ClientTLSSettings{ 1985 Mode: networking.ClientTLSSettings_MUTUAL, 1986 ClientCertificate: "/etc/certs/myclientcert.pem", 1987 PrivateKey: "/etc/certs/client_private_key.pem", 1988 CaCertificates: "/etc/certs/rootcacerts.pem", 1989 }, 1990 }, 1991 }, 1992 } 1993 app2DestinationRule := config.Config{ 1994 Meta: config.Meta{ 1995 Name: "nsRule2", 1996 Namespace: "test", 1997 CreationTimestamp: now.Add(time.Second), 1998 }, 1999 Spec: &networking.DestinationRule{ 2000 Host: testhost, 2001 ExportTo: []string{"test2", "."}, 2002 WorkloadSelector: &selectorpb.WorkloadSelector{ 2003 MatchLabels: map[string]string{"app": "app2"}, 2004 }, 2005 TrafficPolicy: &networking.TrafficPolicy{ 2006 ConnectionPool: &networking.ConnectionPoolSettings{ 2007 Http: &networking.ConnectionPoolSettings_HTTPSettings{ 2008 MaxRetries: 33, 2009 }, 2010 Tcp: &networking.ConnectionPoolSettings_TCPSettings{ 2011 ConnectTimeout: &durationpb.Duration{Seconds: 33}, 2012 }, 2013 }, 2014 OutlierDetection: &networking.OutlierDetection{ 2015 Consecutive_5XxErrors: &wrapperspb.UInt32Value{Value: 3}, 2016 }, 2017 Tls: &networking.ClientTLSSettings{ 2018 Mode: networking.ClientTLSSettings_SIMPLE, 2019 }, 2020 }, 2021 }, 2022 } 2023 app3DestinationRule := config.Config{ 2024 Meta: config.Meta{ 2025 Name: "nsRule3", 2026 Namespace: "test", 2027 CreationTimestamp: now.Add(time.Second), 2028 }, 2029 Spec: &networking.DestinationRule{ 2030 Host: testhost, 2031 ExportTo: []string{"test2", "."}, 2032 WorkloadSelector: &selectorpb.WorkloadSelector{ 2033 MatchLabels: map[string]string{"app": "app2"}, 2034 }, 2035 TrafficPolicy: &networking.TrafficPolicy{ 2036 ConnectionPool: &networking.ConnectionPoolSettings{ 2037 Http: &networking.ConnectionPoolSettings_HTTPSettings{ 2038 MaxRetries: 33, 2039 }, 2040 Tcp: &networking.ConnectionPoolSettings_TCPSettings{ 2041 ConnectTimeout: &durationpb.Duration{Seconds: 33}, 2042 }, 2043 }, 2044 OutlierDetection: &networking.OutlierDetection{ 2045 Consecutive_5XxErrors: &wrapperspb.UInt32Value{Value: 3}, 2046 }, 2047 Tls: &networking.ClientTLSSettings{ 2048 Mode: networking.ClientTLSSettings_SIMPLE, 2049 }, 2050 }, 2051 }, 2052 } 2053 namespaceDestinationRule := config.Config{ 2054 Meta: config.Meta{ 2055 Name: "nsRule4", 2056 Namespace: "test", 2057 CreationTimestamp: now.Add(-time.Second), 2058 }, 2059 Spec: &networking.DestinationRule{ 2060 Host: testhost, 2061 ExportTo: []string{".", "test2"}, 2062 TrafficPolicy: &networking.TrafficPolicy{ 2063 ConnectionPool: &networking.ConnectionPoolSettings{ 2064 Http: &networking.ConnectionPoolSettings_HTTPSettings{ 2065 MaxRetries: 33, 2066 }, 2067 Tcp: &networking.ConnectionPoolSettings_TCPSettings{ 2068 ConnectTimeout: &durationpb.Duration{Seconds: 33}, 2069 }, 2070 }, 2071 OutlierDetection: &networking.OutlierDetection{ 2072 Consecutive_5XxErrors: &wrapperspb.UInt32Value{Value: 3}, 2073 }, 2074 Tls: &networking.ClientTLSSettings{ 2075 Mode: networking.ClientTLSSettings_SIMPLE, 2076 }, 2077 }, 2078 }, 2079 } 2080 testCases := []struct { 2081 name string 2082 proxyNs string 2083 serviceNs string 2084 serviceHostname string 2085 expectedDrCount int 2086 expectedDrName []string 2087 expectedNamespacedFrom map[string][]types.NamespacedName 2088 }{ 2089 { 2090 name: "return list of DRs for specific host", 2091 proxyNs: "test", 2092 serviceNs: "test", 2093 serviceHostname: testhost, 2094 expectedDrCount: 3, 2095 expectedDrName: []string{app1DestinationRule.Meta.Name, app2DestinationRule.Meta.Name, namespaceDestinationRule.Meta.Name}, 2096 expectedNamespacedFrom: map[string][]types.NamespacedName{ 2097 app1DestinationRule.Meta.Name: {app1DestinationRule.NamespacedName(), namespaceDestinationRule.NamespacedName()}, 2098 app2DestinationRule.Meta.Name: {app2DestinationRule.NamespacedName(), app3DestinationRule.NamespacedName(), namespaceDestinationRule.NamespacedName()}, 2099 namespaceDestinationRule.Meta.Name: {namespaceDestinationRule.NamespacedName()}, 2100 }, 2101 }, 2102 { 2103 name: "workload specific DR should not be exported", 2104 proxyNs: "test2", 2105 serviceNs: "test", 2106 serviceHostname: testhost, 2107 expectedDrCount: 1, 2108 expectedDrName: []string{namespaceDestinationRule.Meta.Name}, 2109 expectedNamespacedFrom: map[string][]types.NamespacedName{}, 2110 }, 2111 { 2112 name: "rules with same workloadselector should be merged", 2113 proxyNs: "test", 2114 serviceNs: "test", 2115 serviceHostname: testhost, 2116 expectedDrCount: 3, 2117 expectedDrName: []string{app1DestinationRule.Meta.Name, app2DestinationRule.Meta.Name, namespaceDestinationRule.Meta.Name}, 2118 expectedNamespacedFrom: map[string][]types.NamespacedName{ 2119 app1DestinationRule.Meta.Name: {app1DestinationRule.NamespacedName(), namespaceDestinationRule.NamespacedName()}, 2120 app2DestinationRule.Meta.Name: {app2DestinationRule.NamespacedName(), app3DestinationRule.NamespacedName(), namespaceDestinationRule.NamespacedName()}, 2121 namespaceDestinationRule.Meta.Name: {namespaceDestinationRule.NamespacedName()}, 2122 }, 2123 }, 2124 } 2125 2126 ps.setDestinationRules([]config.Config{app1DestinationRule, app2DestinationRule, app3DestinationRule, namespaceDestinationRule}) 2127 2128 for _, tt := range testCases { 2129 t.Run(tt.name, func(t *testing.T) { 2130 drList := ps.destinationRule(tt.proxyNs, 2131 &Service{ 2132 Hostname: host.Name(tt.serviceHostname), 2133 Attributes: ServiceAttributes{ 2134 Namespace: tt.serviceNs, 2135 }, 2136 }) 2137 if len(drList) != tt.expectedDrCount { 2138 t.Errorf("expected %d destinationRules for host %v got %v", tt.expectedDrCount, tt.serviceHostname, drList) 2139 } 2140 for i, dr := range drList { 2141 if dr.rule.Name != tt.expectedDrName[i] { 2142 t.Errorf("destinationRuleName expected %v got %v", tt.expectedDrName[i], dr.rule.Name) 2143 } 2144 } 2145 testLocal := ps.destinationRuleIndex.namespaceLocal[tt.proxyNs] 2146 if testLocal != nil { 2147 destRules := testLocal.specificDestRules 2148 for _, dr := range destRules[host.Name(testhost)] { 2149 2150 // Check if the 'from' values match the expectedFrom map 2151 expectedFrom := tt.expectedNamespacedFrom[dr.rule.Meta.Name] 2152 if !reflect.DeepEqual(dr.from, expectedFrom) { 2153 t.Errorf("Unexpected 'from' value for destination rule %s. Got: %v, Expected: %v", dr.rule.NamespacedName(), dr.from, expectedFrom) 2154 } 2155 } 2156 } 2157 }) 2158 } 2159 } 2160 2161 func TestSetDestinationRuleMerging(t *testing.T) { 2162 ps := NewPushContext() 2163 ps.exportToDefaults.destinationRule = sets.New(visibility.Public) 2164 testhost := "httpbin.org" 2165 destinationRuleNamespace1 := config.Config{ 2166 Meta: config.Meta{ 2167 Name: "rule1", 2168 Namespace: "test", 2169 }, 2170 Spec: &networking.DestinationRule{ 2171 Host: testhost, 2172 Subsets: []*networking.Subset{ 2173 { 2174 Name: "subset1", 2175 }, 2176 { 2177 Name: "subset2", 2178 }, 2179 }, 2180 }, 2181 } 2182 destinationRuleNamespace2 := config.Config{ 2183 Meta: config.Meta{ 2184 Name: "rule2", 2185 Namespace: "test", 2186 }, 2187 Spec: &networking.DestinationRule{ 2188 Host: testhost, 2189 Subsets: []*networking.Subset{ 2190 { 2191 Name: "subset3", 2192 }, 2193 { 2194 Name: "subset4", 2195 }, 2196 }, 2197 }, 2198 } 2199 expectedDestRules := []types.NamespacedName{ 2200 {Namespace: "test", Name: "rule1"}, 2201 {Namespace: "test", Name: "rule2"}, 2202 } 2203 ps.setDestinationRules([]config.Config{destinationRuleNamespace1, destinationRuleNamespace2}) 2204 private := ps.destinationRuleIndex.namespaceLocal["test"].specificDestRules[host.Name(testhost)] 2205 public := ps.destinationRuleIndex.exportedByNamespace["test"].specificDestRules[host.Name(testhost)] 2206 subsetsLocal := private[0].rule.Spec.(*networking.DestinationRule).Subsets 2207 subsetsExport := public[0].rule.Spec.(*networking.DestinationRule).Subsets 2208 assert.Equal(t, private[0].from, expectedDestRules) 2209 assert.Equal(t, public[0].from, expectedDestRules) 2210 if len(subsetsLocal) != 4 { 2211 t.Errorf("want %d, but got %d", 4, len(subsetsLocal)) 2212 } 2213 if len(subsetsExport) != 4 { 2214 t.Errorf("want %d, but got %d", 4, len(subsetsExport)) 2215 } 2216 } 2217 2218 func TestSetDestinationRuleWithExportTo(t *testing.T) { 2219 ps := NewPushContext() 2220 ps.Mesh = &meshconfig.MeshConfig{RootNamespace: "istio-system"} 2221 testhost := "httpbin.org" 2222 appHost := "foo.app.org" 2223 wildcardHost1 := "*.org" 2224 wildcardHost2 := "*.app.org" 2225 destinationRuleNamespace1 := config.Config{ 2226 Meta: config.Meta{ 2227 Name: "rule1", 2228 Namespace: "test1", 2229 }, 2230 Spec: &networking.DestinationRule{ 2231 Host: testhost, 2232 ExportTo: []string{".", "ns1"}, 2233 Subsets: []*networking.Subset{ 2234 { 2235 Name: "subset1", 2236 }, 2237 { 2238 Name: "subset2", 2239 }, 2240 }, 2241 }, 2242 } 2243 destinationRuleNamespace2 := config.Config{ 2244 Meta: config.Meta{ 2245 Name: "rule2", 2246 Namespace: "test2", 2247 }, 2248 Spec: &networking.DestinationRule{ 2249 Host: testhost, 2250 ExportTo: []string{"test2", "ns1", "test1", "newNS"}, 2251 Subsets: []*networking.Subset{ 2252 { 2253 Name: "subset3", 2254 }, 2255 { 2256 Name: "subset4", 2257 }, 2258 }, 2259 }, 2260 } 2261 destinationRuleNamespace3 := config.Config{ 2262 Meta: config.Meta{ 2263 Name: "rule3", 2264 Namespace: "test3", 2265 }, 2266 Spec: &networking.DestinationRule{ 2267 Host: testhost, 2268 ExportTo: []string{"test1", "test2", "*"}, 2269 Subsets: []*networking.Subset{ 2270 { 2271 Name: "subset5", 2272 }, 2273 { 2274 Name: "subset6", 2275 }, 2276 }, 2277 }, 2278 } 2279 destinationRuleNamespace4Local := config.Config{ 2280 Meta: config.Meta{ 2281 Name: "rule4-local", 2282 Namespace: "test4", 2283 }, 2284 Spec: &networking.DestinationRule{ 2285 Host: testhost, 2286 ExportTo: []string{"test4"}, 2287 Subsets: []*networking.Subset{ 2288 { 2289 Name: "subset15", 2290 }, 2291 { 2292 Name: "subset16", 2293 }, 2294 }, 2295 }, 2296 } 2297 destinationRuleRootNamespace := config.Config{ 2298 Meta: config.Meta{ 2299 Name: "rule4", 2300 Namespace: "istio-system", 2301 }, 2302 Spec: &networking.DestinationRule{ 2303 Host: testhost, 2304 Subsets: []*networking.Subset{ 2305 { 2306 Name: "subset7", 2307 }, 2308 { 2309 Name: "subset8", 2310 }, 2311 }, 2312 }, 2313 } 2314 destinationRuleRootNamespaceLocal := config.Config{ 2315 Meta: config.Meta{ 2316 Name: "rule1", 2317 Namespace: "istio-system", 2318 }, 2319 Spec: &networking.DestinationRule{ 2320 Host: testhost, 2321 ExportTo: []string{"."}, 2322 Subsets: []*networking.Subset{ 2323 { 2324 Name: "subset9", 2325 }, 2326 { 2327 Name: "subset10", 2328 }, 2329 }, 2330 }, 2331 } 2332 destinationRuleRootNamespaceLocalWithWildcardHost1 := config.Config{ 2333 Meta: config.Meta{ 2334 Name: "rule2", 2335 Namespace: "istio-system", 2336 }, 2337 Spec: &networking.DestinationRule{ 2338 Host: wildcardHost1, 2339 ExportTo: []string{"."}, 2340 Subsets: []*networking.Subset{ 2341 { 2342 Name: "subset11", 2343 }, 2344 { 2345 Name: "subset12", 2346 }, 2347 }, 2348 }, 2349 } 2350 destinationRuleRootNamespaceLocalWithWildcardHost2 := config.Config{ 2351 Meta: config.Meta{ 2352 Name: "rule3", 2353 Namespace: "istio-system", 2354 }, 2355 Spec: &networking.DestinationRule{ 2356 Host: wildcardHost2, 2357 ExportTo: []string{"."}, 2358 Subsets: []*networking.Subset{ 2359 { 2360 Name: "subset13", 2361 }, 2362 { 2363 Name: "subset14", 2364 }, 2365 }, 2366 }, 2367 } 2368 2369 destinationRule5ExportToRootNamespace := config.Config{ 2370 Meta: config.Meta{ 2371 Name: "rule5", 2372 Namespace: "test5", 2373 }, 2374 Spec: &networking.DestinationRule{ 2375 Host: "api.test.com", 2376 ExportTo: []string{"istio-system"}, 2377 Subsets: []*networking.Subset{ 2378 { 2379 Name: "subset5-0", 2380 }, 2381 { 2382 Name: "subset5-1", 2383 }, 2384 }, 2385 }, 2386 } 2387 2388 destinationRule6ExportToRootNamespace := config.Config{ 2389 Meta: config.Meta{ 2390 Name: "rule6", 2391 Namespace: "test6", 2392 }, 2393 Spec: &networking.DestinationRule{ 2394 Host: "api.test.com", 2395 ExportTo: []string{"istio-system"}, 2396 Subsets: []*networking.Subset{ 2397 { 2398 Name: "subset6-0", 2399 }, 2400 { 2401 Name: "subset6-1", 2402 }, 2403 }, 2404 }, 2405 } 2406 ps.setDestinationRules([]config.Config{ 2407 destinationRuleNamespace1, destinationRuleNamespace2, 2408 destinationRuleNamespace3, destinationRuleNamespace4Local, 2409 destinationRuleRootNamespace, destinationRuleRootNamespaceLocal, 2410 destinationRuleRootNamespaceLocalWithWildcardHost1, destinationRuleRootNamespaceLocalWithWildcardHost2, 2411 destinationRule5ExportToRootNamespace, destinationRule6ExportToRootNamespace, 2412 }) 2413 cases := []struct { 2414 proxyNs string 2415 serviceNs string 2416 host string 2417 wantSubsets []string 2418 }{ 2419 { 2420 proxyNs: "test1", 2421 serviceNs: "test1", 2422 host: testhost, 2423 wantSubsets: []string{"subset1", "subset2"}, 2424 }, 2425 { 2426 proxyNs: "test1", 2427 serviceNs: "test2", 2428 host: testhost, 2429 wantSubsets: []string{"subset1", "subset2"}, 2430 }, 2431 { 2432 proxyNs: "test2", 2433 serviceNs: "test1", 2434 host: testhost, 2435 wantSubsets: []string{"subset3", "subset4"}, 2436 }, 2437 { 2438 proxyNs: "newNS", 2439 serviceNs: "test2", 2440 host: testhost, 2441 wantSubsets: []string{"subset3", "subset4"}, 2442 }, 2443 { 2444 proxyNs: "test3", 2445 serviceNs: "test1", 2446 host: testhost, 2447 wantSubsets: []string{"subset5", "subset6"}, 2448 }, 2449 { 2450 proxyNs: "test5", 2451 serviceNs: "test4", 2452 host: testhost, 2453 wantSubsets: []string{"subset7", "subset8"}, 2454 }, 2455 { 2456 proxyNs: "ns1", 2457 serviceNs: "test1", 2458 host: testhost, 2459 wantSubsets: []string{"subset1", "subset2"}, 2460 }, 2461 { 2462 proxyNs: "ns1", 2463 serviceNs: "random", 2464 host: testhost, 2465 wantSubsets: []string{"subset7", "subset8"}, 2466 }, 2467 { 2468 proxyNs: "random", 2469 serviceNs: "random", 2470 host: testhost, 2471 wantSubsets: []string{"subset7", "subset8"}, 2472 }, 2473 { 2474 proxyNs: "test3", 2475 serviceNs: "random", 2476 host: testhost, 2477 wantSubsets: []string{"subset5", "subset6"}, 2478 }, 2479 { 2480 proxyNs: "istio-system", 2481 serviceNs: "random", 2482 host: testhost, 2483 wantSubsets: []string{"subset9", "subset10"}, 2484 }, 2485 { 2486 proxyNs: "istio-system", 2487 serviceNs: "istio-system", 2488 host: testhost, 2489 wantSubsets: []string{"subset9", "subset10"}, 2490 }, 2491 { 2492 proxyNs: "istio-system", 2493 serviceNs: "istio-system", 2494 host: appHost, 2495 wantSubsets: []string{"subset13", "subset14"}, 2496 }, 2497 // dr in the svc ns takes effect on proxy in root ns 2498 { 2499 proxyNs: "istio-system", 2500 serviceNs: "test5", 2501 host: "api.test.com", 2502 wantSubsets: []string{"subset5-0", "subset5-1"}, 2503 }, 2504 // dr in the svc ns takes effect on proxy in root ns 2505 { 2506 proxyNs: "istio-system", 2507 serviceNs: "test6", 2508 host: "api.test.com", 2509 wantSubsets: []string{"subset6-0", "subset6-1"}, 2510 }, 2511 // both svc and dr namespace is not equal to proxy ns, the dr will not take effect on the proxy 2512 { 2513 proxyNs: "istio-system", 2514 serviceNs: "test7", 2515 host: "api.test.com", 2516 wantSubsets: nil, 2517 }, 2518 } 2519 for _, tt := range cases { 2520 t.Run(fmt.Sprintf("%s-%s", tt.proxyNs, tt.serviceNs), func(t *testing.T) { 2521 out := ps.destinationRule(tt.proxyNs, 2522 &Service{ 2523 Hostname: host.Name(tt.host), 2524 Attributes: ServiceAttributes{ 2525 Namespace: tt.serviceNs, 2526 }, 2527 }) 2528 if tt.wantSubsets == nil { 2529 if len(out) != 0 { 2530 t.Fatalf("proxy in %s namespace: unexpected dr found %+v", tt.proxyNs, out) 2531 } 2532 return 2533 } 2534 2535 if len(out) == 0 { 2536 t.Fatalf("proxy in %s namespace: dest rule is nil, expected subsets %+v", tt.proxyNs, tt.wantSubsets) 2537 } 2538 destRuleConfig := out[0] 2539 destRule := destRuleConfig.rule.Spec.(*networking.DestinationRule) 2540 var gotSubsets []string 2541 for _, ss := range destRule.Subsets { 2542 gotSubsets = append(gotSubsets, ss.Name) 2543 } 2544 if !reflect.DeepEqual(gotSubsets, tt.wantSubsets) { 2545 t.Fatalf("want %+v, got %+v", tt.wantSubsets, gotSubsets) 2546 } 2547 }) 2548 } 2549 } 2550 2551 func TestVirtualServiceWithExportTo(t *testing.T) { 2552 ps := NewPushContext() 2553 env := &Environment{Watcher: mesh.NewFixedWatcher(&meshconfig.MeshConfig{RootNamespace: "zzz"})} 2554 ps.Mesh = env.Mesh() 2555 configStore := NewFakeStore() 2556 gatewayName := "default/gateway" 2557 2558 rule1 := config.Config{ 2559 Meta: config.Meta{ 2560 Name: "rule1", 2561 Namespace: "test1", 2562 GroupVersionKind: gvk.VirtualService, 2563 }, 2564 Spec: &networking.VirtualService{ 2565 Hosts: []string{"rule1.com"}, 2566 ExportTo: []string{".", "ns1"}, 2567 }, 2568 } 2569 rule2 := config.Config{ 2570 Meta: config.Meta{ 2571 Name: "rule2", 2572 Namespace: "test2", 2573 GroupVersionKind: gvk.VirtualService, 2574 }, 2575 Spec: &networking.VirtualService{ 2576 Hosts: []string{"rule2.com"}, 2577 ExportTo: []string{"test2", "ns1", "test1"}, 2578 }, 2579 } 2580 rule2Gw := config.Config{ 2581 Meta: config.Meta{ 2582 Name: "rule2Gw", 2583 Namespace: "test2", 2584 GroupVersionKind: gvk.VirtualService, 2585 }, 2586 Spec: &networking.VirtualService{ 2587 Gateways: []string{gatewayName, constants.IstioMeshGateway}, 2588 Hosts: []string{"rule2gw.com"}, 2589 ExportTo: []string{"test2", "ns1", "test1"}, 2590 }, 2591 } 2592 rule3 := config.Config{ 2593 Meta: config.Meta{ 2594 Name: "rule3", 2595 Namespace: "test3", 2596 GroupVersionKind: gvk.VirtualService, 2597 }, 2598 Spec: &networking.VirtualService{ 2599 Gateways: []string{constants.IstioMeshGateway}, 2600 Hosts: []string{"rule3.com"}, 2601 ExportTo: []string{"test1", "test2", "*"}, 2602 }, 2603 } 2604 rule3Gw := config.Config{ 2605 Meta: config.Meta{ 2606 Name: "rule3Gw", 2607 Namespace: "test3", 2608 GroupVersionKind: gvk.VirtualService, 2609 }, 2610 Spec: &networking.VirtualService{ 2611 Gateways: []string{gatewayName}, 2612 Hosts: []string{"rule3gw.com"}, 2613 ExportTo: []string{"test1", "test2", "*"}, 2614 }, 2615 } 2616 rootNS := config.Config{ 2617 Meta: config.Meta{ 2618 Name: "zzz", 2619 Namespace: "zzz", 2620 GroupVersionKind: gvk.VirtualService, 2621 }, 2622 Spec: &networking.VirtualService{ 2623 Hosts: []string{"rootNS.com"}, 2624 }, 2625 } 2626 2627 for _, c := range []config.Config{rule1, rule2, rule3, rule2Gw, rule3Gw, rootNS} { 2628 if _, err := configStore.Create(c); err != nil { 2629 t.Fatalf("could not create %v", c.Name) 2630 } 2631 } 2632 2633 env.ConfigStore = configStore 2634 ps.initDefaultExportMaps() 2635 ps.initVirtualServices(env) 2636 2637 cases := []struct { 2638 proxyNs string 2639 gateway string 2640 wantHosts []string 2641 }{ 2642 { 2643 proxyNs: "test1", 2644 wantHosts: []string{"rule1.com", "rule2.com", "rule2gw.com", "rule3.com", "rootNS.com"}, 2645 gateway: constants.IstioMeshGateway, 2646 }, 2647 { 2648 proxyNs: "test2", 2649 wantHosts: []string{"rule2.com", "rule2gw.com", "rule3.com", "rootNS.com"}, 2650 gateway: constants.IstioMeshGateway, 2651 }, 2652 { 2653 proxyNs: "ns1", 2654 wantHosts: []string{"rule1.com", "rule2.com", "rule2gw.com", "rule3.com", "rootNS.com"}, 2655 gateway: constants.IstioMeshGateway, 2656 }, 2657 { 2658 proxyNs: "random", 2659 wantHosts: []string{"rule3.com", "rootNS.com"}, 2660 gateway: constants.IstioMeshGateway, 2661 }, 2662 { 2663 proxyNs: "test1", 2664 wantHosts: []string{"rule2gw.com", "rule3gw.com"}, 2665 gateway: gatewayName, 2666 }, 2667 { 2668 proxyNs: "test2", 2669 wantHosts: []string{"rule2gw.com", "rule3gw.com"}, 2670 gateway: gatewayName, 2671 }, 2672 { 2673 proxyNs: "ns1", 2674 wantHosts: []string{"rule2gw.com", "rule3gw.com"}, 2675 gateway: gatewayName, 2676 }, 2677 { 2678 proxyNs: "random", 2679 wantHosts: []string{"rule3gw.com"}, 2680 gateway: gatewayName, 2681 }, 2682 } 2683 for _, tt := range cases { 2684 t.Run(fmt.Sprintf("%s-%s", tt.proxyNs, tt.gateway), func(t *testing.T) { 2685 rules := ps.VirtualServicesForGateway(tt.proxyNs, tt.gateway) 2686 gotHosts := make([]string, 0) 2687 for _, r := range rules { 2688 vs := r.Spec.(*networking.VirtualService) 2689 gotHosts = append(gotHosts, vs.Hosts...) 2690 } 2691 if !reflect.DeepEqual(gotHosts, tt.wantHosts) { 2692 t.Errorf("want %+v, got %+v", tt.wantHosts, gotHosts) 2693 } 2694 }) 2695 } 2696 } 2697 2698 func TestInitVirtualService(t *testing.T) { 2699 test.SetForTest(t, &features.FilterGatewayClusterConfig, true) 2700 ps := NewPushContext() 2701 env := &Environment{Watcher: mesh.NewFixedWatcher(&meshconfig.MeshConfig{RootNamespace: "istio-system"})} 2702 ps.Mesh = env.Mesh() 2703 configStore := NewFakeStore() 2704 gatewayName := "ns1/gateway" 2705 2706 root := config.Config{ 2707 Meta: config.Meta{ 2708 GroupVersionKind: gvk.VirtualService, 2709 Name: "root", 2710 Namespace: "ns1", 2711 }, 2712 Spec: &networking.VirtualService{ 2713 ExportTo: []string{"*"}, 2714 Hosts: []string{"*.org"}, 2715 Gateways: []string{"gateway"}, 2716 Http: []*networking.HTTPRoute{ 2717 { 2718 Match: []*networking.HTTPMatchRequest{ 2719 { 2720 Uri: &networking.StringMatch{ 2721 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"}, 2722 }, 2723 }, 2724 { 2725 Uri: &networking.StringMatch{ 2726 MatchType: &networking.StringMatch_Exact{Exact: "/login"}, 2727 }, 2728 }, 2729 }, 2730 Delegate: &networking.Delegate{ 2731 Name: "delegate", 2732 Namespace: "ns2", 2733 }, 2734 }, 2735 }, 2736 }, 2737 } 2738 delegate := config.Config{ 2739 Meta: config.Meta{ 2740 GroupVersionKind: gvk.VirtualService, 2741 Name: "delegate", 2742 Namespace: "ns2", 2743 }, 2744 Spec: &networking.VirtualService{ 2745 ExportTo: []string{"*"}, 2746 Hosts: []string{}, 2747 Gateways: []string{gatewayName}, 2748 Http: []*networking.HTTPRoute{ 2749 { 2750 Route: []*networking.HTTPRouteDestination{ 2751 { 2752 Destination: &networking.Destination{ 2753 Host: "delegate", 2754 Port: &networking.PortSelector{ 2755 Number: 80, 2756 }, 2757 }, 2758 }, 2759 }, 2760 }, 2761 }, 2762 }, 2763 } 2764 public := config.Config{ 2765 Meta: config.Meta{ 2766 GroupVersionKind: gvk.VirtualService, 2767 Name: "public", 2768 Namespace: "ns3", 2769 }, 2770 Spec: &networking.VirtualService{ 2771 Hosts: []string{"*.org"}, 2772 Gateways: []string{gatewayName}, 2773 Http: []*networking.HTTPRoute{ 2774 { 2775 Route: []*networking.HTTPRouteDestination{ 2776 { 2777 Destination: &networking.Destination{ 2778 Host: "public", 2779 Port: &networking.PortSelector{ 2780 Number: 80, 2781 }, 2782 }, 2783 }, 2784 }, 2785 }, 2786 }, 2787 }, 2788 } 2789 private := config.Config{ 2790 Meta: config.Meta{ 2791 GroupVersionKind: gvk.VirtualService, 2792 Name: "private", 2793 Namespace: "ns1", 2794 }, 2795 Spec: &networking.VirtualService{ 2796 ExportTo: []string{".", "ns2"}, 2797 Hosts: []string{"*.org"}, 2798 Gateways: []string{gatewayName}, 2799 Http: []*networking.HTTPRoute{ 2800 { 2801 Route: []*networking.HTTPRouteDestination{ 2802 { 2803 Destination: &networking.Destination{ 2804 Host: "private", 2805 Port: &networking.PortSelector{ 2806 Number: 80, 2807 }, 2808 }, 2809 }, 2810 }, 2811 }, 2812 }, 2813 }, 2814 } 2815 invisible := config.Config{ 2816 Meta: config.Meta{ 2817 GroupVersionKind: gvk.VirtualService, 2818 Name: "invisible", 2819 Namespace: "ns5", 2820 }, 2821 Spec: &networking.VirtualService{ 2822 ExportTo: []string{".", "ns3"}, 2823 Hosts: []string{"*.org"}, 2824 Gateways: []string{"gateway", "mesh"}, 2825 Http: []*networking.HTTPRoute{ 2826 { 2827 Route: []*networking.HTTPRouteDestination{ 2828 { 2829 Destination: &networking.Destination{ 2830 Host: "invisible", 2831 Port: &networking.PortSelector{ 2832 Number: 80, 2833 }, 2834 }, 2835 }, 2836 }, 2837 }, 2838 }, 2839 }, 2840 } 2841 2842 for _, c := range []config.Config{root, delegate, public, private, invisible} { 2843 if _, err := configStore.Create(c); err != nil { 2844 t.Fatalf("could not create %v", c.Name) 2845 } 2846 } 2847 2848 env.ConfigStore = configStore 2849 ps.initDefaultExportMaps() 2850 ps.initVirtualServices(env) 2851 2852 t.Run("resolve shortname", func(t *testing.T) { 2853 rules := ps.VirtualServicesForGateway("ns1", gatewayName) 2854 if len(rules) != 3 { 2855 t.Fatalf("wanted 3 virtualservice for gateway %s, actually got %d", gatewayName, len(rules)) 2856 } 2857 gotHTTPHosts := make([]string, 0) 2858 for _, r := range rules { 2859 vs := r.Spec.(*networking.VirtualService) 2860 for _, route := range vs.GetHttp() { 2861 for _, dst := range route.Route { 2862 gotHTTPHosts = append(gotHTTPHosts, dst.Destination.Host) 2863 } 2864 } 2865 } 2866 if !reflect.DeepEqual(gotHTTPHosts, []string{"private.ns1", "public.ns3", "delegate.ns2"}) { 2867 t.Errorf("got %+v", gotHTTPHosts) 2868 } 2869 }) 2870 2871 t.Run("destinations by gateway", func(t *testing.T) { 2872 got := ps.virtualServiceIndex.destinationsByGateway 2873 want := map[string]sets.String{ 2874 gatewayName: sets.New("delegate.ns2", "public.ns3", "private.ns1"), 2875 "ns5/gateway": sets.New("invisible.ns5"), 2876 } 2877 if !reflect.DeepEqual(got, want) { 2878 t.Errorf("destinationsByGateway: got %+v", got) 2879 } 2880 }) 2881 } 2882 2883 func TestServiceWithExportTo(t *testing.T) { 2884 ps := NewPushContext() 2885 env := NewEnvironment() 2886 env.Watcher = mesh.NewFixedWatcher(&meshconfig.MeshConfig{RootNamespace: "zzz"}) 2887 ps.Mesh = env.Mesh() 2888 2889 svc1 := &Service{ 2890 Hostname: "svc1", 2891 Attributes: ServiceAttributes{ 2892 Namespace: "test1", 2893 ExportTo: sets.New(visibility.Private, visibility.Instance("ns1")), 2894 }, 2895 } 2896 svc2 := &Service{ 2897 Hostname: "svc2", 2898 Attributes: ServiceAttributes{ 2899 Namespace: "test2", 2900 ExportTo: sets.New( 2901 visibility.Instance("test1"), 2902 visibility.Instance("ns1"), 2903 visibility.Instance("test2"), 2904 ), 2905 }, 2906 } 2907 svc3 := &Service{ 2908 Hostname: "svc3", 2909 Attributes: ServiceAttributes{ 2910 Namespace: "test3", 2911 ExportTo: sets.New( 2912 visibility.Instance("test1"), 2913 visibility.Public, 2914 visibility.Instance("test2"), 2915 ), 2916 }, 2917 } 2918 svc4 := &Service{ 2919 Hostname: "svc4", 2920 Attributes: ServiceAttributes{ 2921 Namespace: "test4", 2922 ServiceRegistry: provider.External, 2923 }, 2924 } 2925 svc4_1 := &Service{ 2926 Hostname: "svc4", 2927 Attributes: ServiceAttributes{ 2928 Namespace: "test4", 2929 ServiceRegistry: provider.External, 2930 }, 2931 } 2932 // kubernetes service will override non kubernetes 2933 svc4_2 := &Service{ 2934 Hostname: "svc4", 2935 Attributes: ServiceAttributes{ 2936 Namespace: "test4", 2937 ServiceRegistry: provider.Kubernetes, 2938 }, 2939 } 2940 env.ServiceDiscovery = &localServiceDiscovery{ 2941 services: []*Service{svc1, svc2, svc3, svc4, svc4_1, svc4_2}, 2942 } 2943 ps.initDefaultExportMaps() 2944 ps.initServiceRegistry(env, nil) 2945 assert.Equal(t, ps.ServiceIndex.HostnameAndNamespace[svc4.Hostname][svc4.Attributes.Namespace].Attributes.ServiceRegistry, provider.Kubernetes) 2946 cases := []struct { 2947 proxyNs string 2948 wantHosts []string 2949 }{ 2950 { 2951 proxyNs: "test1", 2952 wantHosts: []string{"svc1", "svc2", "svc3", "svc4", "svc4", "svc4"}, 2953 }, 2954 { 2955 proxyNs: "test2", 2956 wantHosts: []string{"svc2", "svc3", "svc4", "svc4", "svc4"}, 2957 }, 2958 { 2959 proxyNs: "ns1", 2960 wantHosts: []string{"svc1", "svc2", "svc3", "svc4", "svc4", "svc4"}, 2961 }, 2962 { 2963 proxyNs: "random", 2964 wantHosts: []string{"svc3", "svc4", "svc4", "svc4"}, 2965 }, 2966 } 2967 for _, tt := range cases { 2968 services := ps.servicesExportedToNamespace(tt.proxyNs) 2969 gotHosts := make([]string, 0) 2970 for _, r := range services { 2971 gotHosts = append(gotHosts, string(r.Hostname)) 2972 } 2973 if !reflect.DeepEqual(gotHosts, tt.wantHosts) { 2974 t.Errorf("proxy in %s namespace: want %+v, got %+v", tt.proxyNs, tt.wantHosts, gotHosts) 2975 } 2976 } 2977 } 2978 2979 func TestInstancesByPort(t *testing.T) { 2980 ps := NewPushContext() 2981 env := NewEnvironment() 2982 env.Watcher = mesh.NewFixedWatcher(&meshconfig.MeshConfig{RootNamespace: "zzz"}) 2983 ps.Mesh = env.Mesh() 2984 2985 // Test the Service Entry merge with same host with different generates 2986 // correct instances by port. 2987 svc5_1 := &Service{ 2988 Hostname: "svc5", 2989 Attributes: ServiceAttributes{ 2990 Namespace: "test5", 2991 ServiceRegistry: provider.External, 2992 ExportTo: sets.New( 2993 visibility.Instance("test5"), 2994 ), 2995 }, 2996 Ports: port7000, 2997 Resolution: DNSLB, 2998 } 2999 svc5_2 := &Service{ 3000 Hostname: "svc5", 3001 Attributes: ServiceAttributes{ 3002 Namespace: "test5", 3003 ServiceRegistry: provider.External, 3004 ExportTo: sets.New( 3005 visibility.Instance("test5"), 3006 ), 3007 }, 3008 Ports: port8000, 3009 Resolution: DNSLB, 3010 } 3011 3012 env.ServiceDiscovery = &localServiceDiscovery{ 3013 services: []*Service{svc5_1, svc5_2}, 3014 } 3015 3016 env.EndpointIndex.shardsBySvc = map[string]map[string]*EndpointShards{ 3017 svc5_1.Hostname.String(): { 3018 svc5_1.Attributes.Namespace: { 3019 Shards: map[ShardKey][]*IstioEndpoint{ 3020 {Cluster: "Kubernets", Provider: provider.External}: { 3021 &IstioEndpoint{ 3022 Address: "1.1.1.1", 3023 EndpointPort: 7000, 3024 ServicePortName: "uds", 3025 }, 3026 &IstioEndpoint{ 3027 Address: "1.1.1.2", 3028 EndpointPort: 8000, 3029 ServicePortName: "uds", 3030 }, 3031 }, 3032 }, 3033 }, 3034 }, 3035 } 3036 3037 ps.initServiceRegistry(env, nil) 3038 instancesByPort := ps.ServiceIndex.instancesByPort[svc5_1.Key()] 3039 assert.Equal(t, len(instancesByPort), 2) 3040 } 3041 3042 func TestGetHostsFromMeshConfig(t *testing.T) { 3043 ps := NewPushContext() 3044 env := &Environment{Watcher: mesh.NewFixedWatcher(&meshconfig.MeshConfig{ 3045 RootNamespace: "istio-system", 3046 ExtensionProviders: []*meshconfig.MeshConfig_ExtensionProvider{ 3047 { 3048 Name: "otel", 3049 Provider: &meshconfig.MeshConfig_ExtensionProvider_EnvoyOtelAls{ 3050 EnvoyOtelAls: &meshconfig.MeshConfig_ExtensionProvider_EnvoyOpenTelemetryLogProvider{ 3051 Service: "otel.foo.svc.cluster.local", 3052 Port: 9811, 3053 }, 3054 }, 3055 }, 3056 }, 3057 DefaultProviders: &meshconfig.MeshConfig_DefaultProviders{ 3058 AccessLogging: []string{"otel"}, 3059 }, 3060 })} 3061 ps.Mesh = env.Mesh() 3062 configStore := NewFakeStore() 3063 gatewayName := "ns1/gateway" 3064 3065 vs1 := config.Config{ 3066 Meta: config.Meta{ 3067 GroupVersionKind: gvk.VirtualService, 3068 Name: "vs1", 3069 Namespace: "ns1", 3070 }, 3071 Spec: &networking.VirtualService{ 3072 Hosts: []string{"*.org"}, 3073 Gateways: []string{"gateway"}, 3074 Http: []*networking.HTTPRoute{ 3075 { 3076 Match: []*networking.HTTPMatchRequest{ 3077 { 3078 Uri: &networking.StringMatch{ 3079 MatchType: &networking.StringMatch_Prefix{Prefix: "/productpage"}, 3080 }, 3081 }, 3082 { 3083 Uri: &networking.StringMatch{ 3084 MatchType: &networking.StringMatch_Exact{Exact: "/login"}, 3085 }, 3086 }, 3087 }, 3088 Delegate: &networking.Delegate{ 3089 Name: "vs2", 3090 Namespace: "ns2", 3091 }, 3092 }, 3093 }, 3094 }, 3095 } 3096 vs2 := config.Config{ 3097 Meta: config.Meta{ 3098 GroupVersionKind: gvk.VirtualService, 3099 Name: "vs2", 3100 Namespace: "ns2", 3101 }, 3102 Spec: &networking.VirtualService{ 3103 Hosts: []string{}, 3104 Gateways: []string{gatewayName}, 3105 Http: []*networking.HTTPRoute{ 3106 { 3107 Route: []*networking.HTTPRouteDestination{}, 3108 }, 3109 }, 3110 }, 3111 } 3112 3113 for _, c := range []config.Config{vs1, vs2} { 3114 if _, err := configStore.Create(c); err != nil { 3115 t.Fatalf("could not create %v", c.Name) 3116 } 3117 } 3118 3119 env.ConfigStore = configStore 3120 test.SetForTest(t, &features.FilterGatewayClusterConfig, true) 3121 ps.initTelemetry(env) 3122 ps.initDefaultExportMaps() 3123 ps.initVirtualServices(env) 3124 assert.Equal(t, ps.virtualServiceIndex.destinationsByGateway[gatewayName], sets.String{}) 3125 assert.Equal(t, ps.extraServicesForProxy(nil), sets.New("otel.foo.svc.cluster.local")) 3126 } 3127 3128 func TestWellKnownProvidersCount(t *testing.T) { 3129 msg := &meshconfig.MeshConfig_ExtensionProvider{} 3130 pb := msg.ProtoReflect() 3131 md := pb.Descriptor() 3132 3133 found := sets.New[string]() 3134 for i := 0; i < md.Oneofs().Get(0).Fields().Len(); i++ { 3135 found.Insert(string(md.Oneofs().Get(0).Fields().Get(i).Name())) 3136 } 3137 // If this fails, there is a provider added that we have not handled. 3138 // DO NOT JUST 3139 assert.Equal(t, found, wellknownProviders) 3140 } 3141 3142 // TestGetHostsFromMeshConfigExhaustiveness exhaustiveness check of `getHostsFromMeshConfig` 3143 // Once some one add a new `Provider` in api, we should update `wellknownProviders` and 3144 // implements of `getHostsFromMeshConfig` 3145 func TestGetHostsFromMeshConfigExhaustiveness(t *testing.T) { 3146 AssertProvidersHandled(addHostsFromMeshConfigProvidersHandled) 3147 unexpectedProviders := make([]string, 0) 3148 msg := &meshconfig.MeshConfig_ExtensionProvider{} 3149 pb := msg.ProtoReflect() 3150 md := pb.Descriptor() 3151 3152 // We only consider ones with `service` field 3153 wellknownProviders := wellknownProviders.Copy().DeleteAll("prometheus", "stackdriver", "envoy_file_access_log") 3154 of := md.Oneofs().Get(0) 3155 for i := 0; i < of.Fields().Len(); i++ { 3156 o := of.Fields().Get(i) 3157 if o.Message().Fields().ByName("service") != nil { 3158 n := string(o.Name()) 3159 if _, ok := wellknownProviders[n]; ok { 3160 delete(wellknownProviders, n) 3161 } else { 3162 unexpectedProviders = append(unexpectedProviders, n) 3163 } 3164 } 3165 } 3166 3167 if len(wellknownProviders) != 0 || len(unexpectedProviders) != 0 { 3168 t.Errorf("unexpected provider not implemented in getHostsFromMeshConfig: %v, %v", wellknownProviders, unexpectedProviders) 3169 t.Fail() 3170 } 3171 } 3172 3173 var _ ServiceDiscovery = &localServiceDiscovery{} 3174 3175 // localServiceDiscovery is an in-memory ServiceDiscovery with mock services 3176 type localServiceDiscovery struct { 3177 services []*Service 3178 serviceInstances []*ServiceInstance 3179 3180 NoopAmbientIndexes 3181 NetworkGatewaysHandler 3182 } 3183 3184 var _ ServiceDiscovery = &localServiceDiscovery{} 3185 3186 func (l *localServiceDiscovery) Services() []*Service { 3187 return l.services 3188 } 3189 3190 func (l *localServiceDiscovery) GetService(host.Name) *Service { 3191 panic("implement me") 3192 } 3193 3194 func (l *localServiceDiscovery) GetProxyServiceTargets(*Proxy) []ServiceTarget { 3195 panic("implement me") 3196 } 3197 3198 func (l *localServiceDiscovery) GetProxyWorkloadLabels(*Proxy) labels.Instance { 3199 panic("implement me") 3200 } 3201 3202 func (l *localServiceDiscovery) GetIstioServiceAccounts(*Service) []string { 3203 return nil 3204 } 3205 3206 func (l *localServiceDiscovery) NetworkGateways() []NetworkGateway { 3207 // TODO implement fromRegistry logic from kube controller if needed 3208 return nil 3209 } 3210 3211 func (l *localServiceDiscovery) MCSServices() []MCSServiceInfo { 3212 return nil 3213 } 3214 3215 func TestResolveServiceAliases(t *testing.T) { 3216 type service struct { 3217 Name host.Name 3218 Aliases host.Names 3219 ExternalName string 3220 } 3221 tests := []struct { 3222 name string 3223 input []service 3224 output []service 3225 }{ 3226 { 3227 name: "no aliases", 3228 input: []service{{Name: "test"}}, 3229 output: []service{{Name: "test"}}, 3230 }, 3231 { 3232 name: "simple alias", 3233 input: []service{ 3234 {Name: "concrete"}, 3235 {Name: "alias", ExternalName: "concrete"}, 3236 }, 3237 output: []service{ 3238 {Name: "concrete", Aliases: host.Names{"alias"}}, 3239 {Name: "alias", ExternalName: "concrete"}, 3240 }, 3241 }, 3242 { 3243 name: "multiple alias", 3244 input: []service{ 3245 {Name: "concrete"}, 3246 {Name: "alias1", ExternalName: "concrete"}, 3247 {Name: "alias2", ExternalName: "concrete"}, 3248 }, 3249 output: []service{ 3250 {Name: "concrete", Aliases: host.Names{"alias1", "alias2"}}, 3251 {Name: "alias1", ExternalName: "concrete"}, 3252 {Name: "alias2", ExternalName: "concrete"}, 3253 }, 3254 }, 3255 { 3256 name: "chained alias", 3257 input: []service{ 3258 {Name: "concrete"}, 3259 {Name: "alias1", ExternalName: "alias2"}, 3260 {Name: "alias2", ExternalName: "concrete"}, 3261 }, 3262 output: []service{ 3263 {Name: "concrete", Aliases: host.Names{"alias1", "alias2"}}, 3264 {Name: "alias1", ExternalName: "alias2"}, 3265 {Name: "alias2", ExternalName: "concrete"}, 3266 }, 3267 }, 3268 { 3269 name: "looping alias", 3270 input: []service{ 3271 {Name: "alias1", ExternalName: "alias2"}, 3272 {Name: "alias2", ExternalName: "alias1"}, 3273 }, 3274 output: []service{ 3275 {Name: "alias1", ExternalName: "alias2"}, 3276 {Name: "alias2", ExternalName: "alias1"}, 3277 }, 3278 }, 3279 } 3280 for _, tt := range tests { 3281 t.Run(tt.name, func(t *testing.T) { 3282 inps := slices.Map(tt.input, func(e service) *Service { 3283 resolution := ClientSideLB 3284 if e.ExternalName != "" { 3285 resolution = Alias 3286 } 3287 return &Service{ 3288 Resolution: resolution, 3289 Attributes: ServiceAttributes{ 3290 K8sAttributes: K8sAttributes{ExternalName: e.ExternalName}, 3291 }, 3292 Hostname: e.Name, 3293 } 3294 }) 3295 resolveServiceAliases(inps, nil) 3296 out := slices.Map(inps, func(e *Service) service { 3297 return service{ 3298 Name: e.Hostname, 3299 Aliases: slices.Map(e.Attributes.Aliases, func(e NamespacedHostname) host.Name { 3300 return e.Hostname 3301 }), 3302 ExternalName: e.Attributes.K8sAttributes.ExternalName, 3303 } 3304 }) 3305 assert.Equal(t, tt.output, out) 3306 }) 3307 } 3308 } 3309 3310 func BenchmarkInitServiceAccounts(b *testing.B) { 3311 ps := NewPushContext() 3312 index := NewEndpointIndex(DisabledCache{}) 3313 env := &Environment{EndpointIndex: index} 3314 ps.Mesh = &meshconfig.MeshConfig{TrustDomainAliases: []string{"td1", "td2"}} 3315 3316 services := []*Service{ 3317 { 3318 Hostname: "svc-unset", 3319 Ports: allPorts, 3320 Attributes: ServiceAttributes{ 3321 Namespace: "test1", 3322 }, 3323 }, 3324 { 3325 Hostname: "svc-public", 3326 Ports: allPorts, 3327 Attributes: ServiceAttributes{ 3328 Namespace: "test1", 3329 ExportTo: sets.New(visibility.Public), 3330 }, 3331 }, 3332 { 3333 Hostname: "svc-private", 3334 Ports: allPorts, 3335 Attributes: ServiceAttributes{ 3336 Namespace: "test1", 3337 ExportTo: sets.New(visibility.Private), 3338 }, 3339 }, 3340 { 3341 Hostname: "svc-none", 3342 Ports: allPorts, 3343 Attributes: ServiceAttributes{ 3344 Namespace: "test1", 3345 ExportTo: sets.New(visibility.None), 3346 }, 3347 }, 3348 { 3349 Hostname: "svc-namespace", 3350 Ports: allPorts, 3351 Attributes: ServiceAttributes{ 3352 Namespace: "test1", 3353 ExportTo: sets.New(visibility.Instance("namespace")), 3354 }, 3355 }, 3356 } 3357 3358 for _, svc := range services { 3359 if index.shardsBySvc[string(svc.Hostname)] == nil { 3360 index.shardsBySvc[string(svc.Hostname)] = map[string]*EndpointShards{} 3361 } 3362 index.shardsBySvc[string(svc.Hostname)][svc.Attributes.Namespace] = &EndpointShards{ 3363 ServiceAccounts: sets.New("spiffe://cluster.local/ns/def/sa/sa1", "spiffe://cluster.local/ns/def/sa/sa2"), 3364 } 3365 } 3366 b.ResetTimer() 3367 for n := 0; n < b.N; n++ { 3368 ps.initServiceAccounts(env, services) 3369 } 3370 }