daemonset_util_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590
  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 util
  14. import (
  15. "fmt"
  16. "reflect"
  17. "testing"
  18. v1 "k8s.io/api/core/v1"
  19. extensions "k8s.io/api/extensions/v1beta1"
  20. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  21. utilfeature "k8s.io/apiserver/pkg/util/feature"
  22. "k8s.io/component-base/featuregate"
  23. featuregatetesting "k8s.io/component-base/featuregate/testing"
  24. api "k8s.io/kubernetes/pkg/apis/core"
  25. utilpointer "k8s.io/utils/pointer"
  26. )
  27. func newPod(podName string, nodeName string, label map[string]string) *v1.Pod {
  28. pod := &v1.Pod{
  29. TypeMeta: metav1.TypeMeta{APIVersion: "v1"},
  30. ObjectMeta: metav1.ObjectMeta{
  31. Labels: label,
  32. Namespace: metav1.NamespaceDefault,
  33. },
  34. Spec: v1.PodSpec{
  35. NodeName: nodeName,
  36. Containers: []v1.Container{
  37. {
  38. Image: "foo/bar",
  39. },
  40. },
  41. },
  42. }
  43. pod.Name = podName
  44. return pod
  45. }
  46. func TestIsPodUpdated(t *testing.T) {
  47. templateGeneration := utilpointer.Int64Ptr(12345)
  48. badGeneration := utilpointer.Int64Ptr(12345)
  49. hash := "55555"
  50. labels := map[string]string{extensions.DaemonSetTemplateGenerationKey: fmt.Sprint(templateGeneration), extensions.DefaultDaemonSetUniqueLabelKey: hash}
  51. labelsNoHash := map[string]string{extensions.DaemonSetTemplateGenerationKey: fmt.Sprint(templateGeneration)}
  52. tests := []struct {
  53. test string
  54. templateGeneration *int64
  55. pod *v1.Pod
  56. hash string
  57. isUpdated bool
  58. }{
  59. {
  60. "templateGeneration and hash both match",
  61. templateGeneration,
  62. newPod("pod1", "node1", labels),
  63. hash,
  64. true,
  65. },
  66. {
  67. "templateGeneration matches, hash doesn't",
  68. templateGeneration,
  69. newPod("pod1", "node1", labels),
  70. hash + "123",
  71. true,
  72. },
  73. {
  74. "templateGeneration matches, no hash label, has hash",
  75. templateGeneration,
  76. newPod("pod1", "node1", labelsNoHash),
  77. hash,
  78. true,
  79. },
  80. {
  81. "templateGeneration matches, no hash label, no hash",
  82. templateGeneration,
  83. newPod("pod1", "node1", labelsNoHash),
  84. "",
  85. true,
  86. },
  87. {
  88. "templateGeneration matches, has hash label, no hash",
  89. templateGeneration,
  90. newPod("pod1", "node1", labels),
  91. "",
  92. true,
  93. },
  94. {
  95. "templateGeneration doesn't match, hash does",
  96. badGeneration,
  97. newPod("pod1", "node1", labels),
  98. hash,
  99. true,
  100. },
  101. {
  102. "templateGeneration and hash don't match",
  103. badGeneration,
  104. newPod("pod1", "node1", labels),
  105. hash + "123",
  106. false,
  107. },
  108. {
  109. "empty labels, no hash",
  110. templateGeneration,
  111. newPod("pod1", "node1", map[string]string{}),
  112. "",
  113. false,
  114. },
  115. {
  116. "empty labels",
  117. templateGeneration,
  118. newPod("pod1", "node1", map[string]string{}),
  119. hash,
  120. false,
  121. },
  122. {
  123. "no labels",
  124. templateGeneration,
  125. newPod("pod1", "node1", nil),
  126. hash,
  127. false,
  128. },
  129. }
  130. for _, test := range tests {
  131. updated := IsPodUpdated(test.pod, test.hash, test.templateGeneration)
  132. if updated != test.isUpdated {
  133. t.Errorf("%s: IsPodUpdated returned wrong value. Expected %t, got %t", test.test, test.isUpdated, updated)
  134. }
  135. }
  136. }
  137. func TestCreatePodTemplate(t *testing.T) {
  138. tests := []struct {
  139. templateGeneration *int64
  140. hash string
  141. expectUniqueLabel bool
  142. }{
  143. {utilpointer.Int64Ptr(1), "", false},
  144. {utilpointer.Int64Ptr(2), "3242341807", true},
  145. }
  146. for _, test := range tests {
  147. podTemplateSpec := v1.PodTemplateSpec{}
  148. newPodTemplate := CreatePodTemplate(podTemplateSpec, test.templateGeneration, test.hash)
  149. val, exists := newPodTemplate.ObjectMeta.Labels[extensions.DaemonSetTemplateGenerationKey]
  150. if !exists || val != fmt.Sprint(*test.templateGeneration) {
  151. t.Errorf("Expected podTemplateSpec to have generation label value: %d, got: %s", *test.templateGeneration, val)
  152. }
  153. val, exists = newPodTemplate.ObjectMeta.Labels[extensions.DefaultDaemonSetUniqueLabelKey]
  154. if test.expectUniqueLabel && (!exists || val != test.hash) {
  155. t.Errorf("Expected podTemplateSpec to have hash label value: %s, got: %s", test.hash, val)
  156. }
  157. if !test.expectUniqueLabel && exists {
  158. t.Errorf("Expected podTemplateSpec to have no hash label, got: %s", val)
  159. }
  160. }
  161. }
  162. func TestReplaceDaemonSetPodNodeNameNodeAffinity(t *testing.T) {
  163. tests := []struct {
  164. affinity *v1.Affinity
  165. hostname string
  166. expected *v1.Affinity
  167. }{
  168. {
  169. affinity: nil,
  170. hostname: "host_1",
  171. expected: &v1.Affinity{
  172. NodeAffinity: &v1.NodeAffinity{
  173. RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
  174. NodeSelectorTerms: []v1.NodeSelectorTerm{
  175. {
  176. MatchFields: []v1.NodeSelectorRequirement{
  177. {
  178. Key: api.ObjectNameField,
  179. Operator: v1.NodeSelectorOpIn,
  180. Values: []string{"host_1"},
  181. },
  182. },
  183. },
  184. },
  185. },
  186. },
  187. },
  188. },
  189. {
  190. affinity: &v1.Affinity{
  191. NodeAffinity: &v1.NodeAffinity{
  192. RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
  193. NodeSelectorTerms: []v1.NodeSelectorTerm{
  194. {
  195. MatchExpressions: []v1.NodeSelectorRequirement{
  196. {
  197. Key: v1.LabelHostname,
  198. Operator: v1.NodeSelectorOpIn,
  199. Values: []string{"host_1"},
  200. },
  201. },
  202. },
  203. },
  204. },
  205. },
  206. },
  207. hostname: "host_1",
  208. expected: &v1.Affinity{
  209. NodeAffinity: &v1.NodeAffinity{
  210. RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
  211. NodeSelectorTerms: []v1.NodeSelectorTerm{
  212. {
  213. MatchFields: []v1.NodeSelectorRequirement{
  214. {
  215. Key: api.ObjectNameField,
  216. Operator: v1.NodeSelectorOpIn,
  217. Values: []string{"host_1"},
  218. },
  219. },
  220. },
  221. },
  222. },
  223. },
  224. },
  225. },
  226. {
  227. affinity: &v1.Affinity{
  228. NodeAffinity: &v1.NodeAffinity{
  229. PreferredDuringSchedulingIgnoredDuringExecution: []v1.PreferredSchedulingTerm{
  230. {
  231. Preference: v1.NodeSelectorTerm{
  232. MatchExpressions: []v1.NodeSelectorRequirement{
  233. {
  234. Key: v1.LabelHostname,
  235. Operator: v1.NodeSelectorOpIn,
  236. Values: []string{"host_1"},
  237. },
  238. },
  239. },
  240. },
  241. },
  242. },
  243. },
  244. hostname: "host_1",
  245. expected: &v1.Affinity{
  246. NodeAffinity: &v1.NodeAffinity{
  247. PreferredDuringSchedulingIgnoredDuringExecution: []v1.PreferredSchedulingTerm{
  248. {
  249. Preference: v1.NodeSelectorTerm{
  250. MatchExpressions: []v1.NodeSelectorRequirement{
  251. {
  252. Key: v1.LabelHostname,
  253. Operator: v1.NodeSelectorOpIn,
  254. Values: []string{"host_1"},
  255. },
  256. },
  257. },
  258. },
  259. },
  260. RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
  261. NodeSelectorTerms: []v1.NodeSelectorTerm{
  262. {
  263. MatchFields: []v1.NodeSelectorRequirement{
  264. {
  265. Key: api.ObjectNameField,
  266. Operator: v1.NodeSelectorOpIn,
  267. Values: []string{"host_1"},
  268. },
  269. },
  270. },
  271. },
  272. },
  273. },
  274. },
  275. },
  276. {
  277. affinity: &v1.Affinity{
  278. NodeAffinity: &v1.NodeAffinity{
  279. RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
  280. NodeSelectorTerms: []v1.NodeSelectorTerm{
  281. {
  282. MatchFields: []v1.NodeSelectorRequirement{
  283. {
  284. Key: api.ObjectNameField,
  285. Operator: v1.NodeSelectorOpIn,
  286. Values: []string{"host_1", "host_2"},
  287. },
  288. },
  289. },
  290. },
  291. },
  292. },
  293. },
  294. hostname: "host_1",
  295. expected: &v1.Affinity{
  296. NodeAffinity: &v1.NodeAffinity{
  297. RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
  298. NodeSelectorTerms: []v1.NodeSelectorTerm{
  299. {
  300. MatchFields: []v1.NodeSelectorRequirement{
  301. {
  302. Key: api.ObjectNameField,
  303. Operator: v1.NodeSelectorOpIn,
  304. Values: []string{"host_1"},
  305. },
  306. },
  307. },
  308. },
  309. },
  310. },
  311. },
  312. },
  313. {
  314. affinity: nil,
  315. hostname: "host_1",
  316. expected: &v1.Affinity{
  317. NodeAffinity: &v1.NodeAffinity{
  318. RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
  319. NodeSelectorTerms: []v1.NodeSelectorTerm{
  320. {
  321. MatchFields: []v1.NodeSelectorRequirement{
  322. {
  323. Key: api.ObjectNameField,
  324. Operator: v1.NodeSelectorOpIn,
  325. Values: []string{"host_1"},
  326. },
  327. },
  328. },
  329. },
  330. },
  331. },
  332. },
  333. },
  334. {
  335. affinity: &v1.Affinity{
  336. NodeAffinity: &v1.NodeAffinity{
  337. RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
  338. NodeSelectorTerms: []v1.NodeSelectorTerm{
  339. {
  340. MatchExpressions: []v1.NodeSelectorRequirement{
  341. {
  342. Key: "hostname",
  343. Operator: v1.NodeSelectorOpIn,
  344. Values: []string{"host_1"},
  345. },
  346. },
  347. },
  348. {
  349. MatchFields: []v1.NodeSelectorRequirement{
  350. {
  351. Key: api.ObjectNameField,
  352. Operator: v1.NodeSelectorOpIn,
  353. Values: []string{"host_2"},
  354. },
  355. },
  356. },
  357. },
  358. },
  359. },
  360. },
  361. hostname: "host_1",
  362. expected: &v1.Affinity{
  363. NodeAffinity: &v1.NodeAffinity{
  364. RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
  365. NodeSelectorTerms: []v1.NodeSelectorTerm{
  366. {
  367. MatchFields: []v1.NodeSelectorRequirement{
  368. {
  369. Key: api.ObjectNameField,
  370. Operator: v1.NodeSelectorOpIn,
  371. Values: []string{"host_1"},
  372. },
  373. },
  374. },
  375. },
  376. },
  377. },
  378. },
  379. },
  380. {
  381. affinity: &v1.Affinity{
  382. NodeAffinity: &v1.NodeAffinity{
  383. RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
  384. NodeSelectorTerms: []v1.NodeSelectorTerm{
  385. {
  386. MatchFields: []v1.NodeSelectorRequirement{
  387. {
  388. Key: api.ObjectNameField,
  389. Operator: v1.NodeSelectorOpNotIn,
  390. Values: []string{"host_2"},
  391. },
  392. },
  393. },
  394. },
  395. },
  396. },
  397. },
  398. hostname: "host_1",
  399. expected: &v1.Affinity{
  400. NodeAffinity: &v1.NodeAffinity{
  401. RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
  402. NodeSelectorTerms: []v1.NodeSelectorTerm{
  403. {
  404. MatchFields: []v1.NodeSelectorRequirement{
  405. {
  406. Key: api.ObjectNameField,
  407. Operator: v1.NodeSelectorOpIn,
  408. Values: []string{"host_1"},
  409. },
  410. },
  411. },
  412. },
  413. },
  414. },
  415. },
  416. },
  417. {
  418. affinity: &v1.Affinity{
  419. NodeAffinity: &v1.NodeAffinity{
  420. RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
  421. NodeSelectorTerms: []v1.NodeSelectorTerm{
  422. {
  423. MatchFields: []v1.NodeSelectorRequirement{
  424. {
  425. // NOTE: Only `metadata.name` is valid key in `MatchFields` in 1.11;
  426. // added this case for compatibility: the feature works as normal
  427. // when new Keys introduced.
  428. Key: "metadata.foo",
  429. Operator: v1.NodeSelectorOpIn,
  430. Values: []string{"bar"},
  431. },
  432. },
  433. },
  434. },
  435. },
  436. },
  437. },
  438. hostname: "host_1",
  439. expected: &v1.Affinity{
  440. NodeAffinity: &v1.NodeAffinity{
  441. RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
  442. NodeSelectorTerms: []v1.NodeSelectorTerm{
  443. {
  444. MatchFields: []v1.NodeSelectorRequirement{
  445. {
  446. Key: api.ObjectNameField,
  447. Operator: v1.NodeSelectorOpIn,
  448. Values: []string{"host_1"},
  449. },
  450. },
  451. },
  452. },
  453. },
  454. },
  455. },
  456. },
  457. }
  458. for i, test := range tests {
  459. got := ReplaceDaemonSetPodNodeNameNodeAffinity(test.affinity, test.hostname)
  460. if !reflect.DeepEqual(test.expected, got) {
  461. t.Errorf("Failed to append NodeAffinity in case %d, got: %v, expected: %v",
  462. i, got, test.expected)
  463. }
  464. }
  465. }
  466. func forEachFeatureGate(t *testing.T, tf func(t *testing.T), gates ...featuregate.Feature) {
  467. for _, fg := range gates {
  468. for _, f := range []bool{true, false} {
  469. func() {
  470. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, fg, f)()
  471. t.Run(fmt.Sprintf("%v (%t)", fg, f), tf)
  472. }()
  473. }
  474. }
  475. }
  476. func TestGetTargetNodeName(t *testing.T) {
  477. testFun := func(t *testing.T) {
  478. tests := []struct {
  479. pod *v1.Pod
  480. nodeName string
  481. expectedErr bool
  482. }{
  483. {
  484. pod: &v1.Pod{
  485. ObjectMeta: metav1.ObjectMeta{
  486. Name: "pod1",
  487. Namespace: "default",
  488. },
  489. Spec: v1.PodSpec{
  490. NodeName: "node-1",
  491. },
  492. },
  493. nodeName: "node-1",
  494. },
  495. {
  496. pod: &v1.Pod{
  497. ObjectMeta: metav1.ObjectMeta{
  498. Name: "pod2",
  499. Namespace: "default",
  500. },
  501. Spec: v1.PodSpec{
  502. Affinity: &v1.Affinity{
  503. NodeAffinity: &v1.NodeAffinity{
  504. RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
  505. NodeSelectorTerms: []v1.NodeSelectorTerm{
  506. {
  507. MatchFields: []v1.NodeSelectorRequirement{
  508. {
  509. Key: api.ObjectNameField,
  510. Operator: v1.NodeSelectorOpIn,
  511. Values: []string{"node-1"},
  512. },
  513. },
  514. },
  515. },
  516. },
  517. },
  518. },
  519. },
  520. },
  521. nodeName: "node-1",
  522. },
  523. {
  524. pod: &v1.Pod{
  525. ObjectMeta: metav1.ObjectMeta{
  526. Name: "pod3",
  527. Namespace: "default",
  528. },
  529. Spec: v1.PodSpec{
  530. Affinity: &v1.Affinity{
  531. NodeAffinity: &v1.NodeAffinity{
  532. RequiredDuringSchedulingIgnoredDuringExecution: &v1.NodeSelector{
  533. NodeSelectorTerms: []v1.NodeSelectorTerm{
  534. {
  535. MatchFields: []v1.NodeSelectorRequirement{
  536. {
  537. Key: api.ObjectNameField,
  538. Operator: v1.NodeSelectorOpIn,
  539. Values: []string{"node-1", "node-2"},
  540. },
  541. },
  542. },
  543. },
  544. },
  545. },
  546. },
  547. },
  548. },
  549. expectedErr: true,
  550. },
  551. {
  552. pod: &v1.Pod{
  553. ObjectMeta: metav1.ObjectMeta{
  554. Name: "pod4",
  555. Namespace: "default",
  556. },
  557. Spec: v1.PodSpec{},
  558. },
  559. expectedErr: true,
  560. },
  561. }
  562. for _, test := range tests {
  563. got, err := GetTargetNodeName(test.pod)
  564. if test.expectedErr != (err != nil) {
  565. t.Errorf("Unexpected error, expectedErr: %v, err: %v", test.expectedErr, err)
  566. } else if !test.expectedErr {
  567. if test.nodeName != got {
  568. t.Errorf("Failed to get target node name, got: %v, expected: %v", got, test.nodeName)
  569. }
  570. }
  571. }
  572. }
  573. forEachFeatureGate(t, testFun)
  574. }