1
16
17 package tolerations
18
19 import (
20 "encoding/json"
21 "fmt"
22 "math/rand"
23 "strings"
24 "testing"
25
26 "github.com/stretchr/testify/assert"
27 "github.com/stretchr/testify/require"
28 "k8s.io/apimachinery/pkg/util/validation/field"
29 api "k8s.io/kubernetes/pkg/apis/core"
30 "k8s.io/kubernetes/pkg/apis/core/validation"
31 utilpointer "k8s.io/utils/pointer"
32 )
33
34 var (
35 tolerations = map[string]api.Toleration{
36 "all": {Operator: api.TolerationOpExists},
37 "all-nosched": {
38 Operator: api.TolerationOpExists,
39 Effect: api.TaintEffectNoSchedule,
40 },
41 "all-noexec": {
42 Operator: api.TolerationOpExists,
43 Effect: api.TaintEffectNoExecute,
44 },
45 "foo": {
46 Key: "foo",
47 Operator: api.TolerationOpExists,
48 },
49 "foo-bar": {
50 Key: "foo",
51 Operator: api.TolerationOpEqual,
52 Value: "bar",
53 },
54 "foo-nosched": {
55 Key: "foo",
56 Operator: api.TolerationOpExists,
57 Effect: api.TaintEffectNoSchedule,
58 },
59 "foo-bar-nosched": {
60 Key: "foo",
61 Operator: api.TolerationOpEqual,
62 Value: "bar",
63 Effect: api.TaintEffectNoSchedule,
64 },
65 "foo-baz-nosched": {
66 Key: "foo",
67 Operator: api.TolerationOpEqual,
68 Value: "baz",
69 Effect: api.TaintEffectNoSchedule,
70 },
71 "faz-nosched": {
72 Key: "faz",
73 Operator: api.TolerationOpExists,
74 Effect: api.TaintEffectNoSchedule,
75 },
76 "faz-baz-nosched": {
77 Key: "faz",
78 Operator: api.TolerationOpEqual,
79 Value: "baz",
80 Effect: api.TaintEffectNoSchedule,
81 },
82 "foo-prefnosched": {
83 Key: "foo",
84 Operator: api.TolerationOpExists,
85 Effect: api.TaintEffectPreferNoSchedule,
86 },
87 "foo-noexec": {
88 Key: "foo",
89 Operator: api.TolerationOpExists,
90 Effect: api.TaintEffectNoExecute,
91 },
92 "foo-bar-noexec": {
93 Key: "foo",
94 Operator: api.TolerationOpEqual,
95 Value: "bar",
96 Effect: api.TaintEffectNoExecute,
97 },
98 "foo-noexec-10": {
99 Key: "foo",
100 Operator: api.TolerationOpExists,
101 Effect: api.TaintEffectNoExecute,
102 TolerationSeconds: utilpointer.Int64Ptr(10),
103 },
104 "foo-noexec-0": {
105 Key: "foo",
106 Operator: api.TolerationOpExists,
107 Effect: api.TaintEffectNoExecute,
108 TolerationSeconds: utilpointer.Int64Ptr(0),
109 },
110 "foo-bar-noexec-10": {
111 Key: "foo",
112 Operator: api.TolerationOpEqual,
113 Value: "bar",
114 Effect: api.TaintEffectNoExecute,
115 TolerationSeconds: utilpointer.Int64Ptr(10),
116 },
117 }
118 )
119
120 func TestIsSuperset(t *testing.T) {
121 tests := []struct {
122 toleration string
123 ss []string
124 }{{
125 "all",
126 []string{"all-nosched", "all-noexec", "foo", "foo-bar", "foo-nosched", "foo-bar-nosched", "foo-baz-nosched", "faz-nosched", "faz-baz-nosched", "foo-prefnosched", "foo-noexec", "foo-bar-noexec", "foo-noexec-10", "foo-noexec-0", "foo-bar-noexec-10"},
127 }, {
128 "all-nosched",
129 []string{"foo-nosched", "foo-bar-nosched", "foo-baz-nosched", "faz-nosched", "faz-baz-nosched"},
130 }, {
131 "all-noexec",
132 []string{"foo-noexec", "foo-bar-noexec", "foo-noexec-10", "foo-noexec-0", "foo-bar-noexec-10"},
133 }, {
134 "foo",
135 []string{"foo-bar", "foo-nosched", "foo-bar-nosched", "foo-baz-nosched", "foo-prefnosched", "foo-noexec", "foo-bar-noexec", "foo-noexec-10", "foo-noexec-0", "foo-bar-noexec-10"},
136 }, {
137 "foo-bar",
138 []string{"foo-bar-nosched", "foo-bar-noexec", "foo-bar-noexec-10"},
139 }, {
140 "foo-nosched",
141 []string{"foo-bar-nosched", "foo-baz-nosched"},
142 }, {
143 "foo-bar-nosched",
144 []string{},
145 }, {
146 "faz-nosched",
147 []string{"faz-baz-nosched"},
148 }, {
149 "faz-baz-nosched",
150 []string{},
151 }, {
152 "foo-prenosched",
153 []string{},
154 }, {
155 "foo-noexec",
156 []string{"foo-noexec", "foo-bar-noexec", "foo-noexec-10", "foo-noexec-0", "foo-bar-noexec-10"},
157 }, {
158 "foo-bar-noexec",
159 []string{"foo-bar-noexec-10"},
160 }, {
161 "foo-noexec-10",
162 []string{"foo-noexec-0", "foo-bar-noexec-10"},
163 }, {
164 "foo-noexec-0",
165 []string{},
166 }, {
167 "foo-bar-noexec-10",
168 []string{},
169 }}
170
171 assertSuperset := func(t *testing.T, super, sub string) {
172 assert.True(t, isSuperset(tolerations[super], tolerations[sub]),
173 "%s should be a superset of %s", super, sub)
174 }
175 assertNotSuperset := func(t *testing.T, super, sub string) {
176 assert.False(t, isSuperset(tolerations[super], tolerations[sub]),
177 "%s should NOT be a superset of %s", super, sub)
178 }
179 contains := func(ss []string, s string) bool {
180 for _, str := range ss {
181 if str == s {
182 return true
183 }
184 }
185 return false
186 }
187
188 for _, test := range tests {
189 t.Run(test.toleration, func(t *testing.T) {
190 for name := range tolerations {
191 if name == test.toleration || contains(test.ss, name) {
192 assertSuperset(t, test.toleration, name)
193 } else {
194 assertNotSuperset(t, test.toleration, name)
195 }
196 }
197 })
198 }
199 }
200
201 func TestVerifyAgainstWhitelist(t *testing.T) {
202 tests := []struct {
203 testName string
204 input []string
205 whitelist []string
206 expected bool
207 }{
208 {
209 testName: "equal input and whitelist",
210 input: []string{"foo-bar-nosched", "foo-baz-nosched"},
211 whitelist: []string{"foo-bar-nosched", "foo-baz-nosched"},
212 expected: true,
213 },
214 {
215 testName: "duplicate input allowed",
216 input: []string{"foo-bar-nosched", "foo-bar-nosched"},
217 whitelist: []string{"foo-bar-nosched", "foo-baz-nosched"},
218 expected: true,
219 },
220 {
221 testName: "allow all",
222 input: []string{"foo-bar-nosched", "foo-bar-nosched"},
223 whitelist: []string{"all"},
224 expected: true,
225 },
226 {
227 testName: "duplicate input forbidden",
228 input: []string{"foo-bar-nosched", "foo-bar-nosched"},
229 whitelist: []string{"foo-baz-nosched"},
230 expected: false,
231 },
232 {
233 testName: "value mismatch",
234 input: []string{"foo-bar-nosched", "foo-baz-nosched"},
235 whitelist: []string{"foo-baz-nosched"},
236 expected: false,
237 },
238 {
239 testName: "input does not exist in whitelist",
240 input: []string{"foo-bar-nosched"},
241 whitelist: []string{"foo-baz-nosched"},
242 expected: false,
243 },
244 {
245 testName: "disjoint sets",
246 input: []string{"foo-bar"},
247 whitelist: []string{"foo-nosched"},
248 expected: false,
249 },
250 {
251 testName: "empty whitelist",
252 input: []string{"foo-bar"},
253 whitelist: []string{},
254 expected: true,
255 },
256 {
257 testName: "empty input",
258 input: []string{},
259 whitelist: []string{"foo-bar"},
260 expected: true,
261 },
262 }
263
264 for _, c := range tests {
265 t.Run(c.testName, func(t *testing.T) {
266 actual := VerifyAgainstWhitelist(getTolerations(c.input), getTolerations(c.whitelist))
267 assert.Equal(t, c.expected, actual)
268 })
269 }
270 }
271
272 func TestMergeTolerations(t *testing.T) {
273 tests := []struct {
274 name string
275 a, b []string
276 expected []string
277 }{{
278 name: "disjoint",
279 a: []string{"foo-bar-nosched", "faz-baz-nosched", "foo-noexec-10"},
280 b: []string{"foo-prefnosched", "foo-baz-nosched"},
281 expected: []string{"foo-bar-nosched", "faz-baz-nosched", "foo-noexec-10", "foo-prefnosched", "foo-baz-nosched"},
282 }, {
283 name: "duplicate",
284 a: []string{"foo-bar-nosched", "faz-baz-nosched", "foo-noexec-10"},
285 b: []string{"foo-bar-nosched", "faz-baz-nosched", "foo-noexec-10"},
286 expected: []string{"foo-bar-nosched", "faz-baz-nosched", "foo-noexec-10"},
287 }, {
288 name: "merge redundant",
289 a: []string{"foo-bar-nosched", "foo-baz-nosched"},
290 b: []string{"foo-nosched", "faz-baz-nosched"},
291 expected: []string{"foo-nosched", "faz-baz-nosched"},
292 }, {
293 name: "merge all",
294 a: []string{"foo-bar-nosched", "foo-baz-nosched", "foo-noexec-10"},
295 b: []string{"all"},
296 expected: []string{"all"},
297 }, {
298 name: "merge into all",
299 a: []string{"all"},
300 b: []string{"foo-bar-nosched", "foo-baz-nosched", "foo-noexec-10"},
301 expected: []string{"all"},
302 }}
303
304 for _, test := range tests {
305 t.Run(test.name, func(t *testing.T) {
306 actual := MergeTolerations(getTolerations(test.a), getTolerations(test.b))
307 require.Len(t, actual, len(test.expected))
308 for i, expect := range getTolerations(test.expected) {
309 assert.Equal(t, expect, actual[i], "expected[%d] = %s", i, test.expected[i])
310 }
311 })
312 }
313 }
314
315 func TestFuzzed(t *testing.T) {
316 r := rand.New(rand.NewSource(1234))
317
318 const (
319 allProbability = 0.01
320 existsProbability = 0.3
321 tolerationSecondsProbability = 0.5
322 )
323 effects := []api.TaintEffect{"", api.TaintEffectNoExecute, api.TaintEffectNoSchedule, api.TaintEffectPreferNoSchedule}
324 genToleration := func() api.Toleration {
325 gen := api.Toleration{
326 Effect: effects[r.Intn(len(effects))],
327 }
328 if r.Float32() < allProbability {
329 gen = tolerations["all"]
330 return gen
331 }
332
333 gen.Key = strings.Repeat("a", r.Intn(6)+1)
334 if r.Float32() < existsProbability {
335 gen.Operator = api.TolerationOpExists
336 } else {
337 gen.Operator = api.TolerationOpEqual
338 gen.Value = strings.Repeat("b", r.Intn(6)+1)
339 }
340 if gen.Effect == api.TaintEffectNoExecute && r.Float32() < tolerationSecondsProbability {
341 gen.TolerationSeconds = utilpointer.Int64Ptr(r.Int63n(10))
342 }
343
344 require.NoError(t, validation.ValidateTolerations([]api.Toleration{gen}, field.NewPath("")).ToAggregate(), "%#v", gen)
345 return gen
346 }
347 genTolerations := func() []api.Toleration {
348 result := []api.Toleration{}
349 for i := 0; i < r.Intn(10); i++ {
350 result = append(result, genToleration())
351 }
352 return result
353 }
354
355
356 isContained := func(toleration api.Toleration, set []api.Toleration) bool {
357 for _, ss := range set {
358 if isSuperset(ss, toleration) {
359 return true
360 }
361 }
362 return false
363 }
364
365 const iterations = 1000
366
367 debugMsg := func(tolerations ...[]api.Toleration) string {
368 str, err := json.Marshal(tolerations)
369 if err != nil {
370 return fmt.Sprintf("[ERR: %v] %v", err, tolerations)
371 }
372 return string(str)
373 }
374 t.Run("VerifyAgainstWhitelist", func(t *testing.T) {
375 for i := 0; i < iterations; i++ {
376 input := genTolerations()
377 whitelist := append(genTolerations(), genToleration())
378 if VerifyAgainstWhitelist(input, whitelist) {
379 for _, tol := range input {
380 require.True(t, isContained(tol, whitelist), debugMsg(input, whitelist))
381 }
382 } else {
383 uncontained := false
384 for _, tol := range input {
385 if !isContained(tol, whitelist) {
386 uncontained = true
387 break
388 }
389 }
390 require.True(t, uncontained, debugMsg(input, whitelist))
391 }
392 }
393 })
394
395 t.Run("MergeTolerations", func(t *testing.T) {
396 for i := 0; i < iterations; i++ {
397 a := genTolerations()
398 b := genTolerations()
399 result := MergeTolerations(a, b)
400 for _, tol := range append(a, b...) {
401 require.True(t, isContained(tol, result), debugMsg(a, b, result))
402 }
403 }
404 })
405 }
406
407 func getTolerations(names []string) []api.Toleration {
408 result := []api.Toleration{}
409 for _, name := range names {
410 result = append(result, tolerations[name])
411 }
412 return result
413 }
414
View as plain text