1 package profiles
2
3 import (
4 "errors"
5 "fmt"
6 "testing"
7 )
8
9 type spExp struct {
10 err error
11 sp string
12 }
13
14 func TestValidate(t *testing.T) {
15 expectations := []spExp{
16 {
17 err: nil,
18 sp: `apiVersion: linkerd.io/v1alpha2
19 kind: ServiceProfile
20 metadata:
21 name: name.ns.svc.cluster.local
22 namespace: linkerd-ns
23 spec:
24 routes:
25 - name: name-1
26 condition:
27 method: GET
28 pathRegex: /route-1`,
29 },
30 {
31 err: nil,
32 sp: `apiVersion: linkerd.io/v1alpha2
33 kind: ServiceProfile
34 metadata:
35 name: name.ns.svc.cluster.local
36 namespace: linkerd-ns
37 spec:
38 retryBudget:
39 minRetriesPerSecond: 5
40 retryRatio: 0.2
41 ttl: 10ms
42 routes:
43 - name: name-1
44 condition:
45 method: GET
46 pathRegex: /route-1
47 any:
48 - all:
49 - method: POST
50 - pathRegex: '/authors/\d+'
51 - all:
52 - not:
53 method: DELETE
54 - pathRegex: /info.txt
55 responseClasses:
56 - condition:
57 status:
58 min: 500
59 max: 599
60 all:
61 - status:
62 min: 500
63 max: 599
64 - not:
65 status:
66 min: 503`,
67 },
68 {
69 err: errors.New("ServiceProfile \"^.^\" has invalid name: a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')"),
70 sp: `apiVersion: linkerd.io/v1alpha2
71 kind: ServiceProfile
72 metadata:
73 name: ^.^
74 namespace: linkerd-ns
75 spec:
76 routes:
77 - name: name-1
78 condition:
79 method: GET
80 pathRegex: /route-1`,
81 },
82 {
83 err: errors.New("failed to validate ServiceProfile: error unmarshaling JSON: while decoding JSON: json: unknown field \"foo\""),
84 sp: `apiVersion: linkerd.io/v1alpha2
85 kind: ServiceProfile
86 metadata:
87 name: name.ns.svc.cluster.local
88 namespace: linkerd-ns
89 spec:
90 foo: bar
91 routes:
92 - name: name-1
93 condition:
94 method: GET
95 pathRegex: /route-1`,
96 },
97 {
98 err: errors.New("failed to validate ServiceProfile: error unmarshaling JSON: while decoding JSON: json: unknown field \"foo\""),
99 sp: `apiVersion: linkerd.io/v1alpha2
100 kind: ServiceProfile
101 metadata:
102 name: name.ns.svc.cluster.local
103 namespace: linkerd-ns
104 spec:
105 routes:
106 - name: name-1
107 foo: bar
108 condition:
109 method: GET
110 pathRegex: /route-1`,
111 },
112 {
113 err: errors.New("failed to validate ServiceProfile: error unmarshaling JSON: while decoding JSON: json: unknown field \"foo\""),
114 sp: `apiVersion: linkerd.io/v1alpha2
115 kind: ServiceProfile
116 metadata:
117 name: name.ns.svc.cluster.local
118 namespace: linkerd-ns
119 spec:
120 routes:
121 - name: name-1
122 condition:
123 foo: bar
124 method: GET
125 pathRegex: /route-1`,
126 },
127 {
128 err: errors.New("ServiceProfile \"name.ns.svc.cluster.local\" has a route with no condition"),
129 sp: `apiVersion: linkerd.io/v1alpha2
130 kind: ServiceProfile
131 metadata:
132 name: name.ns.svc.cluster.local
133 namespace: linkerd-ns
134 spec:
135 routes:
136 - name: name-1`,
137 },
138 {
139 err: errors.New("ServiceProfile \"name.ns.svc.cluster.local\" has a route with no name"),
140 sp: `apiVersion: linkerd.io/v1alpha2
141 kind: ServiceProfile
142 metadata:
143 name: name.ns.svc.cluster.local
144 namespace: linkerd-ns
145 spec:
146 routes:
147 - condition:
148 method: GET
149 pathRegex: /route-1`,
150 },
151 {
152 err: errors.New("failed to validate ServiceProfile: error unmarshaling JSON: while decoding JSON: json: unknown field \"foo\""),
153 sp: `apiVersion: linkerd.io/v1alpha2
154 kind: ServiceProfile
155 metadata:
156 name: name.ns.svc.cluster.local
157 namespace: linkerd-ns
158 spec:
159 routes:
160 - name: name-1
161 condition:
162 foo: bar
163 method: GET
164 pathRegex: /route-1
165 not:
166 method: GET`,
167 },
168 {
169 err: errors.New("ServiceProfile \"name.ns.svc.cluster.local\" has a route with no condition"),
170 sp: `apiVersion: linkerd.io/v1alpha2
171 kind: ServiceProfile
172 metadata:
173 name: name.ns.svc.cluster.local
174 namespace: linkerd-ns
175 spec:
176 routes:
177 - name: name-1
178 condition:`,
179 },
180 {
181 err: errors.New("ServiceProfile \"name.ns.svc.cluster.local\" has a route with an invalid condition: A request match must have a field set"),
182 sp: `apiVersion: linkerd.io/v1alpha2
183 kind: ServiceProfile
184 metadata:
185 name: name.ns.svc.cluster.local
186 namespace: linkerd-ns
187 spec:
188 routes:
189 - name: name-1
190 condition:
191 method:`,
192 },
193 {
194 err: errors.New("ServiceProfile \"name.ns.svc.cluster.local\" has a response class with no condition"),
195 sp: `apiVersion: linkerd.io/v1alpha2
196 kind: ServiceProfile
197 metadata:
198 name: name.ns.svc.cluster.local
199 namespace: linkerd-ns
200 spec:
201 routes:
202 - name: name-1
203 condition:
204 method: GET
205 pathRegex: /route-1
206 responseClasses:
207 - condition:`,
208 },
209 {
210 err: errors.New("ServiceProfile \"name.ns.svc.cluster.local\" has a response class with an invalid condition: A response match must have a field set"),
211 sp: `apiVersion: linkerd.io/v1alpha2
212 kind: ServiceProfile
213 metadata:
214 name: name.ns.svc.cluster.local
215 namespace: linkerd-ns
216 spec:
217 routes:
218 - name: name-1
219 condition:
220 method: GET
221 pathRegex: /route-1
222 responseClasses:
223 - condition:
224 status:
225 min: 500
226 max: 599
227 all:
228 - status:`,
229 },
230 {
231 err: errors.New("ServiceProfile \"name.ns.svc.cluster.local\" has a response class with an invalid condition: Range maximum must be between 100 and 599, inclusive"),
232 sp: `apiVersion: linkerd.io/v1alpha2
233 kind: ServiceProfile
234 metadata:
235 name: name.ns.svc.cluster.local
236 namespace: linkerd-ns
237 spec:
238 routes:
239 - name: name-1
240 condition:
241 method: GET
242 pathRegex: /route-1
243 responseClasses:
244 - condition:
245 status:
246 min: 500
247 max: 600`,
248 },
249 {
250 err: errors.New("ServiceProfile \"name.ns.svc.cluster.local\" has a response class with an invalid condition: Range maximum cannot be smaller than minimum"),
251 sp: `apiVersion: linkerd.io/v1alpha2
252 kind: ServiceProfile
253 metadata:
254 name: name.ns.svc.cluster.local
255 namespace: linkerd-ns
256 spec:
257 routes:
258 - name: name-1
259 condition:
260 method: GET
261 pathRegex: /route-1
262 responseClasses:
263 - condition:
264 status:
265 min: 500
266 max: 599
267 all:
268 - status:
269 min: 500
270 max: 599
271 - not:
272 status:
273 min: 300
274 max: 200`,
275 },
276 {
277 err: errors.New("ServiceProfile \"name.ns.svc.cluster.local\" has a response class with an invalid condition: Range minimum must be between 100 and 599, inclusive"),
278 sp: `apiVersion: linkerd.io/v1alpha2
279 kind: ServiceProfile
280 metadata:
281 name: name.ns.svc.cluster.local
282 namespace: linkerd-ns
283 spec:
284 routes:
285 - name: name-1
286 condition:
287 method: GET
288 pathRegex: /route-1
289 responseClasses:
290 - condition:
291 status:
292 min: 500
293 max: 599
294 all:
295 - status:
296 min: 500
297 max: 599
298 - not:
299 status:
300 min: 1`,
301 },
302 {
303 err: errors.New("failed to validate ServiceProfile: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal bool into Go struct field Range.spec.routes.responseClasses.condition.all.not.status.min of type uint32"),
304 sp: `apiVersion: linkerd.io/v1alpha2
305 kind: ServiceProfile
306 metadata:
307 name: name.ns.svc.cluster.local
308 namespace: linkerd-ns
309 spec:
310 routes:
311 - name: name-1
312 condition:
313 method: GET
314 pathRegex: /route-1
315 responseClasses:
316 - condition:
317 status:
318 min: 500
319 max: 599
320 all:
321 - status:
322 min: 500
323 max: 599
324 - not:
325 status:
326 min: false`,
327 },
328 {
329 err: errors.New("ServiceProfile \"name.ns.svc.cluster.local\" RetryBudget missing TTL field"),
330 sp: `apiVersion: linkerd.io/v1alpha2
331 kind: ServiceProfile
332 metadata:
333 name: name.ns.svc.cluster.local
334 namespace: linkerd-ns
335 spec:
336 retryBudget:
337 minRetriesPerSecond: 5
338 retryRatio: 0.2
339 routes:
340 - name: name-1
341 condition:
342 method: GET
343 pathRegex: /route-1`,
344 },
345 {
346 err: errors.New("ServiceProfile \"name.ns.svc.cluster.local\" RetryBudget: time: invalid duration \"foo\""),
347 sp: `apiVersion: linkerd.io/v1alpha2
348 kind: ServiceProfile
349 metadata:
350 name: name.ns.svc.cluster.local
351 namespace: linkerd-ns
352 spec:
353 retryBudget:
354 minRetriesPerSecond: 5
355 retryRatio: 0.2
356 ttl: foo
357 routes:
358 - name: name-1
359 condition:
360 method: GET
361 pathRegex: /route-1`,
362 },
363 {
364 err: errors.New("ServiceProfile \"name.ns.svc.cluster.local\" RetryBudget RetryRatio must be non-negative: -0.200000"),
365 sp: `apiVersion: linkerd.io/v1alpha2
366 kind: ServiceProfile
367 metadata:
368 name: name.ns.svc.cluster.local
369 namespace: linkerd-ns
370 spec:
371 retryBudget:
372 minRetriesPerSecond: 5
373 retryRatio: -0.2
374 ttl: 10s
375 routes:
376 - name: name-1
377 condition:
378 method: GET
379 pathRegex: /route-1`,
380 },
381 {
382 err: errors.New("failed to validate ServiceProfile: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal number -5 into Go struct field RetryBudget.spec.retryBudget.minRetriesPerSecond of type uint32"),
383 sp: `apiVersion: linkerd.io/v1alpha2
384 kind: ServiceProfile
385 metadata:
386 name: name.ns.svc.cluster.local
387 namespace: linkerd-ns
388 spec:
389 retryBudget:
390 minRetriesPerSecond: -5
391 retryRatio: 0.2
392 ttl: 10s
393 routes:
394 - name: name-1
395 condition:
396 method: GET
397 pathRegex: /route-1`,
398 },
399 }
400
401 for id, exp := range expectations {
402 exp := exp
403 t.Run(fmt.Sprintf("%d", id), func(t *testing.T) {
404 err := Validate([]byte(exp.sp))
405 if err != nil || exp.err != nil {
406 if (err == nil && exp.err != nil) ||
407 (err != nil && exp.err == nil) ||
408 (err.Error() != exp.err.Error()) {
409 t.Fatalf("Unexpected error (Expected: %s, Got: %s)", exp.err, err)
410 }
411 }
412 })
413 }
414 }
415
View as plain text