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