predicates_test.go 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038
  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 scheduler
  14. import (
  15. "context"
  16. "fmt"
  17. "testing"
  18. "time"
  19. v1 "k8s.io/api/core/v1"
  20. apierrors "k8s.io/apimachinery/pkg/api/errors"
  21. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  22. "k8s.io/apimachinery/pkg/util/wait"
  23. utilfeature "k8s.io/apiserver/pkg/util/feature"
  24. featuregatetesting "k8s.io/component-base/featuregate/testing"
  25. "k8s.io/kubernetes/pkg/features"
  26. st "k8s.io/kubernetes/pkg/scheduler/testing"
  27. testutils "k8s.io/kubernetes/test/utils"
  28. imageutils "k8s.io/kubernetes/test/utils/image"
  29. )
  30. // This file tests the scheduler predicates functionality.
  31. const pollInterval = 100 * time.Millisecond
  32. // TestInterPodAffinity verifies that scheduler's inter pod affinity and
  33. // anti-affinity predicate functions works correctly.
  34. func TestInterPodAffinity(t *testing.T) {
  35. testCtx := initTest(t, "inter-pod-affinity")
  36. defer cleanupTest(t, testCtx)
  37. // Add a few nodes.
  38. nodes, err := createNodes(testCtx.clientSet, "testnode", nil, 2)
  39. if err != nil {
  40. t.Fatalf("Cannot create nodes: %v", err)
  41. }
  42. // Add labels to the nodes.
  43. labels1 := map[string]string{
  44. "region": "r1",
  45. "zone": "z11",
  46. }
  47. for _, node := range nodes {
  48. if err = testutils.AddLabelsToNode(testCtx.clientSet, node.Name, labels1); err != nil {
  49. t.Fatalf("Cannot add labels to node: %v", err)
  50. }
  51. if err = waitForNodeLabels(testCtx.clientSet, node.Name, labels1); err != nil {
  52. t.Fatalf("Adding labels to node didn't succeed: %v", err)
  53. }
  54. }
  55. cs := testCtx.clientSet
  56. podLabel := map[string]string{"service": "securityscan"}
  57. podLabel2 := map[string]string{"security": "S1"}
  58. tests := []struct {
  59. pod *v1.Pod
  60. pods []*v1.Pod
  61. node *v1.Node
  62. fits bool
  63. errorType string
  64. test string
  65. }{
  66. {
  67. pod: &v1.Pod{
  68. ObjectMeta: metav1.ObjectMeta{
  69. Name: "fakename",
  70. Labels: podLabel2,
  71. },
  72. Spec: v1.PodSpec{
  73. Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}},
  74. Affinity: &v1.Affinity{
  75. PodAffinity: &v1.PodAffinity{
  76. RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{
  77. {
  78. LabelSelector: &metav1.LabelSelector{
  79. MatchExpressions: []metav1.LabelSelectorRequirement{
  80. {
  81. Key: "security",
  82. Operator: metav1.LabelSelectorOpDoesNotExist,
  83. },
  84. },
  85. },
  86. TopologyKey: "region",
  87. },
  88. },
  89. },
  90. },
  91. },
  92. },
  93. node: nodes[0],
  94. fits: false,
  95. errorType: "invalidPod",
  96. test: "validates that a pod with an invalid podAffinity is rejected because of the LabelSelectorRequirement is invalid",
  97. },
  98. {
  99. pod: &v1.Pod{
  100. ObjectMeta: metav1.ObjectMeta{
  101. Name: "fakename",
  102. Labels: podLabel2,
  103. },
  104. Spec: v1.PodSpec{
  105. Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}},
  106. Affinity: &v1.Affinity{
  107. PodAffinity: &v1.PodAffinity{
  108. RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{
  109. {
  110. LabelSelector: &metav1.LabelSelector{
  111. MatchExpressions: []metav1.LabelSelectorRequirement{
  112. {
  113. Key: "security",
  114. Operator: metav1.LabelSelectorOpIn,
  115. Values: []string{"securityscan"},
  116. },
  117. },
  118. },
  119. TopologyKey: "region",
  120. },
  121. },
  122. },
  123. },
  124. },
  125. },
  126. node: nodes[0],
  127. fits: false,
  128. test: "validates that Inter-pod-Affinity is respected if not matching",
  129. },
  130. {
  131. pod: &v1.Pod{
  132. ObjectMeta: metav1.ObjectMeta{
  133. Name: "fakename",
  134. Labels: podLabel2,
  135. },
  136. Spec: v1.PodSpec{
  137. Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}},
  138. Affinity: &v1.Affinity{
  139. PodAffinity: &v1.PodAffinity{
  140. RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{
  141. {
  142. LabelSelector: &metav1.LabelSelector{
  143. MatchExpressions: []metav1.LabelSelectorRequirement{
  144. {
  145. Key: "service",
  146. Operator: metav1.LabelSelectorOpIn,
  147. Values: []string{"securityscan", "value2"},
  148. },
  149. },
  150. },
  151. TopologyKey: "region",
  152. },
  153. },
  154. },
  155. },
  156. },
  157. },
  158. pods: []*v1.Pod{{
  159. ObjectMeta: metav1.ObjectMeta{
  160. Name: "fakename2",
  161. Labels: podLabel,
  162. },
  163. Spec: v1.PodSpec{
  164. Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}},
  165. NodeName: nodes[0].Name,
  166. },
  167. },
  168. },
  169. node: nodes[0],
  170. fits: true,
  171. test: "validates that InterPodAffinity is respected if matching. requiredDuringSchedulingIgnoredDuringExecution in PodAffinity using In operator that matches the existing pod",
  172. },
  173. {
  174. pod: &v1.Pod{
  175. ObjectMeta: metav1.ObjectMeta{
  176. Name: "fakename",
  177. Labels: podLabel2,
  178. },
  179. Spec: v1.PodSpec{
  180. Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}},
  181. Affinity: &v1.Affinity{
  182. PodAffinity: &v1.PodAffinity{
  183. RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{
  184. {
  185. LabelSelector: &metav1.LabelSelector{
  186. MatchExpressions: []metav1.LabelSelectorRequirement{
  187. {
  188. Key: "service",
  189. Operator: metav1.LabelSelectorOpNotIn,
  190. Values: []string{"securityscan3", "value3"},
  191. },
  192. },
  193. },
  194. TopologyKey: "region",
  195. },
  196. },
  197. },
  198. },
  199. },
  200. },
  201. pods: []*v1.Pod{{Spec: v1.PodSpec{
  202. Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}},
  203. NodeName: nodes[0].Name},
  204. ObjectMeta: metav1.ObjectMeta{
  205. Name: "fakename2",
  206. Labels: podLabel}}},
  207. node: nodes[0],
  208. fits: true,
  209. test: "validates that InterPodAffinity is respected if matching. requiredDuringSchedulingIgnoredDuringExecution in PodAffinity using not in operator in labelSelector that matches the existing pod",
  210. },
  211. {
  212. pod: &v1.Pod{
  213. ObjectMeta: metav1.ObjectMeta{
  214. Name: "fakename",
  215. Labels: podLabel2,
  216. },
  217. Spec: v1.PodSpec{
  218. Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}},
  219. Affinity: &v1.Affinity{
  220. PodAffinity: &v1.PodAffinity{
  221. RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{
  222. {
  223. LabelSelector: &metav1.LabelSelector{
  224. MatchExpressions: []metav1.LabelSelectorRequirement{
  225. {
  226. Key: "service",
  227. Operator: metav1.LabelSelectorOpIn,
  228. Values: []string{"securityscan", "value2"},
  229. },
  230. },
  231. },
  232. TopologyKey: "region",
  233. Namespaces: []string{"diff-namespace"},
  234. },
  235. },
  236. },
  237. },
  238. },
  239. },
  240. pods: []*v1.Pod{{Spec: v1.PodSpec{
  241. Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}},
  242. NodeName: nodes[0].Name},
  243. ObjectMeta: metav1.ObjectMeta{
  244. Name: "fakename2",
  245. Labels: podLabel, Namespace: "ns"}}},
  246. node: nodes[0],
  247. fits: false,
  248. test: "validates that inter-pod-affinity is respected when pods have different Namespaces",
  249. },
  250. {
  251. pod: &v1.Pod{
  252. ObjectMeta: metav1.ObjectMeta{
  253. Name: "fakename",
  254. Labels: podLabel,
  255. },
  256. Spec: v1.PodSpec{
  257. Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}},
  258. Affinity: &v1.Affinity{
  259. PodAffinity: &v1.PodAffinity{
  260. RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{
  261. {
  262. LabelSelector: &metav1.LabelSelector{
  263. MatchExpressions: []metav1.LabelSelectorRequirement{
  264. {
  265. Key: "service",
  266. Operator: metav1.LabelSelectorOpIn,
  267. Values: []string{"antivirusscan", "value2"},
  268. },
  269. },
  270. },
  271. TopologyKey: "region",
  272. },
  273. },
  274. },
  275. },
  276. },
  277. },
  278. pods: []*v1.Pod{{Spec: v1.PodSpec{
  279. Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}},
  280. NodeName: nodes[0].Name}, ObjectMeta: metav1.ObjectMeta{
  281. Name: "fakename2",
  282. Labels: podLabel}}},
  283. node: nodes[0],
  284. fits: false,
  285. test: "Doesn't satisfy the PodAffinity because of unmatching labelSelector with the existing pod",
  286. },
  287. {
  288. pod: &v1.Pod{
  289. ObjectMeta: metav1.ObjectMeta{
  290. Name: "fakename",
  291. Labels: podLabel2,
  292. },
  293. Spec: v1.PodSpec{
  294. Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}},
  295. Affinity: &v1.Affinity{
  296. PodAffinity: &v1.PodAffinity{
  297. RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{
  298. {
  299. LabelSelector: &metav1.LabelSelector{
  300. MatchExpressions: []metav1.LabelSelectorRequirement{
  301. {
  302. Key: "service",
  303. Operator: metav1.LabelSelectorOpExists,
  304. }, {
  305. Key: "wrongkey",
  306. Operator: metav1.LabelSelectorOpDoesNotExist,
  307. },
  308. },
  309. },
  310. TopologyKey: "region",
  311. }, {
  312. LabelSelector: &metav1.LabelSelector{
  313. MatchExpressions: []metav1.LabelSelectorRequirement{
  314. {
  315. Key: "service",
  316. Operator: metav1.LabelSelectorOpIn,
  317. Values: []string{"securityscan"},
  318. }, {
  319. Key: "service",
  320. Operator: metav1.LabelSelectorOpNotIn,
  321. Values: []string{"WrongValue"},
  322. },
  323. },
  324. },
  325. TopologyKey: "region",
  326. },
  327. },
  328. },
  329. },
  330. },
  331. },
  332. pods: []*v1.Pod{{Spec: v1.PodSpec{
  333. Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}},
  334. NodeName: nodes[0].Name}, ObjectMeta: metav1.ObjectMeta{
  335. Name: "fakename2",
  336. Labels: podLabel}}},
  337. node: nodes[0],
  338. fits: true,
  339. test: "validates that InterPodAffinity is respected if matching with multiple affinities in multiple RequiredDuringSchedulingIgnoredDuringExecution ",
  340. },
  341. {
  342. pod: &v1.Pod{
  343. ObjectMeta: metav1.ObjectMeta{
  344. Labels: podLabel2,
  345. Name: "fakename",
  346. },
  347. Spec: v1.PodSpec{
  348. Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}},
  349. Affinity: &v1.Affinity{
  350. PodAffinity: &v1.PodAffinity{
  351. RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{
  352. {
  353. LabelSelector: &metav1.LabelSelector{
  354. MatchExpressions: []metav1.LabelSelectorRequirement{
  355. {
  356. Key: "service",
  357. Operator: metav1.LabelSelectorOpExists,
  358. }, {
  359. Key: "wrongkey",
  360. Operator: metav1.LabelSelectorOpDoesNotExist,
  361. },
  362. },
  363. },
  364. TopologyKey: "region",
  365. }, {
  366. LabelSelector: &metav1.LabelSelector{
  367. MatchExpressions: []metav1.LabelSelectorRequirement{
  368. {
  369. Key: "service",
  370. Operator: metav1.LabelSelectorOpIn,
  371. Values: []string{"securityscan2"},
  372. }, {
  373. Key: "service",
  374. Operator: metav1.LabelSelectorOpNotIn,
  375. Values: []string{"WrongValue"},
  376. },
  377. },
  378. },
  379. TopologyKey: "region",
  380. },
  381. },
  382. },
  383. },
  384. },
  385. },
  386. pods: []*v1.Pod{{Spec: v1.PodSpec{
  387. Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}},
  388. NodeName: nodes[0].Name}, ObjectMeta: metav1.ObjectMeta{
  389. Name: "fakename2",
  390. Labels: podLabel}}},
  391. node: nodes[0],
  392. fits: false,
  393. test: "The labelSelector requirements(items of matchExpressions) are ANDed, the pod cannot schedule onto the node because one of the matchExpression items doesn't match.",
  394. },
  395. {
  396. pod: &v1.Pod{
  397. ObjectMeta: metav1.ObjectMeta{
  398. Name: "fakename",
  399. Labels: podLabel2,
  400. },
  401. Spec: v1.PodSpec{
  402. Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}},
  403. Affinity: &v1.Affinity{
  404. PodAffinity: &v1.PodAffinity{
  405. RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{
  406. {
  407. LabelSelector: &metav1.LabelSelector{
  408. MatchExpressions: []metav1.LabelSelectorRequirement{
  409. {
  410. Key: "service",
  411. Operator: metav1.LabelSelectorOpIn,
  412. Values: []string{"securityscan", "value2"},
  413. },
  414. },
  415. },
  416. TopologyKey: "region",
  417. },
  418. },
  419. },
  420. PodAntiAffinity: &v1.PodAntiAffinity{
  421. RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{
  422. {
  423. LabelSelector: &metav1.LabelSelector{
  424. MatchExpressions: []metav1.LabelSelectorRequirement{
  425. {
  426. Key: "service",
  427. Operator: metav1.LabelSelectorOpIn,
  428. Values: []string{"antivirusscan", "value2"},
  429. },
  430. },
  431. },
  432. TopologyKey: "node",
  433. },
  434. },
  435. },
  436. },
  437. },
  438. },
  439. pods: []*v1.Pod{{Spec: v1.PodSpec{
  440. Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}},
  441. NodeName: nodes[0].Name}, ObjectMeta: metav1.ObjectMeta{
  442. Name: "fakename2",
  443. Labels: podLabel}}},
  444. node: nodes[0],
  445. fits: true,
  446. test: "validates that InterPod Affinity and AntiAffinity is respected if matching",
  447. },
  448. {
  449. pod: &v1.Pod{
  450. ObjectMeta: metav1.ObjectMeta{
  451. Name: "fakename",
  452. Labels: podLabel2,
  453. },
  454. Spec: v1.PodSpec{
  455. Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}},
  456. Affinity: &v1.Affinity{
  457. PodAffinity: &v1.PodAffinity{
  458. RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{
  459. {
  460. LabelSelector: &metav1.LabelSelector{
  461. MatchExpressions: []metav1.LabelSelectorRequirement{
  462. {
  463. Key: "service",
  464. Operator: metav1.LabelSelectorOpIn,
  465. Values: []string{"securityscan", "value2"},
  466. },
  467. },
  468. },
  469. TopologyKey: "region",
  470. },
  471. },
  472. },
  473. PodAntiAffinity: &v1.PodAntiAffinity{
  474. RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{
  475. {
  476. LabelSelector: &metav1.LabelSelector{
  477. MatchExpressions: []metav1.LabelSelectorRequirement{
  478. {
  479. Key: "service",
  480. Operator: metav1.LabelSelectorOpIn,
  481. Values: []string{"antivirusscan", "value2"},
  482. },
  483. },
  484. },
  485. TopologyKey: "node",
  486. },
  487. },
  488. },
  489. },
  490. },
  491. },
  492. pods: []*v1.Pod{
  493. {
  494. Spec: v1.PodSpec{
  495. Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}},
  496. NodeName: nodes[0].Name,
  497. Affinity: &v1.Affinity{
  498. PodAntiAffinity: &v1.PodAntiAffinity{
  499. RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{
  500. {
  501. LabelSelector: &metav1.LabelSelector{
  502. MatchExpressions: []metav1.LabelSelectorRequirement{
  503. {
  504. Key: "service",
  505. Operator: metav1.LabelSelectorOpIn,
  506. Values: []string{"antivirusscan", "value2"},
  507. },
  508. },
  509. },
  510. TopologyKey: "node",
  511. },
  512. },
  513. },
  514. },
  515. },
  516. ObjectMeta: metav1.ObjectMeta{
  517. Name: "fakename2",
  518. Labels: podLabel},
  519. },
  520. },
  521. node: nodes[0],
  522. fits: true,
  523. test: "satisfies the PodAffinity and PodAntiAffinity and PodAntiAffinity symmetry with the existing pod",
  524. },
  525. {
  526. pod: &v1.Pod{
  527. ObjectMeta: metav1.ObjectMeta{
  528. Name: "fakename",
  529. Labels: podLabel2,
  530. },
  531. Spec: v1.PodSpec{
  532. Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}},
  533. Affinity: &v1.Affinity{
  534. PodAffinity: &v1.PodAffinity{
  535. RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{
  536. {
  537. LabelSelector: &metav1.LabelSelector{
  538. MatchExpressions: []metav1.LabelSelectorRequirement{
  539. {
  540. Key: "service",
  541. Operator: metav1.LabelSelectorOpIn,
  542. Values: []string{"securityscan", "value2"},
  543. },
  544. },
  545. },
  546. TopologyKey: "region",
  547. },
  548. },
  549. },
  550. PodAntiAffinity: &v1.PodAntiAffinity{
  551. RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{
  552. {
  553. LabelSelector: &metav1.LabelSelector{
  554. MatchExpressions: []metav1.LabelSelectorRequirement{
  555. {
  556. Key: "service",
  557. Operator: metav1.LabelSelectorOpIn,
  558. Values: []string{"securityscan", "value2"},
  559. },
  560. },
  561. },
  562. TopologyKey: "zone",
  563. },
  564. },
  565. },
  566. },
  567. },
  568. },
  569. pods: []*v1.Pod{{Spec: v1.PodSpec{
  570. Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}},
  571. NodeName: nodes[0].Name}, ObjectMeta: metav1.ObjectMeta{
  572. Name: "fakename2",
  573. Labels: podLabel}}},
  574. node: nodes[0],
  575. fits: false,
  576. test: "satisfies the PodAffinity but doesn't satisfies the PodAntiAffinity with the existing pod",
  577. },
  578. {
  579. pod: &v1.Pod{
  580. ObjectMeta: metav1.ObjectMeta{
  581. Name: "fakename",
  582. Labels: podLabel,
  583. },
  584. Spec: v1.PodSpec{
  585. Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}},
  586. Affinity: &v1.Affinity{
  587. PodAffinity: &v1.PodAffinity{
  588. RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{
  589. {
  590. LabelSelector: &metav1.LabelSelector{
  591. MatchExpressions: []metav1.LabelSelectorRequirement{
  592. {
  593. Key: "service",
  594. Operator: metav1.LabelSelectorOpIn,
  595. Values: []string{"securityscan", "value2"},
  596. },
  597. },
  598. },
  599. TopologyKey: "region",
  600. },
  601. },
  602. },
  603. PodAntiAffinity: &v1.PodAntiAffinity{
  604. RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{
  605. {
  606. LabelSelector: &metav1.LabelSelector{
  607. MatchExpressions: []metav1.LabelSelectorRequirement{
  608. {
  609. Key: "service",
  610. Operator: metav1.LabelSelectorOpIn,
  611. Values: []string{"antivirusscan", "value2"},
  612. },
  613. },
  614. },
  615. TopologyKey: "node",
  616. },
  617. },
  618. },
  619. },
  620. },
  621. },
  622. pods: []*v1.Pod{
  623. {
  624. Spec: v1.PodSpec{
  625. NodeName: nodes[0].Name,
  626. Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}},
  627. Affinity: &v1.Affinity{
  628. PodAntiAffinity: &v1.PodAntiAffinity{
  629. RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{
  630. {
  631. LabelSelector: &metav1.LabelSelector{
  632. MatchExpressions: []metav1.LabelSelectorRequirement{
  633. {
  634. Key: "service",
  635. Operator: metav1.LabelSelectorOpIn,
  636. Values: []string{"securityscan", "value3"},
  637. },
  638. },
  639. },
  640. TopologyKey: "zone",
  641. },
  642. },
  643. },
  644. },
  645. },
  646. ObjectMeta: metav1.ObjectMeta{
  647. Name: "fakename2",
  648. Labels: podLabel},
  649. },
  650. },
  651. node: nodes[0],
  652. fits: false,
  653. test: "satisfies the PodAffinity and PodAntiAffinity but doesn't satisfies PodAntiAffinity symmetry with the existing pod",
  654. },
  655. {
  656. pod: &v1.Pod{
  657. ObjectMeta: metav1.ObjectMeta{
  658. Name: "fakename",
  659. Labels: podLabel,
  660. },
  661. Spec: v1.PodSpec{
  662. Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}},
  663. Affinity: &v1.Affinity{
  664. PodAffinity: &v1.PodAffinity{
  665. RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{
  666. {
  667. LabelSelector: &metav1.LabelSelector{
  668. MatchExpressions: []metav1.LabelSelectorRequirement{
  669. {
  670. Key: "service",
  671. Operator: metav1.LabelSelectorOpNotIn,
  672. Values: []string{"securityscan", "value2"},
  673. },
  674. },
  675. },
  676. TopologyKey: "region",
  677. },
  678. },
  679. },
  680. },
  681. },
  682. },
  683. pods: []*v1.Pod{{Spec: v1.PodSpec{
  684. Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}},
  685. NodeName: "machine2"}, ObjectMeta: metav1.ObjectMeta{
  686. Name: "fakename2",
  687. Labels: podLabel}}},
  688. node: nodes[0],
  689. fits: false,
  690. test: "pod matches its own Label in PodAffinity and that matches the existing pod Labels",
  691. },
  692. {
  693. pod: &v1.Pod{
  694. ObjectMeta: metav1.ObjectMeta{
  695. Name: "fakename",
  696. Labels: podLabel,
  697. },
  698. Spec: v1.PodSpec{Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}},
  699. },
  700. pods: []*v1.Pod{
  701. {
  702. Spec: v1.PodSpec{NodeName: nodes[0].Name,
  703. Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}},
  704. Affinity: &v1.Affinity{
  705. PodAntiAffinity: &v1.PodAntiAffinity{
  706. RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{
  707. {
  708. LabelSelector: &metav1.LabelSelector{
  709. MatchExpressions: []metav1.LabelSelectorRequirement{
  710. {
  711. Key: "service",
  712. Operator: metav1.LabelSelectorOpIn,
  713. Values: []string{"securityscan", "value2"},
  714. },
  715. },
  716. },
  717. TopologyKey: "zone",
  718. },
  719. },
  720. },
  721. },
  722. },
  723. ObjectMeta: metav1.ObjectMeta{
  724. Name: "fakename2",
  725. Labels: podLabel},
  726. },
  727. },
  728. node: nodes[0],
  729. fits: false,
  730. test: "Verify that PodAntiAffinity of an existing pod is respected when PodAntiAffinity symmetry is not satisfied with the existing pod",
  731. },
  732. {
  733. pod: &v1.Pod{
  734. ObjectMeta: metav1.ObjectMeta{
  735. Name: "fake-name",
  736. Labels: podLabel,
  737. },
  738. Spec: v1.PodSpec{Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}}},
  739. },
  740. pods: []*v1.Pod{
  741. {
  742. Spec: v1.PodSpec{NodeName: nodes[0].Name,
  743. Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}},
  744. Affinity: &v1.Affinity{
  745. PodAntiAffinity: &v1.PodAntiAffinity{
  746. RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{
  747. {
  748. LabelSelector: &metav1.LabelSelector{
  749. MatchExpressions: []metav1.LabelSelectorRequirement{
  750. {
  751. Key: "service",
  752. Operator: metav1.LabelSelectorOpNotIn,
  753. Values: []string{"securityscan", "value2"},
  754. },
  755. },
  756. },
  757. TopologyKey: "zone",
  758. },
  759. },
  760. },
  761. },
  762. },
  763. ObjectMeta: metav1.ObjectMeta{
  764. Name: "fake-name2",
  765. Labels: podLabel},
  766. },
  767. },
  768. node: nodes[0],
  769. fits: true,
  770. test: "Verify that PodAntiAffinity from existing pod is respected when pod statisfies PodAntiAffinity symmetry with the existing pod",
  771. },
  772. {
  773. pod: &v1.Pod{
  774. ObjectMeta: metav1.ObjectMeta{Name: "fake-name2"},
  775. Spec: v1.PodSpec{
  776. Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}},
  777. NodeSelector: map[string]string{"region": "r1"},
  778. Affinity: &v1.Affinity{
  779. PodAntiAffinity: &v1.PodAntiAffinity{
  780. RequiredDuringSchedulingIgnoredDuringExecution: []v1.PodAffinityTerm{
  781. {
  782. LabelSelector: &metav1.LabelSelector{
  783. MatchExpressions: []metav1.LabelSelectorRequirement{
  784. {
  785. Key: "foo",
  786. Operator: metav1.LabelSelectorOpIn,
  787. Values: []string{"abc"},
  788. },
  789. },
  790. },
  791. TopologyKey: "region",
  792. },
  793. },
  794. },
  795. },
  796. },
  797. },
  798. pods: []*v1.Pod{
  799. {Spec: v1.PodSpec{
  800. Containers: []v1.Container{{Name: "container", Image: imageutils.GetPauseImageName()}},
  801. NodeName: nodes[0].Name}, ObjectMeta: metav1.ObjectMeta{Name: "fakename", Labels: map[string]string{"foo": "abc"}}},
  802. },
  803. fits: false,
  804. test: "nodes[0] and nodes[1] have same topologyKey and label value. nodes[0] has an existing pod that matches the inter pod affinity rule. The new pod can not be scheduled onto either of the two nodes.",
  805. },
  806. }
  807. for _, test := range tests {
  808. for _, pod := range test.pods {
  809. var nsName string
  810. if pod.Namespace != "" {
  811. nsName = pod.Namespace
  812. } else {
  813. nsName = testCtx.ns.Name
  814. }
  815. createdPod, err := cs.CoreV1().Pods(nsName).Create(context.TODO(), pod, metav1.CreateOptions{})
  816. if err != nil {
  817. t.Fatalf("Test Failed: error, %v, while creating pod during test: %v", err, test.test)
  818. }
  819. err = wait.Poll(pollInterval, wait.ForeverTestTimeout, podScheduled(cs, createdPod.Namespace, createdPod.Name))
  820. if err != nil {
  821. t.Errorf("Test Failed: error, %v, while waiting for pod during test, %v", err, test)
  822. }
  823. }
  824. testPod, err := cs.CoreV1().Pods(testCtx.ns.Name).Create(context.TODO(), test.pod, metav1.CreateOptions{})
  825. if err != nil {
  826. if !(test.errorType == "invalidPod" && apierrors.IsInvalid(err)) {
  827. t.Fatalf("Test Failed: error, %v, while creating pod during test: %v", err, test.test)
  828. }
  829. }
  830. if test.fits {
  831. err = wait.Poll(pollInterval, wait.ForeverTestTimeout, podScheduled(cs, testPod.Namespace, testPod.Name))
  832. } else {
  833. err = wait.Poll(pollInterval, wait.ForeverTestTimeout, podUnschedulable(cs, testPod.Namespace, testPod.Name))
  834. }
  835. if err != nil {
  836. t.Errorf("Test Failed: %v, err %v, test.fits %v", test.test, err, test.fits)
  837. }
  838. err = cs.CoreV1().Pods(testCtx.ns.Name).Delete(context.TODO(), test.pod.Name, metav1.NewDeleteOptions(0))
  839. if err != nil {
  840. t.Errorf("Test Failed: error, %v, while deleting pod during test: %v", err, test.test)
  841. }
  842. err = wait.Poll(pollInterval, wait.ForeverTestTimeout, podDeleted(cs, testCtx.ns.Name, test.pod.Name))
  843. if err != nil {
  844. t.Errorf("Test Failed: error, %v, while waiting for pod to get deleted, %v", err, test.test)
  845. }
  846. for _, pod := range test.pods {
  847. var nsName string
  848. if pod.Namespace != "" {
  849. nsName = pod.Namespace
  850. } else {
  851. nsName = testCtx.ns.Name
  852. }
  853. err = cs.CoreV1().Pods(nsName).Delete(context.TODO(), pod.Name, metav1.NewDeleteOptions(0))
  854. if err != nil {
  855. t.Errorf("Test Failed: error, %v, while deleting pod during test: %v", err, test.test)
  856. }
  857. err = wait.Poll(pollInterval, wait.ForeverTestTimeout, podDeleted(cs, nsName, pod.Name))
  858. if err != nil {
  859. t.Errorf("Test Failed: error, %v, while waiting for pod to get deleted, %v", err, test.test)
  860. }
  861. }
  862. }
  863. }
  864. // TestEvenPodsSpreadPredicate verifies that EvenPodsSpread predicate functions well.
  865. func TestEvenPodsSpreadPredicate(t *testing.T) {
  866. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.EvenPodsSpread, true)()
  867. testCtx := initTest(t, "eps-predicate")
  868. cs := testCtx.clientSet
  869. ns := testCtx.ns.Name
  870. defer cleanupTest(t, testCtx)
  871. // Add 4 nodes.
  872. nodes, err := createNodes(cs, "node", nil, 4)
  873. if err != nil {
  874. t.Fatalf("Cannot create nodes: %v", err)
  875. }
  876. for i, node := range nodes {
  877. // Apply labels "zone: zone-{0,1}" and "node: <node name>" to each node.
  878. labels := map[string]string{
  879. "zone": fmt.Sprintf("zone-%d", i/2),
  880. "node": node.Name,
  881. }
  882. if err = testutils.AddLabelsToNode(cs, node.Name, labels); err != nil {
  883. t.Fatalf("Cannot add labels to node: %v", err)
  884. }
  885. if err = waitForNodeLabels(cs, node.Name, labels); err != nil {
  886. t.Fatalf("Failed to poll node labels: %v", err)
  887. }
  888. }
  889. pause := imageutils.GetPauseImageName()
  890. tests := []struct {
  891. name string
  892. incomingPod *v1.Pod
  893. existingPods []*v1.Pod
  894. fits bool
  895. candidateNodes []string // nodes expected to schedule onto
  896. }{
  897. // note: naming starts at index 0
  898. {
  899. name: "place pod on a 1/1/0/1 cluster with MaxSkew=1, node-2 is the only fit",
  900. incomingPod: st.MakePod().Namespace(ns).Name("p").Label("foo", "").Container(pause).
  901. SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()).
  902. Obj(),
  903. existingPods: []*v1.Pod{
  904. st.MakePod().Namespace(ns).Name("p0").Node("node-0").Label("foo", "").Container(pause).Obj(),
  905. st.MakePod().Namespace(ns).Name("p1").Node("node-1").Label("foo", "").Container(pause).Obj(),
  906. st.MakePod().Namespace(ns).Name("p3").Node("node-3").Label("foo", "").Container(pause).Obj(),
  907. },
  908. fits: true,
  909. candidateNodes: []string{"node-2"},
  910. },
  911. {
  912. name: "place pod on a 2/0/0/1 cluster with MaxSkew=2, node-{1,2,3} are good fits",
  913. incomingPod: st.MakePod().Namespace(ns).Name("p").Label("foo", "").Container(pause).
  914. SpreadConstraint(2, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()).
  915. Obj(),
  916. existingPods: []*v1.Pod{
  917. st.MakePod().Namespace(ns).Name("p0a").Node("node-0").Label("foo", "").Container(pause).Obj(),
  918. st.MakePod().Namespace(ns).Name("p0b").Node("node-0").Label("foo", "").Container(pause).Obj(),
  919. st.MakePod().Namespace(ns).Name("p3").Node("node-3").Label("foo", "").Container(pause).Obj(),
  920. },
  921. fits: true,
  922. candidateNodes: []string{"node-1", "node-2", "node-3"},
  923. },
  924. {
  925. name: "pod is required to be placed on zone0, so only node-1 fits",
  926. incomingPod: st.MakePod().Namespace(ns).Name("p").Label("foo", "").Container(pause).
  927. NodeAffinityIn("zone", []string{"zone-0"}).
  928. SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()).
  929. Obj(),
  930. existingPods: []*v1.Pod{
  931. st.MakePod().Namespace(ns).Name("p0").Node("node-0").Label("foo", "").Container(pause).Obj(),
  932. st.MakePod().Namespace(ns).Name("p3").Node("node-3").Label("foo", "").Container(pause).Obj(),
  933. },
  934. fits: true,
  935. candidateNodes: []string{"node-1"},
  936. },
  937. {
  938. name: "two constraints: pod can only be placed to zone-1/node-2",
  939. incomingPod: st.MakePod().Namespace(ns).Name("p").Label("foo", "").Container(pause).
  940. SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()).
  941. SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()).
  942. Obj(),
  943. existingPods: []*v1.Pod{
  944. st.MakePod().Namespace(ns).Name("p0").Node("node-0").Label("foo", "").Container(pause).Obj(),
  945. st.MakePod().Namespace(ns).Name("p1").Node("node-1").Label("foo", "").Container(pause).Obj(),
  946. st.MakePod().Namespace(ns).Name("p3a").Node("node-3").Label("foo", "").Container(pause).Obj(),
  947. st.MakePod().Namespace(ns).Name("p3b").Node("node-3").Label("foo", "").Container(pause).Obj(),
  948. },
  949. fits: true,
  950. candidateNodes: []string{"node-2"},
  951. },
  952. {
  953. name: "pod cannot be placed onto any node",
  954. incomingPod: st.MakePod().Namespace(ns).Name("p").Label("foo", "").Container(pause).
  955. NodeAffinityNotIn("node", []string{"node-0"}). // mock a 3-node cluster
  956. SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()).
  957. SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()).
  958. Obj(),
  959. existingPods: []*v1.Pod{
  960. st.MakePod().Namespace(ns).Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(),
  961. st.MakePod().Namespace(ns).Name("p1b").Node("node-1").Label("foo", "").Container(pause).Obj(),
  962. st.MakePod().Namespace(ns).Name("p2a").Node("node-2").Label("foo", "").Container(pause).Obj(),
  963. st.MakePod().Namespace(ns).Name("p2b").Node("node-2").Label("foo", "").Container(pause).Obj(),
  964. st.MakePod().Namespace(ns).Name("p3").Node("node-3").Label("foo", "").Container(pause).Obj(),
  965. },
  966. fits: false,
  967. },
  968. {
  969. name: "high priority pod can preempt others",
  970. incomingPod: st.MakePod().Namespace(ns).Name("p").Label("foo", "").Container(pause).Priority(100).
  971. NodeAffinityNotIn("node", []string{"node-0"}). // mock a 3-node cluster
  972. SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()).
  973. SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()).
  974. Obj(),
  975. existingPods: []*v1.Pod{
  976. st.MakePod().ZeroTerminationGracePeriod().Namespace(ns).Name("p1a").Node("node-1").Label("foo", "").Container(pause).Obj(),
  977. st.MakePod().ZeroTerminationGracePeriod().Namespace(ns).Name("p1b").Node("node-1").Label("foo", "").Container(pause).Obj(),
  978. st.MakePod().ZeroTerminationGracePeriod().Namespace(ns).Name("p2a").Node("node-2").Label("foo", "").Container(pause).Obj(),
  979. st.MakePod().ZeroTerminationGracePeriod().Namespace(ns).Name("p2b").Node("node-2").Label("foo", "").Container(pause).Obj(),
  980. st.MakePod().ZeroTerminationGracePeriod().Namespace(ns).Name("p3").Node("node-3").Label("foo", "").Container(pause).Obj(),
  981. },
  982. fits: true,
  983. candidateNodes: []string{"node-1", "node-2", "node-3"},
  984. },
  985. }
  986. for _, tt := range tests {
  987. t.Run(tt.name, func(t *testing.T) {
  988. allPods := append(tt.existingPods, tt.incomingPod)
  989. defer cleanupPods(cs, t, allPods)
  990. for _, pod := range tt.existingPods {
  991. createdPod, err := cs.CoreV1().Pods(pod.Namespace).Create(context.TODO(), pod, metav1.CreateOptions{})
  992. if err != nil {
  993. t.Fatalf("Test Failed: error while creating pod during test: %v", err)
  994. }
  995. err = wait.Poll(pollInterval, wait.ForeverTestTimeout, podScheduled(cs, createdPod.Namespace, createdPod.Name))
  996. if err != nil {
  997. t.Errorf("Test Failed: error while waiting for pod during test: %v", err)
  998. }
  999. }
  1000. testPod, err := cs.CoreV1().Pods(tt.incomingPod.Namespace).Create(context.TODO(), tt.incomingPod, metav1.CreateOptions{})
  1001. if err != nil && !apierrors.IsInvalid(err) {
  1002. t.Fatalf("Test Failed: error while creating pod during test: %v", err)
  1003. }
  1004. if tt.fits {
  1005. err = wait.Poll(pollInterval, wait.ForeverTestTimeout, podScheduledIn(cs, testPod.Namespace, testPod.Name, tt.candidateNodes))
  1006. } else {
  1007. err = wait.Poll(pollInterval, wait.ForeverTestTimeout, podUnschedulable(cs, testPod.Namespace, testPod.Name))
  1008. }
  1009. if err != nil {
  1010. t.Errorf("Test Failed: %v", err)
  1011. }
  1012. })
  1013. }
  1014. }
  1015. var (
  1016. hardSpread = v1.DoNotSchedule
  1017. softSpread = v1.ScheduleAnyway
  1018. )