util_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  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. "io/ioutil"
  16. "os"
  17. "runtime"
  18. "testing"
  19. v1 "k8s.io/api/core/v1"
  20. _ "k8s.io/kubernetes/pkg/apis/core/install"
  21. "reflect"
  22. "strings"
  23. "k8s.io/apimachinery/pkg/api/resource"
  24. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  25. "k8s.io/kubernetes/pkg/util/slice"
  26. "k8s.io/kubernetes/pkg/volume"
  27. )
  28. var nodeLabels = map[string]string{
  29. "test-key1": "test-value1",
  30. "test-key2": "test-value2",
  31. }
  32. func TestCheckVolumeNodeAffinity(t *testing.T) {
  33. type affinityTest struct {
  34. name string
  35. expectSuccess bool
  36. pv *v1.PersistentVolume
  37. }
  38. cases := []affinityTest{
  39. {
  40. name: "valid-nil",
  41. expectSuccess: true,
  42. pv: testVolumeWithNodeAffinity(t, nil),
  43. },
  44. {
  45. name: "valid-no-constraints",
  46. expectSuccess: true,
  47. pv: testVolumeWithNodeAffinity(t, &v1.VolumeNodeAffinity{}),
  48. },
  49. {
  50. name: "select-nothing",
  51. expectSuccess: false,
  52. pv: testVolumeWithNodeAffinity(t, &v1.VolumeNodeAffinity{Required: &v1.NodeSelector{}}),
  53. },
  54. {
  55. name: "select-nothing-empty-terms",
  56. expectSuccess: false,
  57. pv: testVolumeWithNodeAffinity(t, &v1.VolumeNodeAffinity{
  58. Required: &v1.NodeSelector{
  59. NodeSelectorTerms: []v1.NodeSelectorTerm{
  60. {
  61. MatchExpressions: []v1.NodeSelectorRequirement{},
  62. },
  63. },
  64. },
  65. }),
  66. },
  67. {
  68. name: "valid-multiple-terms",
  69. expectSuccess: true,
  70. pv: testVolumeWithNodeAffinity(t, &v1.VolumeNodeAffinity{
  71. Required: &v1.NodeSelector{
  72. NodeSelectorTerms: []v1.NodeSelectorTerm{
  73. {
  74. MatchExpressions: []v1.NodeSelectorRequirement{
  75. {
  76. Key: "test-key3",
  77. Operator: v1.NodeSelectorOpIn,
  78. Values: []string{"test-value1", "test-value3"},
  79. },
  80. },
  81. },
  82. {
  83. MatchExpressions: []v1.NodeSelectorRequirement{
  84. {
  85. Key: "test-key2",
  86. Operator: v1.NodeSelectorOpIn,
  87. Values: []string{"test-value0", "test-value2"},
  88. },
  89. },
  90. },
  91. },
  92. },
  93. }),
  94. },
  95. {
  96. name: "valid-multiple-match-expressions",
  97. expectSuccess: true,
  98. pv: testVolumeWithNodeAffinity(t, &v1.VolumeNodeAffinity{
  99. Required: &v1.NodeSelector{
  100. NodeSelectorTerms: []v1.NodeSelectorTerm{
  101. {
  102. MatchExpressions: []v1.NodeSelectorRequirement{
  103. {
  104. Key: "test-key1",
  105. Operator: v1.NodeSelectorOpIn,
  106. Values: []string{"test-value1", "test-value3"},
  107. },
  108. {
  109. Key: "test-key2",
  110. Operator: v1.NodeSelectorOpIn,
  111. Values: []string{"test-value0", "test-value2"},
  112. },
  113. },
  114. },
  115. },
  116. },
  117. }),
  118. },
  119. {
  120. name: "invalid-multiple-match-expressions-key",
  121. expectSuccess: false,
  122. pv: testVolumeWithNodeAffinity(t, &v1.VolumeNodeAffinity{
  123. Required: &v1.NodeSelector{
  124. NodeSelectorTerms: []v1.NodeSelectorTerm{
  125. {
  126. MatchExpressions: []v1.NodeSelectorRequirement{
  127. {
  128. Key: "test-key1",
  129. Operator: v1.NodeSelectorOpIn,
  130. Values: []string{"test-value1", "test-value3"},
  131. },
  132. {
  133. Key: "test-key3",
  134. Operator: v1.NodeSelectorOpIn,
  135. Values: []string{"test-value0", "test-value2"},
  136. },
  137. },
  138. },
  139. },
  140. },
  141. }),
  142. },
  143. {
  144. name: "invalid-multiple-match-expressions-values",
  145. expectSuccess: false,
  146. pv: testVolumeWithNodeAffinity(t, &v1.VolumeNodeAffinity{
  147. Required: &v1.NodeSelector{
  148. NodeSelectorTerms: []v1.NodeSelectorTerm{
  149. {
  150. MatchExpressions: []v1.NodeSelectorRequirement{
  151. {
  152. Key: "test-key1",
  153. Operator: v1.NodeSelectorOpIn,
  154. Values: []string{"test-value3", "test-value4"},
  155. },
  156. {
  157. Key: "test-key2",
  158. Operator: v1.NodeSelectorOpIn,
  159. Values: []string{"test-value0", "test-value2"},
  160. },
  161. },
  162. },
  163. },
  164. },
  165. }),
  166. },
  167. {
  168. name: "invalid-multiple-terms",
  169. expectSuccess: false,
  170. pv: testVolumeWithNodeAffinity(t, &v1.VolumeNodeAffinity{
  171. Required: &v1.NodeSelector{
  172. NodeSelectorTerms: []v1.NodeSelectorTerm{
  173. {
  174. MatchExpressions: []v1.NodeSelectorRequirement{
  175. {
  176. Key: "test-key3",
  177. Operator: v1.NodeSelectorOpIn,
  178. Values: []string{"test-value1", "test-value3"},
  179. },
  180. },
  181. },
  182. {
  183. MatchExpressions: []v1.NodeSelectorRequirement{
  184. {
  185. Key: "test-key2",
  186. Operator: v1.NodeSelectorOpIn,
  187. Values: []string{"test-value0", "test-value1"},
  188. },
  189. },
  190. },
  191. },
  192. },
  193. }),
  194. },
  195. }
  196. for _, c := range cases {
  197. err := CheckNodeAffinity(c.pv, nodeLabels)
  198. if err != nil && c.expectSuccess {
  199. t.Errorf("CheckTopology %v returned error: %v", c.name, err)
  200. }
  201. if err == nil && !c.expectSuccess {
  202. t.Errorf("CheckTopology %v returned success, expected error", c.name)
  203. }
  204. }
  205. }
  206. func testVolumeWithNodeAffinity(t *testing.T, affinity *v1.VolumeNodeAffinity) *v1.PersistentVolume {
  207. objMeta := metav1.ObjectMeta{Name: "test-constraints"}
  208. return &v1.PersistentVolume{
  209. ObjectMeta: objMeta,
  210. Spec: v1.PersistentVolumeSpec{
  211. NodeAffinity: affinity,
  212. },
  213. }
  214. }
  215. func TestLoadPodFromFile(t *testing.T) {
  216. tests := []struct {
  217. name string
  218. content string
  219. expectError bool
  220. }{
  221. {
  222. "yaml",
  223. `
  224. apiVersion: v1
  225. kind: Pod
  226. metadata:
  227. name: testpod
  228. spec:
  229. containers:
  230. - image: k8s.gcr.io/busybox
  231. `,
  232. false,
  233. },
  234. {
  235. "json",
  236. `
  237. {
  238. "apiVersion": "v1",
  239. "kind": "Pod",
  240. "metadata": {
  241. "name": "testpod"
  242. },
  243. "spec": {
  244. "containers": [
  245. {
  246. "image": "k8s.gcr.io/busybox"
  247. }
  248. ]
  249. }
  250. }`,
  251. false,
  252. },
  253. {
  254. "invalid pod",
  255. `
  256. apiVersion: v1
  257. kind: Pod
  258. metadata:
  259. name: testpod
  260. spec:
  261. - image: k8s.gcr.io/busybox
  262. `,
  263. true,
  264. },
  265. }
  266. for _, test := range tests {
  267. tempFile, err := ioutil.TempFile("", "podfile")
  268. defer os.Remove(tempFile.Name())
  269. if err != nil {
  270. t.Fatalf("cannot create temporary file: %v", err)
  271. }
  272. if _, err = tempFile.Write([]byte(test.content)); err != nil {
  273. t.Fatalf("cannot save temporary file: %v", err)
  274. }
  275. if err = tempFile.Close(); err != nil {
  276. t.Fatalf("cannot close temporary file: %v", err)
  277. }
  278. pod, err := LoadPodFromFile(tempFile.Name())
  279. if test.expectError {
  280. if err == nil {
  281. t.Errorf("test %q expected error, got nil", test.name)
  282. }
  283. } else {
  284. // no error expected
  285. if err != nil {
  286. t.Errorf("error loading pod %q: %v", test.name, err)
  287. }
  288. if pod == nil {
  289. t.Errorf("test %q expected pod, got nil", test.name)
  290. }
  291. }
  292. }
  293. }
  294. func TestCalculateTimeoutForVolume(t *testing.T) {
  295. pv := &v1.PersistentVolume{
  296. Spec: v1.PersistentVolumeSpec{
  297. Capacity: v1.ResourceList{
  298. v1.ResourceName(v1.ResourceStorage): resource.MustParse("500M"),
  299. },
  300. },
  301. }
  302. timeout := CalculateTimeoutForVolume(50, 30, pv)
  303. if timeout != 50 {
  304. t.Errorf("Expected 50 for timeout but got %v", timeout)
  305. }
  306. pv.Spec.Capacity[v1.ResourceStorage] = resource.MustParse("2Gi")
  307. timeout = CalculateTimeoutForVolume(50, 30, pv)
  308. if timeout != 60 {
  309. t.Errorf("Expected 60 for timeout but got %v", timeout)
  310. }
  311. pv.Spec.Capacity[v1.ResourceStorage] = resource.MustParse("150Gi")
  312. timeout = CalculateTimeoutForVolume(50, 30, pv)
  313. if timeout != 4500 {
  314. t.Errorf("Expected 4500 for timeout but got %v", timeout)
  315. }
  316. }
  317. func TestGenerateVolumeName(t *testing.T) {
  318. // Normal operation, no truncate
  319. v1 := GenerateVolumeName("kubernetes", "pv-cinder-abcde", 255)
  320. if v1 != "kubernetes-dynamic-pv-cinder-abcde" {
  321. t.Errorf("Expected kubernetes-dynamic-pv-cinder-abcde, got %s", v1)
  322. }
  323. // Truncate trailing "6789-dynamic"
  324. prefix := strings.Repeat("0123456789", 9) // 90 characters prefix + 8 chars. of "-dynamic"
  325. v2 := GenerateVolumeName(prefix, "pv-cinder-abcde", 100)
  326. expect := prefix[:84] + "-pv-cinder-abcde"
  327. if v2 != expect {
  328. t.Errorf("Expected %s, got %s", expect, v2)
  329. }
  330. // Truncate really long cluster name
  331. prefix = strings.Repeat("0123456789", 1000) // 10000 characters prefix
  332. v3 := GenerateVolumeName(prefix, "pv-cinder-abcde", 100)
  333. if v3 != expect {
  334. t.Errorf("Expected %s, got %s", expect, v3)
  335. }
  336. }
  337. func TestMountOptionFromSpec(t *testing.T) {
  338. scenarios := map[string]struct {
  339. volume *volume.Spec
  340. expectedMountList []string
  341. systemOptions []string
  342. }{
  343. "volume-with-mount-options": {
  344. volume: createVolumeSpecWithMountOption("good-mount-opts", "ro,nfsvers=3", v1.PersistentVolumeSpec{
  345. PersistentVolumeSource: v1.PersistentVolumeSource{
  346. NFS: &v1.NFSVolumeSource{Server: "localhost", Path: "/srv", ReadOnly: false},
  347. },
  348. }),
  349. expectedMountList: []string{"ro", "nfsvers=3"},
  350. systemOptions: nil,
  351. },
  352. "volume-with-bad-mount-options": {
  353. volume: createVolumeSpecWithMountOption("good-mount-opts", "", v1.PersistentVolumeSpec{
  354. PersistentVolumeSource: v1.PersistentVolumeSource{
  355. NFS: &v1.NFSVolumeSource{Server: "localhost", Path: "/srv", ReadOnly: false},
  356. },
  357. }),
  358. expectedMountList: []string{},
  359. systemOptions: nil,
  360. },
  361. "vol-with-sys-opts": {
  362. volume: createVolumeSpecWithMountOption("good-mount-opts", "ro,nfsvers=3", v1.PersistentVolumeSpec{
  363. PersistentVolumeSource: v1.PersistentVolumeSource{
  364. NFS: &v1.NFSVolumeSource{Server: "localhost", Path: "/srv", ReadOnly: false},
  365. },
  366. }),
  367. expectedMountList: []string{"ro", "nfsvers=3", "fsid=100", "hard"},
  368. systemOptions: []string{"fsid=100", "hard"},
  369. },
  370. "vol-with-sys-opts-with-dup": {
  371. volume: createVolumeSpecWithMountOption("good-mount-opts", "ro,nfsvers=3", v1.PersistentVolumeSpec{
  372. PersistentVolumeSource: v1.PersistentVolumeSource{
  373. NFS: &v1.NFSVolumeSource{Server: "localhost", Path: "/srv", ReadOnly: false},
  374. },
  375. }),
  376. expectedMountList: []string{"ro", "nfsvers=3", "fsid=100"},
  377. systemOptions: []string{"fsid=100", "ro"},
  378. },
  379. }
  380. for name, scenario := range scenarios {
  381. mountOptions := MountOptionFromSpec(scenario.volume, scenario.systemOptions...)
  382. if !reflect.DeepEqual(slice.SortStrings(mountOptions), slice.SortStrings(scenario.expectedMountList)) {
  383. t.Errorf("for %s expected mount options : %v got %v", name, scenario.expectedMountList, mountOptions)
  384. }
  385. }
  386. }
  387. func createVolumeSpecWithMountOption(name string, mountOptions string, spec v1.PersistentVolumeSpec) *volume.Spec {
  388. annotations := map[string]string{
  389. v1.MountOptionAnnotation: mountOptions,
  390. }
  391. objMeta := metav1.ObjectMeta{
  392. Name: name,
  393. Annotations: annotations,
  394. }
  395. pv := &v1.PersistentVolume{
  396. ObjectMeta: objMeta,
  397. Spec: spec,
  398. }
  399. return &volume.Spec{PersistentVolume: pv}
  400. }
  401. func TestGetWindowsPath(t *testing.T) {
  402. tests := []struct {
  403. path string
  404. expectedPath string
  405. }{
  406. {
  407. path: `/var/lib/kubelet/pods/146f8428-83e7-11e7-8dd4-000d3a31dac4/volumes/kubernetes.io~disk`,
  408. expectedPath: `c:\var\lib\kubelet\pods\146f8428-83e7-11e7-8dd4-000d3a31dac4\volumes\kubernetes.io~disk`,
  409. },
  410. {
  411. path: `\var/lib/kubelet/pods/146f8428-83e7-11e7-8dd4-000d3a31dac4\volumes\kubernetes.io~disk`,
  412. expectedPath: `c:\var\lib\kubelet\pods\146f8428-83e7-11e7-8dd4-000d3a31dac4\volumes\kubernetes.io~disk`,
  413. },
  414. {
  415. path: `/`,
  416. expectedPath: `c:\`,
  417. },
  418. {
  419. path: ``,
  420. expectedPath: ``,
  421. },
  422. }
  423. for _, test := range tests {
  424. result := GetWindowsPath(test.path)
  425. if result != test.expectedPath {
  426. t.Errorf("GetWindowsPath(%v) returned (%v), want (%v)", test.path, result, test.expectedPath)
  427. }
  428. }
  429. }
  430. func TestIsWindowsUNCPath(t *testing.T) {
  431. tests := []struct {
  432. goos string
  433. path string
  434. isUNCPath bool
  435. }{
  436. {
  437. goos: "linux",
  438. path: `/usr/bin`,
  439. isUNCPath: false,
  440. },
  441. {
  442. goos: "linux",
  443. path: `\\.\pipe\foo`,
  444. isUNCPath: false,
  445. },
  446. {
  447. goos: "windows",
  448. path: `C:\foo`,
  449. isUNCPath: false,
  450. },
  451. {
  452. goos: "windows",
  453. path: `\\server\share\foo`,
  454. isUNCPath: true,
  455. },
  456. {
  457. goos: "windows",
  458. path: `\\?\server\share`,
  459. isUNCPath: true,
  460. },
  461. {
  462. goos: "windows",
  463. path: `\\?\c:\`,
  464. isUNCPath: true,
  465. },
  466. {
  467. goos: "windows",
  468. path: `\\.\pipe\valid_pipe`,
  469. isUNCPath: true,
  470. },
  471. }
  472. for _, test := range tests {
  473. result := IsWindowsUNCPath(test.goos, test.path)
  474. if result != test.isUNCPath {
  475. t.Errorf("IsWindowsUNCPath(%v) returned (%v), expected (%v)", test.path, result, test.isUNCPath)
  476. }
  477. }
  478. }
  479. func TestIsWindowsLocalPath(t *testing.T) {
  480. tests := []struct {
  481. goos string
  482. path string
  483. isWindowsLocalPath bool
  484. }{
  485. {
  486. goos: "linux",
  487. path: `/usr/bin`,
  488. isWindowsLocalPath: false,
  489. },
  490. {
  491. goos: "linux",
  492. path: `\\.\pipe\foo`,
  493. isWindowsLocalPath: false,
  494. },
  495. {
  496. goos: "windows",
  497. path: `C:\foo`,
  498. isWindowsLocalPath: false,
  499. },
  500. {
  501. goos: "windows",
  502. path: `:\foo`,
  503. isWindowsLocalPath: false,
  504. },
  505. {
  506. goos: "windows",
  507. path: `X:\foo`,
  508. isWindowsLocalPath: false,
  509. },
  510. {
  511. goos: "windows",
  512. path: `\\server\share\foo`,
  513. isWindowsLocalPath: false,
  514. },
  515. {
  516. goos: "windows",
  517. path: `\\?\server\share`,
  518. isWindowsLocalPath: false,
  519. },
  520. {
  521. goos: "windows",
  522. path: `\\?\c:\`,
  523. isWindowsLocalPath: false,
  524. },
  525. {
  526. goos: "windows",
  527. path: `\\.\pipe\valid_pipe`,
  528. isWindowsLocalPath: false,
  529. },
  530. {
  531. goos: "windows",
  532. path: `foo`,
  533. isWindowsLocalPath: false,
  534. },
  535. {
  536. goos: "windows",
  537. path: `:foo`,
  538. isWindowsLocalPath: false,
  539. },
  540. {
  541. goos: "windows",
  542. path: `\foo`,
  543. isWindowsLocalPath: true,
  544. },
  545. {
  546. goos: "windows",
  547. path: `\foo\bar`,
  548. isWindowsLocalPath: true,
  549. },
  550. {
  551. goos: "windows",
  552. path: `/foo`,
  553. isWindowsLocalPath: true,
  554. },
  555. {
  556. goos: "windows",
  557. path: `/foo/bar`,
  558. isWindowsLocalPath: true,
  559. },
  560. }
  561. for _, test := range tests {
  562. result := IsWindowsLocalPath(test.goos, test.path)
  563. if result != test.isWindowsLocalPath {
  564. t.Errorf("isWindowsLocalPath(%v) returned (%v), expected (%v)", test.path, result, test.isWindowsLocalPath)
  565. }
  566. }
  567. }
  568. func TestMakeAbsolutePath(t *testing.T) {
  569. tests := []struct {
  570. goos string
  571. path string
  572. expectedPath string
  573. name string
  574. }{
  575. {
  576. goos: "linux",
  577. path: "non-absolute/path",
  578. expectedPath: "/non-absolute/path",
  579. name: "linux non-absolute path",
  580. },
  581. {
  582. goos: "linux",
  583. path: "/absolute/path",
  584. expectedPath: "/absolute/path",
  585. name: "linux absolute path",
  586. },
  587. {
  588. goos: "windows",
  589. path: "some\\path",
  590. expectedPath: "c:\\some\\path",
  591. name: "basic windows",
  592. },
  593. {
  594. goos: "windows",
  595. path: "/some/path",
  596. expectedPath: "c:/some/path",
  597. name: "linux path on windows",
  598. },
  599. {
  600. goos: "windows",
  601. path: "\\some\\path",
  602. expectedPath: "c:\\some\\path",
  603. name: "windows path no drive",
  604. },
  605. {
  606. goos: "windows",
  607. path: "\\:\\some\\path",
  608. expectedPath: "\\:\\some\\path",
  609. name: "windows path with colon",
  610. },
  611. }
  612. for _, test := range tests {
  613. if runtime.GOOS == test.goos {
  614. path := MakeAbsolutePath(test.goos, test.path)
  615. if path != test.expectedPath {
  616. t.Errorf("[%s] Expected %s saw %s", test.name, test.expectedPath, path)
  617. }
  618. }
  619. }
  620. }