1 package watcher
2
3 import (
4 "testing"
5
6 "k8s.io/client-go/tools/cache"
7
8 sp "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
9 "github.com/linkerd/linkerd2/controller/k8s"
10 logging "github.com/sirupsen/logrus"
11 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12 )
13
14 var testServiceProfile = sp.ServiceProfile{
15 ObjectMeta: metav1.ObjectMeta{
16 Name: "foobar.ns.svc.cluster.local",
17 Namespace: "linkerd",
18 },
19 Spec: sp.ServiceProfileSpec{
20 Routes: []*sp.RouteSpec{
21 {
22 Condition: &sp.RequestMatch{
23 PathRegex: "/x/y/z",
24 },
25 ResponseClasses: []*sp.ResponseClass{
26 {
27 Condition: &sp.ResponseMatch{
28 Status: &sp.Range{
29 Min: 500,
30 },
31 },
32 IsFailure: true,
33 },
34 },
35 },
36 },
37 },
38 }
39
40 var testServiceProfileResource = `
41 apiVersion: linkerd.io/v1alpha2
42 kind: ServiceProfile
43 metadata:
44 name: foobar.ns.svc.cluster.local
45 namespace: linkerd
46 spec:
47 routes:
48 - condition:
49 pathRegex: "/x/y/z"
50 responseClasses:
51 - condition:
52 status:
53 min: 500
54 isFailure: true`
55
56 func TestProfileWatcherUpdates(t *testing.T) {
57 for _, tt := range []struct {
58 name string
59 k8sConfigs []string
60 id ProfileID
61 expectedProfiles []*sp.ServiceProfileSpec
62 }{
63 {
64 name: "service profile",
65 k8sConfigs: []string{testServiceProfileResource},
66 id: ProfileID{Name: testServiceProfile.Name, Namespace: testServiceProfile.Namespace},
67 expectedProfiles: []*sp.ServiceProfileSpec{
68 &testServiceProfile.Spec,
69 },
70 },
71 {
72 name: "service without profile",
73 k8sConfigs: []string{},
74 id: ProfileID{Name: "foobar.ns.svc.cluster.local", Namespace: "ns"},
75 expectedProfiles: []*sp.ServiceProfileSpec{
76 nil,
77 },
78 },
79 } {
80 tt := tt
81 t.Run(tt.name, func(t *testing.T) {
82 k8sAPI, err := k8s.NewFakeAPI(tt.k8sConfigs...)
83 if err != nil {
84 t.Fatalf("NewFakeAPI returned an error: %s", err)
85 }
86
87 watcher, err := NewProfileWatcher(k8sAPI, logging.WithField("test", t.Name()))
88 if err != nil {
89 t.Fatalf("can't create profile watcher: %s", err)
90 }
91
92 k8sAPI.Sync(nil)
93
94 listener := NewBufferingProfileListener()
95
96 watcher.Subscribe(tt.id, listener)
97
98 actualProfiles := make([]*sp.ServiceProfileSpec, 0)
99
100 listener.mu.RLock()
101 defer listener.mu.RUnlock()
102 for _, profile := range listener.Profiles {
103 if profile == nil {
104 actualProfiles = append(actualProfiles, nil)
105 } else {
106 actualProfiles = append(actualProfiles, &profile.Spec)
107 }
108 }
109
110 testCompare(t, tt.expectedProfiles, actualProfiles)
111 })
112 }
113 }
114
115 func TestProfileWatcherDeletes(t *testing.T) {
116 for _, tt := range []struct {
117 name string
118 k8sConfigs []string
119 id ProfileID
120 objectToDelete interface{}
121 }{
122 {
123 name: "can delete service profiles",
124 k8sConfigs: []string{testServiceProfileResource},
125 id: ProfileID{Name: testServiceProfile.Name, Namespace: testServiceProfile.Namespace},
126 objectToDelete: &testServiceProfile,
127 },
128 {
129 name: "can delete service profiles wrapped in a DeletedFinalStateUnknown",
130 k8sConfigs: []string{testServiceProfileResource},
131 id: ProfileID{Name: testServiceProfile.Name, Namespace: testServiceProfile.Namespace},
132 objectToDelete: cache.DeletedFinalStateUnknown{Obj: &testServiceProfile},
133 },
134 } {
135 tt := tt
136 t.Run(tt.name, func(t *testing.T) {
137 k8sAPI, err := k8s.NewFakeAPI(tt.k8sConfigs...)
138 if err != nil {
139 t.Fatalf("NewFakeAPI returned an error: %s", err)
140 }
141
142 watcher, err := NewProfileWatcher(k8sAPI, logging.WithField("test", t.Name()))
143 if err != nil {
144 t.Fatalf("can't create profile watcher: %s", err)
145 }
146 k8sAPI.Sync(nil)
147
148 listener := NewDeletingProfileListener()
149
150 watcher.Subscribe(tt.id, listener)
151
152 watcher.deleteProfile(tt.objectToDelete)
153
154 if listener.NumDeletes != 1 {
155 t.Fatalf("Expected to get 1 deletes but got %v", listener.NumDeletes)
156 }
157 })
158 }
159 }
160
View as plain text