1// Copyright 2017 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 15package model_test 16 17import ( 18 "fmt" 19 "reflect" 20 "testing" 21 "time" 22 23 "github.com/davecgh/go-spew/spew" 24 "github.com/gogo/protobuf/proto" 25 26 mccpb "istio.io/api/mixer/v1/config/client" 27 networking "istio.io/api/networking/v1alpha3" 28 rbacproto "istio.io/api/rbac/v1alpha1" 29 authz "istio.io/api/security/v1beta1" 30 api "istio.io/api/type/v1beta1" 31 32 "istio.io/istio/pilot/pkg/config/memory" 33 "istio.io/istio/pilot/pkg/model" 34 mock_config "istio.io/istio/pilot/test/mock" 35 "istio.io/istio/pkg/config/constants" 36 "istio.io/istio/pkg/config/host" 37 "istio.io/istio/pkg/config/labels" 38 "istio.io/istio/pkg/config/protocol" 39 "istio.io/istio/pkg/config/schema/collection" 40 "istio.io/istio/pkg/config/schema/collections" 41 "istio.io/istio/pkg/config/schema/resource" 42) 43 44// getByMessageName finds a schema by message name if it is available 45// In test setup, we do not have more than one descriptor with the same message type, so this 46// function is ok for testing purpose. 47func getByMessageName(schemas collection.Schemas, name string) (collection.Schema, bool) { 48 for _, s := range schemas.All() { 49 if s.Resource().Proto() == name { 50 return s, true 51 } 52 } 53 return nil, false 54} 55 56func schemaFor(kind, proto string) collection.Schema { 57 return collection.Builder{ 58 Name: kind, 59 Resource: resource.Builder{ 60 Kind: kind, 61 Plural: kind + "s", 62 Proto: proto, 63 }.BuildNoValidate(), 64 }.MustBuild() 65} 66 67func TestConfigDescriptor(t *testing.T) { 68 a := schemaFor("a", "proxy.A") 69 schemas := collection.SchemasFor( 70 a, 71 schemaFor("b", "proxy.B"), 72 schemaFor("c", "proxy.C")) 73 want := []string{"a", "b", "c"} 74 got := schemas.Kinds() 75 if !reflect.DeepEqual(got, want) { 76 t.Errorf("descriptor.Types() => got %+vwant %+v", spew.Sdump(got), spew.Sdump(want)) 77 } 78 79 aType, aExists := schemas.FindByGroupVersionKind(a.Resource().GroupVersionKind()) 80 if !aExists || !reflect.DeepEqual(aType, a) { 81 t.Errorf("descriptor.GetByType(a) => got %+v, want %+v", aType, a) 82 } 83 if _, exists := schemas.FindByGroupVersionKind(resource.GroupVersionKind{Kind: "missing"}); exists { 84 t.Error("descriptor.GetByType(missing) => got true, want false") 85 } 86 87 aSchema, aSchemaExists := getByMessageName(schemas, a.Resource().Proto()) 88 if !aSchemaExists || !reflect.DeepEqual(aSchema, a) { 89 t.Errorf("descriptor.GetByMessageName(a) => got %+v, want %+v", aType, a) 90 } 91 _, aSchemaNotExist := getByMessageName(schemas, "blah") 92 if aSchemaNotExist { 93 t.Errorf("descriptor.GetByMessageName(blah) => got true, want false") 94 } 95} 96 97func TestEventString(t *testing.T) { 98 cases := []struct { 99 in model.Event 100 want string 101 }{ 102 {model.EventAdd, "add"}, 103 {model.EventUpdate, "update"}, 104 {model.EventDelete, "delete"}, 105 } 106 for _, c := range cases { 107 if got := c.in.String(); got != c.want { 108 t.Errorf("Failed: got %q want %q", got, c.want) 109 } 110 } 111} 112 113func TestPortList(t *testing.T) { 114 pl := model.PortList{ 115 {Name: "http", Port: 80, Protocol: protocol.HTTP}, 116 {Name: "http-alt", Port: 8080, Protocol: protocol.HTTP}, 117 } 118 119 gotNames := pl.GetNames() 120 wantNames := []string{"http", "http-alt"} 121 if !reflect.DeepEqual(gotNames, wantNames) { 122 t.Errorf("GetNames() failed: got %v want %v", gotNames, wantNames) 123 } 124 125 cases := []struct { 126 name string 127 port *model.Port 128 found bool 129 }{ 130 {name: pl[0].Name, port: pl[0], found: true}, 131 {name: "foobar", found: false}, 132 } 133 134 for _, c := range cases { 135 gotPort, gotFound := pl.Get(c.name) 136 if c.found != gotFound || !reflect.DeepEqual(gotPort, c.port) { 137 t.Errorf("Get() failed: gotFound=%v wantFound=%v\ngot %+vwant %+v", 138 gotFound, c.found, spew.Sdump(gotPort), spew.Sdump(c.port)) 139 } 140 } 141} 142 143func TestServiceKey(t *testing.T) { 144 svc := &model.Service{Hostname: "hostname"} 145 146 // Verify Service.Key() delegates to ServiceKey() 147 { 148 want := "hostname|http|a=b,c=d" 149 port := &model.Port{Name: "http", Port: 80, Protocol: protocol.HTTP} 150 l := labels.Instance{"a": "b", "c": "d"} 151 got := svc.Key(port, l) 152 if !reflect.DeepEqual(got, want) { 153 t.Errorf("Service.Key() failed: got %v want %v", got, want) 154 } 155 } 156 157 cases := []struct { 158 port model.PortList 159 l labels.Collection 160 want string 161 }{ 162 { 163 port: model.PortList{ 164 {Name: "http", Port: 80, Protocol: protocol.HTTP}, 165 {Name: "http-alt", Port: 8080, Protocol: protocol.HTTP}, 166 }, 167 l: labels.Collection{{"a": "b", "c": "d"}}, 168 want: "hostname|http,http-alt|a=b,c=d", 169 }, 170 { 171 port: model.PortList{{Name: "http", Port: 80, Protocol: protocol.HTTP}}, 172 l: labels.Collection{{"a": "b", "c": "d"}}, 173 want: "hostname|http|a=b,c=d", 174 }, 175 { 176 port: model.PortList{{Port: 80, Protocol: protocol.HTTP}}, 177 l: labels.Collection{{"a": "b", "c": "d"}}, 178 want: "hostname||a=b,c=d", 179 }, 180 { 181 port: model.PortList{}, 182 l: labels.Collection{{"a": "b", "c": "d"}}, 183 want: "hostname||a=b,c=d", 184 }, 185 { 186 port: model.PortList{{Name: "http", Port: 80, Protocol: protocol.HTTP}}, 187 l: labels.Collection{nil}, 188 want: "hostname|http", 189 }, 190 { 191 port: model.PortList{{Name: "http", Port: 80, Protocol: protocol.HTTP}}, 192 l: labels.Collection{}, 193 want: "hostname|http", 194 }, 195 { 196 port: model.PortList{}, 197 l: labels.Collection{}, 198 want: "hostname", 199 }, 200 } 201 202 for _, c := range cases { 203 got := model.ServiceKey(svc.Hostname, c.port, c.l) 204 if !reflect.DeepEqual(got, c.want) { 205 t.Errorf("Failed: got %q want %q", got, c.want) 206 } 207 } 208} 209 210func TestSubsetKey(t *testing.T) { 211 hostname := host.Name("hostname") 212 cases := []struct { 213 hostname host.Name 214 subset string 215 port int 216 want string 217 }{ 218 { 219 hostname: "hostname", 220 subset: "subset", 221 port: 80, 222 want: "outbound|80|subset|hostname", 223 }, 224 { 225 hostname: "hostname", 226 subset: "", 227 port: 80, 228 want: "outbound|80||hostname", 229 }, 230 } 231 232 for _, c := range cases { 233 got := model.BuildSubsetKey(model.TrafficDirectionOutbound, c.subset, hostname, c.port) 234 if got != c.want { 235 t.Errorf("Failed: got %q want %q", got, c.want) 236 } 237 238 // test parse subset key. ParseSubsetKey is the inverse of BuildSubsetKey 239 _, s, h, p := model.ParseSubsetKey(got) 240 if s != c.subset || h != c.hostname || p != c.port { 241 t.Errorf("Failed: got %s,%s,%d want %s,%s,%d", s, h, p, c.subset, c.hostname, c.port) 242 } 243 } 244} 245 246func TestLabelsEquals(t *testing.T) { 247 cases := []struct { 248 a, b labels.Instance 249 want bool 250 }{ 251 { 252 a: nil, 253 b: labels.Instance{"a": "b"}, 254 }, 255 { 256 a: labels.Instance{"a": "b"}, 257 b: nil, 258 }, 259 { 260 a: labels.Instance{"a": "b"}, 261 b: labels.Instance{"a": "b"}, 262 want: true, 263 }, 264 } 265 for _, c := range cases { 266 if got := c.a.Equals(c.b); got != c.want { 267 t.Errorf("Failed: got eq=%v want=%v for %q ?= %q", got, c.want, c.a, c.b) 268 } 269 } 270} 271 272func TestConfigKey(t *testing.T) { 273 cfg := mock_config.Make("ns", 2) 274 want := "MockConfig/ns/mock-config2" 275 if key := cfg.ConfigMeta.Key(); key != want { 276 t.Fatalf("config.Key() => got %q, want %q", key, want) 277 } 278} 279 280func TestResolveHostname(t *testing.T) { 281 cases := []struct { 282 meta model.ConfigMeta 283 svc *mccpb.IstioService 284 want host.Name 285 }{ 286 { 287 meta: model.ConfigMeta{Namespace: "default", Domain: "cluster.local"}, 288 svc: &mccpb.IstioService{Name: "hello"}, 289 want: "hello.default.svc.cluster.local", 290 }, 291 { 292 meta: model.ConfigMeta{Namespace: "foo", Domain: "foo"}, 293 svc: &mccpb.IstioService{Name: "hello", 294 Namespace: "default", Domain: "svc.cluster.local"}, 295 want: "hello.default.svc.cluster.local", 296 }, 297 { 298 meta: model.ConfigMeta{}, 299 svc: &mccpb.IstioService{Name: "hello"}, 300 want: "hello", 301 }, 302 { 303 meta: model.ConfigMeta{Namespace: "default"}, 304 svc: &mccpb.IstioService{Name: "hello"}, 305 want: "hello.default", 306 }, 307 { 308 meta: model.ConfigMeta{Namespace: "default", Domain: "cluster.local"}, 309 svc: &mccpb.IstioService{Service: "reviews.service.consul"}, 310 want: "reviews.service.consul", 311 }, 312 { 313 meta: model.ConfigMeta{Namespace: "foo", Domain: "foo"}, 314 svc: &mccpb.IstioService{Name: "hello", Service: "reviews.service.consul", 315 Namespace: "default", Domain: "svc.cluster.local"}, 316 want: "reviews.service.consul", 317 }, 318 { 319 meta: model.ConfigMeta{Namespace: "default", Domain: "cluster.local"}, 320 svc: &mccpb.IstioService{Service: "*cnn.com"}, 321 want: "*cnn.com", 322 }, 323 { 324 meta: model.ConfigMeta{Namespace: "foo", Domain: "foo"}, 325 svc: &mccpb.IstioService{Name: "hello", Service: "*cnn.com", 326 Namespace: "default", Domain: "svc.cluster.local"}, 327 want: "*cnn.com", 328 }, 329 } 330 331 for _, test := range cases { 332 if got := model.ResolveHostname(test.meta, test.svc); got != test.want { 333 t.Errorf("ResolveHostname(%v, %v) => got %q, want %q", test.meta, test.svc, got, test.want) 334 } 335 } 336} 337 338func TestResolveShortnameToFQDN(t *testing.T) { 339 tests := []struct { 340 name string 341 meta model.ConfigMeta 342 out host.Name 343 }{ 344 { 345 "*", model.ConfigMeta{}, "*", 346 }, 347 { 348 "*", model.ConfigMeta{Namespace: "default", Domain: "cluster.local"}, "*", 349 }, 350 { 351 "foo", model.ConfigMeta{Namespace: "default", Domain: "cluster.local"}, "foo.default.svc.cluster.local", 352 }, 353 { 354 "foo.bar", model.ConfigMeta{Namespace: "default", Domain: "cluster.local"}, "foo.bar", 355 }, 356 { 357 "foo", model.ConfigMeta{Domain: "cluster.local"}, "foo.svc.cluster.local", 358 }, 359 { 360 "foo", model.ConfigMeta{Namespace: "default"}, "foo.default", 361 }, 362 } 363 364 for idx, tt := range tests { 365 t.Run(fmt.Sprintf("[%d] %s", idx, tt.out), func(t *testing.T) { 366 if actual := model.ResolveShortnameToFQDN(tt.name, tt.meta); actual != tt.out { 367 t.Fatalf("model.ResolveShortnameToFQDN(%q, %v) = %q wanted %q", tt.name, tt.meta, actual, tt.out) 368 } 369 }) 370 } 371} 372 373func TestMostSpecificHostMatch(t *testing.T) { 374 tests := []struct { 375 in []host.Name 376 needle host.Name 377 want host.Name 378 }{ 379 // this has to be a sorted list 380 {[]host.Name{}, "*", ""}, 381 {[]host.Name{"*.foo.com", "*.com"}, "bar.foo.com", "*.foo.com"}, 382 {[]host.Name{"*.foo.com", "*.com"}, "foo.com", "*.com"}, 383 {[]host.Name{"foo.com", "*.com"}, "*.foo.com", "*.com"}, 384 385 {[]host.Name{"*.foo.com", "foo.com"}, "foo.com", "foo.com"}, 386 {[]host.Name{"*.foo.com", "foo.com"}, "*.foo.com", "*.foo.com"}, 387 388 // this passes because we sort alphabetically 389 {[]host.Name{"bar.com", "foo.com"}, "*.com", ""}, 390 391 {[]host.Name{"bar.com", "*.foo.com"}, "*foo.com", ""}, 392 {[]host.Name{"foo.com", "*.foo.com"}, "*foo.com", ""}, 393 394 // should prioritize closest match 395 {[]host.Name{"*.bar.com", "foo.bar.com"}, "foo.bar.com", "foo.bar.com"}, 396 {[]host.Name{"*.foo.bar.com", "bar.foo.bar.com"}, "bar.foo.bar.com", "bar.foo.bar.com"}, 397 398 // should not match non-wildcards for wildcard needle 399 {[]host.Name{"bar.foo.com", "foo.bar.com"}, "*.foo.com", ""}, 400 {[]host.Name{"foo.bar.foo.com", "bar.foo.bar.com"}, "*.bar.foo.com", ""}, 401 } 402 403 for idx, tt := range tests { 404 t.Run(fmt.Sprintf("[%d] %s", idx, tt.needle), func(t *testing.T) { 405 actual, found := model.MostSpecificHostMatch(tt.needle, tt.in) 406 if tt.want != "" && !found { 407 t.Fatalf("model.MostSpecificHostMatch(%q, %v) = %v, %t; want: %v", tt.needle, tt.in, actual, found, tt.want) 408 } else if actual != tt.want { 409 t.Fatalf("model.MostSpecificHostMatch(%q, %v) = %v, %t; want: %v", tt.needle, tt.in, actual, found, tt.want) 410 } 411 }) 412 } 413} 414 415func TestServiceRoles(t *testing.T) { 416 store := model.MakeIstioStore(memory.Make(collections.Pilot)) 417 addRbacConfigToStore(collections.IstioRbacV1Alpha1Serviceroles.Resource().Kind(), "role1", "istio-system", store, t) 418 addRbacConfigToStore(collections.IstioRbacV1Alpha1Serviceroles.Resource().Kind(), "role2", "default", store, t) 419 addRbacConfigToStore(collections.IstioRbacV1Alpha1Serviceroles.Resource().Kind(), "role3", "istio-system", store, t) 420 tests := []struct { 421 namespace string 422 expectName map[string]bool 423 }{ 424 {namespace: "wrong", expectName: nil}, 425 {namespace: "default", expectName: map[string]bool{"role2": true}}, 426 {namespace: "istio-system", expectName: map[string]bool{"role1": true, "role3": true}}, 427 } 428 429 for _, tt := range tests { 430 cfg := store.ServiceRoles(tt.namespace) 431 if tt.expectName != nil { 432 for _, cfg := range cfg { 433 if !tt.expectName[cfg.Name] { 434 t.Errorf("model.ServiceRoles: expecting %v, but got %v", tt.expectName, cfg) 435 } 436 } 437 } else if len(cfg) != 0 { 438 t.Errorf("model.ServiceRoles: expecting nil, but got %v", cfg) 439 } 440 } 441} 442 443func TestServiceRoleBindings(t *testing.T) { 444 store := model.MakeIstioStore(memory.Make(collections.Pilot)) 445 addRbacConfigToStore(collections.IstioRbacV1Alpha1Servicerolebindings.Resource().Kind(), "binding1", "istio-system", store, t) 446 addRbacConfigToStore(collections.IstioRbacV1Alpha1Servicerolebindings.Resource().Kind(), "binding2", "default", store, t) 447 addRbacConfigToStore(collections.IstioRbacV1Alpha1Servicerolebindings.Resource().Kind(), "binding3", "istio-system", store, t) 448 tests := []struct { 449 namespace string 450 expectName map[string]bool 451 }{ 452 {namespace: "wrong", expectName: nil}, 453 {namespace: "default", expectName: map[string]bool{"binding2": true}}, 454 {namespace: "istio-system", expectName: map[string]bool{"binding1": true, "binding3": true}}, 455 } 456 457 for _, tt := range tests { 458 cfg := store.ServiceRoleBindings(tt.namespace) 459 if tt.expectName != nil { 460 for _, cfg := range cfg { 461 if !tt.expectName[cfg.Name] { 462 t.Errorf("model.ServiceRoleBinding: expecting %v, but got %v", tt.expectName, cfg) 463 } 464 } 465 } else if len(cfg) != 0 { 466 t.Errorf("model.ServiceRoleBinding: expecting nil, but got %v", cfg) 467 } 468 } 469} 470 471func TestRbacConfig(t *testing.T) { 472 store := model.MakeIstioStore(memory.Make(collections.Pilot)) 473 addRbacConfigToStore(collections.IstioRbacV1Alpha1Rbacconfigs.Resource().Kind(), constants.DefaultRbacConfigName, "", store, t) 474 rbacConfig := store.RbacConfig() 475 if rbacConfig.Name != constants.DefaultRbacConfigName { 476 t.Errorf("model.RbacConfig: expecting %s, but got %s", constants.DefaultRbacConfigName, rbacConfig.Name) 477 } 478} 479 480func TestClusterRbacConfig(t *testing.T) { 481 store := model.MakeIstioStore(memory.Make(collections.Pilot)) 482 addRbacConfigToStore(collections.IstioRbacV1Alpha1Clusterrbacconfigs.Resource().Kind(), constants.DefaultRbacConfigName, "", store, t) 483 rbacConfig := store.ClusterRbacConfig() 484 if rbacConfig.Name != constants.DefaultRbacConfigName { 485 t.Errorf("model.ClusterRbacConfig: expecting %s, but got %s", constants.DefaultRbacConfigName, rbacConfig.Name) 486 } 487} 488 489func TestAuthorizationPolicies(t *testing.T) { 490 store := model.MakeIstioStore(memory.Make(collections.Pilot)) 491 addRbacConfigToStore(collections.IstioSecurityV1Beta1Authorizationpolicies.Resource().Kind(), "policy1", "istio-system", store, t) 492 addRbacConfigToStore(collections.IstioSecurityV1Beta1Authorizationpolicies.Resource().Kind(), "policy2", "default", store, t) 493 addRbacConfigToStore(collections.IstioSecurityV1Beta1Authorizationpolicies.Resource().Kind(), "policy3", "istio-system", store, t) 494 tests := []struct { 495 namespace string 496 expectName map[string]bool 497 }{ 498 {namespace: "wrong", expectName: nil}, 499 {namespace: "default", expectName: map[string]bool{"policy2": true}}, 500 {namespace: "istio-system", expectName: map[string]bool{"policy1": true, "policy3": true}}, 501 } 502 503 for _, tt := range tests { 504 cfg := store.AuthorizationPolicies(tt.namespace) 505 if tt.expectName != nil { 506 for _, cfg := range cfg { 507 if !tt.expectName[cfg.Name] { 508 t.Errorf("model.AuthorizationPolicy: expecting %v, but got %v", tt.expectName, cfg) 509 } 510 } 511 } else if len(cfg) != 0 { 512 t.Errorf("model.AuthorizationPolicy: expecting nil, but got %v", cfg) 513 } 514 } 515} 516 517func addRbacConfigToStore(kind, name, namespace string, store model.IstioConfigStore, t *testing.T) { 518 var value proto.Message 519 var group, version string 520 switch kind { 521 case collections.IstioRbacV1Alpha1Serviceroles.Resource().Kind(): 522 group = collections.IstioRbacV1Alpha1Serviceroles.Resource().Group() 523 version = collections.IstioRbacV1Alpha1Serviceroles.Resource().Version() 524 value = &rbacproto.ServiceRole{Rules: []*rbacproto.AccessRule{ 525 {Services: []string{"service0"}, Methods: []string{"GET"}}}} 526 case collections.IstioRbacV1Alpha1Servicerolebindings.Resource().Kind(): 527 group = collections.IstioRbacV1Alpha1Servicerolebindings.Resource().Group() 528 version = collections.IstioRbacV1Alpha1Servicerolebindings.Resource().Version() 529 value = &rbacproto.ServiceRoleBinding{ 530 Subjects: []*rbacproto.Subject{{User: "User0"}}, 531 RoleRef: &rbacproto.RoleRef{Kind: "ServiceRole", Name: "ServiceRole001"}} 532 case collections.IstioSecurityV1Beta1Authorizationpolicies.Resource().Kind(): 533 group = collections.IstioSecurityV1Beta1Authorizationpolicies.Resource().Group() 534 version = collections.IstioSecurityV1Beta1Authorizationpolicies.Resource().Version() 535 value = &authz.AuthorizationPolicy{ 536 Selector: &api.WorkloadSelector{ 537 MatchLabels: map[string]string{"app": "test"}, 538 }, 539 } 540 case collections.IstioRbacV1Alpha1Rbacconfigs.Resource().Kind(): 541 group = collections.IstioRbacV1Alpha1Rbacconfigs.Resource().Group() 542 version = collections.IstioRbacV1Alpha1Rbacconfigs.Resource().Version() 543 value = &rbacproto.RbacConfig{Mode: rbacproto.RbacConfig_ON} 544 case collections.IstioRbacV1Alpha1Clusterrbacconfigs.Resource().Kind(): 545 group = collections.IstioRbacV1Alpha1Clusterrbacconfigs.Resource().Group() 546 version = collections.IstioRbacV1Alpha1Clusterrbacconfigs.Resource().Version() 547 value = &rbacproto.RbacConfig{Mode: rbacproto.RbacConfig_ON} 548 default: 549 panic("Unknown kind: " + kind) 550 } 551 cfg := model.Config{ 552 ConfigMeta: model.ConfigMeta{ 553 Type: kind, 554 Group: group, 555 Version: version, 556 Name: name, 557 Namespace: namespace, 558 }, 559 Spec: value, // Not used in test, added to pass validation. 560 } 561 if _, err := store.Create(cfg); err != nil { 562 t.Error(err) 563 } 564} 565 566type fakeStore struct { 567 model.ConfigStore 568 cfg map[resource.GroupVersionKind][]model.Config 569 err error 570} 571 572func (l *fakeStore) List(typ resource.GroupVersionKind, namespace string) ([]model.Config, error) { 573 ret := l.cfg[typ] 574 return ret, l.err 575} 576 577func (l *fakeStore) Schemas() collection.Schemas { 578 return collections.Pilot 579} 580 581func TestIstioConfigStore_QuotaSpecByDestination(t *testing.T) { 582 ns := "ns1" 583 l := &fakeStore{ 584 cfg: map[resource.GroupVersionKind][]model.Config{ 585 collections.IstioMixerV1ConfigClientQuotaspecbindings.Resource().GroupVersionKind(): { 586 { 587 ConfigMeta: model.ConfigMeta{ 588 Namespace: ns, 589 Domain: "cluster.local", 590 }, 591 Spec: &mccpb.QuotaSpecBinding{ 592 Services: []*mccpb.IstioService{ 593 { 594 Name: "a", 595 Namespace: ns, 596 }, 597 }, 598 QuotaSpecs: []*mccpb.QuotaSpecBinding_QuotaSpecReference{ 599 { 600 Name: "request-count", 601 }, 602 { 603 Name: "does-not-exist", 604 }, 605 }, 606 }, 607 }, 608 }, 609 collections.IstioMixerV1ConfigClientQuotaspecs.Resource().GroupVersionKind(): { 610 { 611 ConfigMeta: model.ConfigMeta{ 612 Name: "request-count", 613 Namespace: ns, 614 }, 615 Spec: &mccpb.QuotaSpec{ 616 Rules: []*mccpb.QuotaRule{ 617 { 618 Quotas: []*mccpb.Quota{ 619 { 620 Quota: "requestcount", 621 Charge: 100, 622 }, 623 }, 624 }, 625 }, 626 }, 627 }, 628 }, 629 }, 630 } 631 ii := model.MakeIstioStore(l) 632 cfgs := ii.QuotaSpecByDestination(host.Name("a." + ns + ".svc.cluster.local")) 633 634 if len(cfgs) != 1 { 635 t.Fatalf("did not find 1 matched quota") 636 } 637} 638 639func TestMatchesDestHost(t *testing.T) { 640 for _, tst := range []struct { 641 destinationHost string 642 svc string 643 ans bool 644 }{ 645 { 646 destinationHost: "myhost.ns.cluster.local", 647 svc: "myhost.ns.cluster.local", 648 ans: true, 649 }, 650 { 651 destinationHost: "myhost.ns.cluster.local", 652 svc: "*", 653 ans: true, 654 }, 655 { 656 destinationHost: "myhost.ns.cluster.local", 657 svc: "*.ns.*", 658 ans: true, 659 }, 660 { 661 destinationHost: "myhost.ns.cluster.local", 662 svc: "*.ns2.*", 663 ans: false, 664 }, 665 { 666 destinationHost: "myhost.ns.cluster.local", 667 svc: "myhost.ns2.cluster.local", 668 ans: false, 669 }, 670 { 671 destinationHost: "myhost.ns.cluster.local", 672 svc: "ns.*.svc.cluster", 673 ans: false, 674 }, 675 } { 676 t.Run(fmt.Sprintf("%s-%s", tst.destinationHost, tst.svc), func(t *testing.T) { 677 ans := model.MatchesDestHost(tst.destinationHost, model.ConfigMeta{}, &mccpb.IstioService{ 678 Service: tst.svc, 679 }) 680 if ans != tst.ans { 681 t.Fatalf("want: %v, got: %v", tst.ans, ans) 682 } 683 }) 684 } 685} 686 687func TestIstioConfigStore_ServiceEntries(t *testing.T) { 688 ns := "ns1" 689 l := &fakeStore{ 690 cfg: map[resource.GroupVersionKind][]model.Config{ 691 collections.IstioNetworkingV1Alpha3Serviceentries.Resource().GroupVersionKind(): { 692 { 693 ConfigMeta: model.ConfigMeta{ 694 Name: "request-count-1", 695 Namespace: ns, 696 }, 697 Spec: &networking.ServiceEntry{ 698 Hosts: []string{"*.googleapis.com"}, 699 Ports: []*networking.Port{ 700 { 701 Name: "https", 702 Number: 443, 703 Protocol: "HTTP", 704 }, 705 }, 706 }, 707 }, 708 }, 709 collections.IstioMixerV1ConfigClientQuotaspecs.Resource().GroupVersionKind(): { 710 { 711 ConfigMeta: model.ConfigMeta{ 712 Name: "request-count-2", 713 Namespace: ns, 714 }, 715 Spec: &mccpb.QuotaSpec{ 716 Rules: []*mccpb.QuotaRule{ 717 { 718 Quotas: []*mccpb.Quota{ 719 { 720 Quota: "requestcount", 721 Charge: 100, 722 }, 723 }, 724 }, 725 }, 726 }, 727 }, 728 }, 729 }, 730 } 731 ii := model.MakeIstioStore(l) 732 cfgs := ii.ServiceEntries() 733 734 if len(cfgs) != 1 { 735 t.Fatalf("did not find 1 matched ServiceEntry, \n%v", cfgs) 736 } 737} 738 739func TestIstioConfigStore_Gateway(t *testing.T) { 740 workloadLabels := labels.Collection{} 741 now := time.Now() 742 gw1 := model.Config{ 743 ConfigMeta: model.ConfigMeta{ 744 Name: "name1", 745 Namespace: "zzz", 746 CreationTimestamp: now, 747 }, 748 Spec: &networking.Gateway{}, 749 } 750 gw2 := model.Config{ 751 ConfigMeta: model.ConfigMeta{ 752 Name: "name1", 753 Namespace: "aaa", 754 CreationTimestamp: now, 755 }, 756 Spec: &networking.Gateway{}, 757 } 758 gw3 := model.Config{ 759 ConfigMeta: model.ConfigMeta{ 760 Name: "name1", 761 Namespace: "ns2", 762 CreationTimestamp: now.Add(time.Second * -1), 763 }, 764 Spec: &networking.Gateway{}, 765 } 766 767 l := &fakeStore{ 768 cfg: map[resource.GroupVersionKind][]model.Config{ 769 collections.IstioNetworkingV1Alpha3Gateways.Resource().GroupVersionKind(): {gw1, gw2, gw3}, 770 }, 771 } 772 ii := model.MakeIstioStore(l) 773 774 // Gateways should be returned in a stable order 775 expectedConfig := []model.Config{ 776 gw3, // first config by timestamp 777 gw2, // timestamp match with gw1, but name comes first 778 gw1, // timestamp match with gw2, but name comes last 779 } 780 cfgs := ii.Gateways(workloadLabels) 781 782 if !reflect.DeepEqual(expectedConfig, cfgs) { 783 t.Errorf("Got different Config, Excepted:\n%v\n, Got: \n%v\n", expectedConfig, cfgs) 784 } 785} 786 787func TestDeepCopy(t *testing.T) { 788 cfg := model.Config{ 789 ConfigMeta: model.ConfigMeta{ 790 Name: "name1", 791 Namespace: "zzz", 792 CreationTimestamp: time.Now(), 793 }, 794 Spec: &networking.Gateway{}, 795 } 796 797 copied := cfg.DeepCopy() 798 799 if !(cfg.Spec.String() == copied.Spec.String() && 800 cfg.Namespace == copied.Namespace && 801 cfg.Name == copied.Name && 802 cfg.CreationTimestamp == copied.CreationTimestamp) { 803 t.Fatalf("cloned config is not identical") 804 } 805 806 // change the copied gateway to see if the original config is not effected 807 copiedGateway := copied.Spec.(*networking.Gateway) 808 copiedGateway.Selector = map[string]string{"app": "test"} 809 810 gateway := cfg.Spec.(*networking.Gateway) 811 if gateway.Selector != nil { 812 t.Errorf("Original gateway is mutated") 813 } 814} 815