tolerations_test.go 11 KB

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