taint_toleration_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. /*
  2. Copyright 2019 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 tainttoleration
  14. import (
  15. "context"
  16. "reflect"
  17. "testing"
  18. v1 "k8s.io/api/core/v1"
  19. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  20. framework "k8s.io/kubernetes/pkg/scheduler/framework/v1alpha1"
  21. "k8s.io/kubernetes/pkg/scheduler/internal/cache"
  22. schedulernodeinfo "k8s.io/kubernetes/pkg/scheduler/nodeinfo"
  23. )
  24. func nodeWithTaints(nodeName string, taints []v1.Taint) *v1.Node {
  25. return &v1.Node{
  26. ObjectMeta: metav1.ObjectMeta{
  27. Name: nodeName,
  28. },
  29. Spec: v1.NodeSpec{
  30. Taints: taints,
  31. },
  32. }
  33. }
  34. func podWithTolerations(podName string, tolerations []v1.Toleration) *v1.Pod {
  35. return &v1.Pod{
  36. ObjectMeta: metav1.ObjectMeta{
  37. Name: podName,
  38. },
  39. Spec: v1.PodSpec{
  40. Tolerations: tolerations,
  41. },
  42. }
  43. }
  44. func TestTaintTolerationScore(t *testing.T) {
  45. tests := []struct {
  46. name string
  47. pod *v1.Pod
  48. nodes []*v1.Node
  49. expectedList framework.NodeScoreList
  50. }{
  51. // basic test case
  52. {
  53. name: "node with taints tolerated by the pod, gets a higher score than those node with intolerable taints",
  54. pod: podWithTolerations("pod1", []v1.Toleration{{
  55. Key: "foo",
  56. Operator: v1.TolerationOpEqual,
  57. Value: "bar",
  58. Effect: v1.TaintEffectPreferNoSchedule,
  59. }}),
  60. nodes: []*v1.Node{
  61. nodeWithTaints("nodeA", []v1.Taint{{
  62. Key: "foo",
  63. Value: "bar",
  64. Effect: v1.TaintEffectPreferNoSchedule,
  65. }}),
  66. nodeWithTaints("nodeB", []v1.Taint{{
  67. Key: "foo",
  68. Value: "blah",
  69. Effect: v1.TaintEffectPreferNoSchedule,
  70. }}),
  71. },
  72. expectedList: []framework.NodeScore{
  73. {Name: "nodeA", Score: framework.MaxNodeScore},
  74. {Name: "nodeB", Score: 0},
  75. },
  76. },
  77. // the count of taints that are tolerated by pod, does not matter.
  78. {
  79. name: "the nodes that all of their taints are tolerated by the pod, get the same score, no matter how many tolerable taints a node has",
  80. pod: podWithTolerations("pod1", []v1.Toleration{
  81. {
  82. Key: "cpu-type",
  83. Operator: v1.TolerationOpEqual,
  84. Value: "arm64",
  85. Effect: v1.TaintEffectPreferNoSchedule,
  86. }, {
  87. Key: "disk-type",
  88. Operator: v1.TolerationOpEqual,
  89. Value: "ssd",
  90. Effect: v1.TaintEffectPreferNoSchedule,
  91. },
  92. }),
  93. nodes: []*v1.Node{
  94. nodeWithTaints("nodeA", []v1.Taint{}),
  95. nodeWithTaints("nodeB", []v1.Taint{
  96. {
  97. Key: "cpu-type",
  98. Value: "arm64",
  99. Effect: v1.TaintEffectPreferNoSchedule,
  100. },
  101. }),
  102. nodeWithTaints("nodeC", []v1.Taint{
  103. {
  104. Key: "cpu-type",
  105. Value: "arm64",
  106. Effect: v1.TaintEffectPreferNoSchedule,
  107. }, {
  108. Key: "disk-type",
  109. Value: "ssd",
  110. Effect: v1.TaintEffectPreferNoSchedule,
  111. },
  112. }),
  113. },
  114. expectedList: []framework.NodeScore{
  115. {Name: "nodeA", Score: framework.MaxNodeScore},
  116. {Name: "nodeB", Score: framework.MaxNodeScore},
  117. {Name: "nodeC", Score: framework.MaxNodeScore},
  118. },
  119. },
  120. // the count of taints on a node that are not tolerated by pod, matters.
  121. {
  122. name: "the more intolerable taints a node has, the lower score it gets.",
  123. pod: podWithTolerations("pod1", []v1.Toleration{{
  124. Key: "foo",
  125. Operator: v1.TolerationOpEqual,
  126. Value: "bar",
  127. Effect: v1.TaintEffectPreferNoSchedule,
  128. }}),
  129. nodes: []*v1.Node{
  130. nodeWithTaints("nodeA", []v1.Taint{}),
  131. nodeWithTaints("nodeB", []v1.Taint{
  132. {
  133. Key: "cpu-type",
  134. Value: "arm64",
  135. Effect: v1.TaintEffectPreferNoSchedule,
  136. },
  137. }),
  138. nodeWithTaints("nodeC", []v1.Taint{
  139. {
  140. Key: "cpu-type",
  141. Value: "arm64",
  142. Effect: v1.TaintEffectPreferNoSchedule,
  143. }, {
  144. Key: "disk-type",
  145. Value: "ssd",
  146. Effect: v1.TaintEffectPreferNoSchedule,
  147. },
  148. }),
  149. },
  150. expectedList: []framework.NodeScore{
  151. {Name: "nodeA", Score: framework.MaxNodeScore},
  152. {Name: "nodeB", Score: 50},
  153. {Name: "nodeC", Score: 0},
  154. },
  155. },
  156. // taints-tolerations priority only takes care about the taints and tolerations that have effect PreferNoSchedule
  157. {
  158. name: "only taints and tolerations that have effect PreferNoSchedule are checked by taints-tolerations priority function",
  159. pod: podWithTolerations("pod1", []v1.Toleration{
  160. {
  161. Key: "cpu-type",
  162. Operator: v1.TolerationOpEqual,
  163. Value: "arm64",
  164. Effect: v1.TaintEffectNoSchedule,
  165. }, {
  166. Key: "disk-type",
  167. Operator: v1.TolerationOpEqual,
  168. Value: "ssd",
  169. Effect: v1.TaintEffectNoSchedule,
  170. },
  171. }),
  172. nodes: []*v1.Node{
  173. nodeWithTaints("nodeA", []v1.Taint{}),
  174. nodeWithTaints("nodeB", []v1.Taint{
  175. {
  176. Key: "cpu-type",
  177. Value: "arm64",
  178. Effect: v1.TaintEffectNoSchedule,
  179. },
  180. }),
  181. nodeWithTaints("nodeC", []v1.Taint{
  182. {
  183. Key: "cpu-type",
  184. Value: "arm64",
  185. Effect: v1.TaintEffectPreferNoSchedule,
  186. }, {
  187. Key: "disk-type",
  188. Value: "ssd",
  189. Effect: v1.TaintEffectPreferNoSchedule,
  190. },
  191. }),
  192. },
  193. expectedList: []framework.NodeScore{
  194. {Name: "nodeA", Score: framework.MaxNodeScore},
  195. {Name: "nodeB", Score: framework.MaxNodeScore},
  196. {Name: "nodeC", Score: 0},
  197. },
  198. },
  199. {
  200. name: "Default behaviour No taints and tolerations, lands on node with no taints",
  201. //pod without tolerations
  202. pod: podWithTolerations("pod1", []v1.Toleration{}),
  203. nodes: []*v1.Node{
  204. //Node without taints
  205. nodeWithTaints("nodeA", []v1.Taint{}),
  206. nodeWithTaints("nodeB", []v1.Taint{
  207. {
  208. Key: "cpu-type",
  209. Value: "arm64",
  210. Effect: v1.TaintEffectPreferNoSchedule,
  211. },
  212. }),
  213. },
  214. expectedList: []framework.NodeScore{
  215. {Name: "nodeA", Score: framework.MaxNodeScore},
  216. {Name: "nodeB", Score: 0},
  217. },
  218. },
  219. }
  220. for _, test := range tests {
  221. t.Run(test.name, func(t *testing.T) {
  222. state := framework.NewCycleState()
  223. snapshot := cache.NewSnapshot(nil, test.nodes)
  224. fh, _ := framework.NewFramework(nil, nil, nil, framework.WithSnapshotSharedLister(snapshot))
  225. p, _ := New(nil, fh)
  226. status := p.(framework.PreScorePlugin).PreScore(context.Background(), state, test.pod, test.nodes)
  227. if !status.IsSuccess() {
  228. t.Errorf("unexpected error: %v", status)
  229. }
  230. var gotList framework.NodeScoreList
  231. for _, n := range test.nodes {
  232. nodeName := n.ObjectMeta.Name
  233. score, status := p.(framework.ScorePlugin).Score(context.Background(), state, test.pod, nodeName)
  234. if !status.IsSuccess() {
  235. t.Errorf("unexpected error: %v", status)
  236. }
  237. gotList = append(gotList, framework.NodeScore{Name: nodeName, Score: score})
  238. }
  239. status = p.(framework.ScorePlugin).ScoreExtensions().NormalizeScore(context.Background(), state, test.pod, gotList)
  240. if !status.IsSuccess() {
  241. t.Errorf("unexpected error: %v", status)
  242. }
  243. if !reflect.DeepEqual(test.expectedList, gotList) {
  244. t.Errorf("expected:\n\t%+v,\ngot:\n\t%+v", test.expectedList, gotList)
  245. }
  246. })
  247. }
  248. }
  249. func TestTaintTolerationFilter(t *testing.T) {
  250. tests := []struct {
  251. name string
  252. pod *v1.Pod
  253. node *v1.Node
  254. wantStatus *framework.Status
  255. }{
  256. {
  257. name: "A pod having no tolerations can't be scheduled onto a node with nonempty taints",
  258. pod: podWithTolerations("pod1", []v1.Toleration{}),
  259. node: nodeWithTaints("nodeA", []v1.Taint{{Key: "dedicated", Value: "user1", Effect: "NoSchedule"}}),
  260. wantStatus: framework.NewStatus(framework.UnschedulableAndUnresolvable,
  261. "node(s) had taint {dedicated: user1}, that the pod didn't tolerate"),
  262. },
  263. {
  264. name: "A pod which can be scheduled on a dedicated node assigned to user1 with effect NoSchedule",
  265. pod: podWithTolerations("pod1", []v1.Toleration{{Key: "dedicated", Value: "user1", Effect: "NoSchedule"}}),
  266. node: nodeWithTaints("nodeA", []v1.Taint{{Key: "dedicated", Value: "user1", Effect: "NoSchedule"}}),
  267. },
  268. {
  269. name: "A pod which can't be scheduled on a dedicated node assigned to user2 with effect NoSchedule",
  270. pod: podWithTolerations("pod1", []v1.Toleration{{Key: "dedicated", Operator: "Equal", Value: "user2", Effect: "NoSchedule"}}),
  271. node: nodeWithTaints("nodeA", []v1.Taint{{Key: "dedicated", Value: "user1", Effect: "NoSchedule"}}),
  272. wantStatus: framework.NewStatus(framework.UnschedulableAndUnresolvable,
  273. "node(s) had taint {dedicated: user1}, that the pod didn't tolerate"),
  274. },
  275. {
  276. name: "A pod can be scheduled onto the node, with a toleration uses operator Exists that tolerates the taints on the node",
  277. pod: podWithTolerations("pod1", []v1.Toleration{{Key: "foo", Operator: "Exists", Effect: "NoSchedule"}}),
  278. node: nodeWithTaints("nodeA", []v1.Taint{{Key: "foo", Value: "bar", Effect: "NoSchedule"}}),
  279. },
  280. {
  281. name: "A pod has multiple tolerations, node has multiple taints, all the taints are tolerated, pod can be scheduled onto the node",
  282. pod: podWithTolerations("pod1", []v1.Toleration{
  283. {Key: "dedicated", Operator: "Equal", Value: "user2", Effect: "NoSchedule"},
  284. {Key: "foo", Operator: "Exists", Effect: "NoSchedule"},
  285. }),
  286. node: nodeWithTaints("nodeA", []v1.Taint{
  287. {Key: "dedicated", Value: "user2", Effect: "NoSchedule"},
  288. {Key: "foo", Value: "bar", Effect: "NoSchedule"},
  289. }),
  290. },
  291. {
  292. name: "A pod has a toleration that keys and values match the taint on the node, but (non-empty) effect doesn't match, " +
  293. "can't be scheduled onto the node",
  294. pod: podWithTolerations("pod1", []v1.Toleration{{Key: "foo", Operator: "Equal", Value: "bar", Effect: "PreferNoSchedule"}}),
  295. node: nodeWithTaints("nodeA", []v1.Taint{{Key: "foo", Value: "bar", Effect: "NoSchedule"}}),
  296. wantStatus: framework.NewStatus(framework.UnschedulableAndUnresolvable,
  297. "node(s) had taint {foo: bar}, that the pod didn't tolerate"),
  298. },
  299. {
  300. name: "The pod has a toleration that keys and values match the taint on the node, the effect of toleration is empty, " +
  301. "and the effect of taint is NoSchedule. Pod can be scheduled onto the node",
  302. pod: podWithTolerations("pod1", []v1.Toleration{{Key: "foo", Operator: "Equal", Value: "bar"}}),
  303. node: nodeWithTaints("nodeA", []v1.Taint{{Key: "foo", Value: "bar", Effect: "NoSchedule"}}),
  304. },
  305. {
  306. name: "The pod has a toleration that key and value don't match the taint on the node, " +
  307. "but the effect of taint on node is PreferNoSchedule. Pod can be scheduled onto the node",
  308. pod: podWithTolerations("pod1", []v1.Toleration{{Key: "dedicated", Operator: "Equal", Value: "user2", Effect: "NoSchedule"}}),
  309. node: nodeWithTaints("nodeA", []v1.Taint{{Key: "dedicated", Value: "user1", Effect: "PreferNoSchedule"}}),
  310. },
  311. {
  312. name: "The pod has no toleration, " +
  313. "but the effect of taint on node is PreferNoSchedule. Pod can be scheduled onto the node",
  314. pod: podWithTolerations("pod1", []v1.Toleration{}),
  315. node: nodeWithTaints("nodeA", []v1.Taint{{Key: "dedicated", Value: "user1", Effect: "PreferNoSchedule"}}),
  316. },
  317. }
  318. for _, test := range tests {
  319. t.Run(test.name, func(t *testing.T) {
  320. nodeInfo := schedulernodeinfo.NewNodeInfo()
  321. nodeInfo.SetNode(test.node)
  322. p, _ := New(nil, nil)
  323. gotStatus := p.(framework.FilterPlugin).Filter(context.Background(), nil, test.pod, nodeInfo)
  324. if !reflect.DeepEqual(gotStatus, test.wantStatus) {
  325. t.Errorf("status does not match: %v, want: %v", gotStatus, test.wantStatus)
  326. }
  327. })
  328. }
  329. }