admission_test.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806
  1. /*
  2. Copyright 2015 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 label
  14. import (
  15. "context"
  16. "errors"
  17. "reflect"
  18. "sort"
  19. "testing"
  20. v1 "k8s.io/api/core/v1"
  21. apierrors "k8s.io/apimachinery/pkg/api/errors"
  22. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  23. "k8s.io/apimachinery/pkg/runtime/schema"
  24. "k8s.io/apiserver/pkg/admission"
  25. admissiontesting "k8s.io/apiserver/pkg/admission/testing"
  26. cloudprovider "k8s.io/cloud-provider"
  27. api "k8s.io/kubernetes/pkg/apis/core"
  28. )
  29. type mockVolumes struct {
  30. volumeLabels map[string]string
  31. volumeLabelsError error
  32. }
  33. var _ cloudprovider.PVLabeler = &mockVolumes{}
  34. func (v *mockVolumes) GetLabelsForVolume(ctx context.Context, pv *v1.PersistentVolume) (map[string]string, error) {
  35. return v.volumeLabels, v.volumeLabelsError
  36. }
  37. func mockVolumeFailure(err error) *mockVolumes {
  38. return &mockVolumes{volumeLabelsError: err}
  39. }
  40. func mockVolumeLabels(labels map[string]string) *mockVolumes {
  41. return &mockVolumes{volumeLabels: labels}
  42. }
  43. func Test_PVLAdmission(t *testing.T) {
  44. testcases := []struct {
  45. name string
  46. handler *persistentVolumeLabel
  47. pvlabeler cloudprovider.PVLabeler
  48. preAdmissionPV *api.PersistentVolume
  49. postAdmissionPV *api.PersistentVolume
  50. err error
  51. }{
  52. {
  53. name: "non-cloud PV ignored",
  54. handler: newPersistentVolumeLabel(),
  55. pvlabeler: mockVolumeLabels(map[string]string{
  56. "a": "1",
  57. "b": "2",
  58. v1.LabelZoneFailureDomain: "1__2__3",
  59. }),
  60. preAdmissionPV: &api.PersistentVolume{
  61. ObjectMeta: metav1.ObjectMeta{Name: "noncloud", Namespace: "myns"},
  62. Spec: api.PersistentVolumeSpec{
  63. PersistentVolumeSource: api.PersistentVolumeSource{
  64. HostPath: &api.HostPathVolumeSource{
  65. Path: "/",
  66. },
  67. },
  68. },
  69. },
  70. postAdmissionPV: &api.PersistentVolume{
  71. ObjectMeta: metav1.ObjectMeta{Name: "noncloud", Namespace: "myns"},
  72. Spec: api.PersistentVolumeSpec{
  73. PersistentVolumeSource: api.PersistentVolumeSource{
  74. HostPath: &api.HostPathVolumeSource{
  75. Path: "/",
  76. },
  77. },
  78. },
  79. },
  80. err: nil,
  81. },
  82. {
  83. name: "cloud provider error blocks creation of volume",
  84. handler: newPersistentVolumeLabel(),
  85. pvlabeler: mockVolumeFailure(errors.New("invalid volume")),
  86. preAdmissionPV: &api.PersistentVolume{
  87. ObjectMeta: metav1.ObjectMeta{Name: "awsebs", Namespace: "myns"},
  88. Spec: api.PersistentVolumeSpec{
  89. PersistentVolumeSource: api.PersistentVolumeSource{
  90. AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
  91. VolumeID: "123",
  92. },
  93. },
  94. },
  95. },
  96. postAdmissionPV: &api.PersistentVolume{
  97. ObjectMeta: metav1.ObjectMeta{Name: "awsebs", Namespace: "myns"},
  98. Spec: api.PersistentVolumeSpec{
  99. PersistentVolumeSource: api.PersistentVolumeSource{
  100. AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
  101. VolumeID: "123",
  102. },
  103. },
  104. },
  105. },
  106. err: apierrors.NewForbidden(schema.ParseGroupResource("persistentvolumes"), "awsebs", errors.New("error querying AWS EBS volume 123: invalid volume")),
  107. },
  108. {
  109. name: "cloud provider returns no labels",
  110. handler: newPersistentVolumeLabel(),
  111. pvlabeler: mockVolumeLabels(map[string]string{}),
  112. preAdmissionPV: &api.PersistentVolume{
  113. ObjectMeta: metav1.ObjectMeta{Name: "awsebs", Namespace: "myns"},
  114. Spec: api.PersistentVolumeSpec{
  115. PersistentVolumeSource: api.PersistentVolumeSource{
  116. AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
  117. VolumeID: "123",
  118. },
  119. },
  120. },
  121. },
  122. postAdmissionPV: &api.PersistentVolume{
  123. ObjectMeta: metav1.ObjectMeta{Name: "awsebs", Namespace: "myns"},
  124. Spec: api.PersistentVolumeSpec{
  125. PersistentVolumeSource: api.PersistentVolumeSource{
  126. AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
  127. VolumeID: "123",
  128. },
  129. },
  130. },
  131. },
  132. err: nil,
  133. },
  134. {
  135. name: "cloud provider returns nil, nil",
  136. handler: newPersistentVolumeLabel(),
  137. pvlabeler: mockVolumeFailure(nil),
  138. preAdmissionPV: &api.PersistentVolume{
  139. ObjectMeta: metav1.ObjectMeta{Name: "awsebs", Namespace: "myns"},
  140. Spec: api.PersistentVolumeSpec{
  141. PersistentVolumeSource: api.PersistentVolumeSource{
  142. AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
  143. VolumeID: "123",
  144. },
  145. },
  146. },
  147. },
  148. postAdmissionPV: &api.PersistentVolume{
  149. ObjectMeta: metav1.ObjectMeta{Name: "awsebs", Namespace: "myns"},
  150. Spec: api.PersistentVolumeSpec{
  151. PersistentVolumeSource: api.PersistentVolumeSource{
  152. AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
  153. VolumeID: "123",
  154. },
  155. },
  156. },
  157. },
  158. err: nil,
  159. },
  160. {
  161. name: "AWS EBS PV labeled correctly",
  162. handler: newPersistentVolumeLabel(),
  163. pvlabeler: mockVolumeLabels(map[string]string{
  164. "a": "1",
  165. "b": "2",
  166. v1.LabelZoneFailureDomain: "1__2__3",
  167. }),
  168. preAdmissionPV: &api.PersistentVolume{
  169. ObjectMeta: metav1.ObjectMeta{Name: "awsebs", Namespace: "myns"},
  170. Spec: api.PersistentVolumeSpec{
  171. PersistentVolumeSource: api.PersistentVolumeSource{
  172. AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
  173. VolumeID: "123",
  174. },
  175. },
  176. },
  177. },
  178. postAdmissionPV: &api.PersistentVolume{
  179. ObjectMeta: metav1.ObjectMeta{
  180. Name: "awsebs",
  181. Namespace: "myns",
  182. Labels: map[string]string{
  183. "a": "1",
  184. "b": "2",
  185. v1.LabelZoneFailureDomain: "1__2__3",
  186. },
  187. },
  188. Spec: api.PersistentVolumeSpec{
  189. PersistentVolumeSource: api.PersistentVolumeSource{
  190. AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
  191. VolumeID: "123",
  192. },
  193. },
  194. NodeAffinity: &api.VolumeNodeAffinity{
  195. Required: &api.NodeSelector{
  196. NodeSelectorTerms: []api.NodeSelectorTerm{
  197. {
  198. MatchExpressions: []api.NodeSelectorRequirement{
  199. {
  200. Key: "a",
  201. Operator: api.NodeSelectorOpIn,
  202. Values: []string{"1"},
  203. },
  204. {
  205. Key: "b",
  206. Operator: api.NodeSelectorOpIn,
  207. Values: []string{"2"},
  208. },
  209. {
  210. Key: v1.LabelZoneFailureDomain,
  211. Operator: api.NodeSelectorOpIn,
  212. Values: []string{"1", "2", "3"},
  213. },
  214. },
  215. },
  216. },
  217. },
  218. },
  219. },
  220. },
  221. err: nil,
  222. },
  223. {
  224. name: "GCE PD PV labeled correctly",
  225. handler: newPersistentVolumeLabel(),
  226. pvlabeler: mockVolumeLabels(map[string]string{
  227. "a": "1",
  228. "b": "2",
  229. v1.LabelZoneFailureDomain: "1__2__3",
  230. }),
  231. preAdmissionPV: &api.PersistentVolume{
  232. ObjectMeta: metav1.ObjectMeta{Name: "gcepd", Namespace: "myns"},
  233. Spec: api.PersistentVolumeSpec{
  234. PersistentVolumeSource: api.PersistentVolumeSource{
  235. GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{
  236. PDName: "123",
  237. },
  238. },
  239. },
  240. },
  241. postAdmissionPV: &api.PersistentVolume{
  242. ObjectMeta: metav1.ObjectMeta{
  243. Name: "gcepd",
  244. Namespace: "myns",
  245. Labels: map[string]string{
  246. "a": "1",
  247. "b": "2",
  248. v1.LabelZoneFailureDomain: "1__2__3",
  249. },
  250. },
  251. Spec: api.PersistentVolumeSpec{
  252. PersistentVolumeSource: api.PersistentVolumeSource{
  253. GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{
  254. PDName: "123",
  255. },
  256. },
  257. NodeAffinity: &api.VolumeNodeAffinity{
  258. Required: &api.NodeSelector{
  259. NodeSelectorTerms: []api.NodeSelectorTerm{
  260. {
  261. MatchExpressions: []api.NodeSelectorRequirement{
  262. {
  263. Key: "a",
  264. Operator: api.NodeSelectorOpIn,
  265. Values: []string{"1"},
  266. },
  267. {
  268. Key: "b",
  269. Operator: api.NodeSelectorOpIn,
  270. Values: []string{"2"},
  271. },
  272. {
  273. Key: v1.LabelZoneFailureDomain,
  274. Operator: api.NodeSelectorOpIn,
  275. Values: []string{"1", "2", "3"},
  276. },
  277. },
  278. },
  279. },
  280. },
  281. },
  282. },
  283. },
  284. err: nil,
  285. },
  286. {
  287. name: "Azure Disk PV labeled correctly",
  288. handler: newPersistentVolumeLabel(),
  289. pvlabeler: mockVolumeLabels(map[string]string{
  290. "a": "1",
  291. "b": "2",
  292. v1.LabelZoneFailureDomain: "1__2__3",
  293. }),
  294. preAdmissionPV: &api.PersistentVolume{
  295. ObjectMeta: metav1.ObjectMeta{
  296. Name: "azurepd",
  297. Namespace: "myns",
  298. },
  299. Spec: api.PersistentVolumeSpec{
  300. PersistentVolumeSource: api.PersistentVolumeSource{
  301. AzureDisk: &api.AzureDiskVolumeSource{
  302. DiskName: "123",
  303. },
  304. },
  305. },
  306. },
  307. postAdmissionPV: &api.PersistentVolume{
  308. ObjectMeta: metav1.ObjectMeta{
  309. Name: "azurepd",
  310. Namespace: "myns",
  311. Labels: map[string]string{
  312. "a": "1",
  313. "b": "2",
  314. v1.LabelZoneFailureDomain: "1__2__3",
  315. },
  316. },
  317. Spec: api.PersistentVolumeSpec{
  318. PersistentVolumeSource: api.PersistentVolumeSource{
  319. AzureDisk: &api.AzureDiskVolumeSource{
  320. DiskName: "123",
  321. },
  322. },
  323. NodeAffinity: &api.VolumeNodeAffinity{
  324. Required: &api.NodeSelector{
  325. NodeSelectorTerms: []api.NodeSelectorTerm{
  326. {
  327. MatchExpressions: []api.NodeSelectorRequirement{
  328. {
  329. Key: "a",
  330. Operator: api.NodeSelectorOpIn,
  331. Values: []string{"1"},
  332. },
  333. {
  334. Key: "b",
  335. Operator: api.NodeSelectorOpIn,
  336. Values: []string{"2"},
  337. },
  338. {
  339. Key: v1.LabelZoneFailureDomain,
  340. Operator: api.NodeSelectorOpIn,
  341. Values: []string{"1", "2", "3"},
  342. },
  343. },
  344. },
  345. },
  346. },
  347. },
  348. },
  349. },
  350. err: nil,
  351. },
  352. {
  353. name: "Cinder Disk PV labeled correctly",
  354. handler: newPersistentVolumeLabel(),
  355. pvlabeler: mockVolumeLabels(map[string]string{
  356. "a": "1",
  357. "b": "2",
  358. v1.LabelZoneFailureDomain: "1__2__3",
  359. }),
  360. preAdmissionPV: &api.PersistentVolume{
  361. ObjectMeta: metav1.ObjectMeta{
  362. Name: "azurepd",
  363. Namespace: "myns",
  364. },
  365. Spec: api.PersistentVolumeSpec{
  366. PersistentVolumeSource: api.PersistentVolumeSource{
  367. Cinder: &api.CinderPersistentVolumeSource{
  368. VolumeID: "123",
  369. },
  370. },
  371. },
  372. },
  373. postAdmissionPV: &api.PersistentVolume{
  374. ObjectMeta: metav1.ObjectMeta{
  375. Name: "azurepd",
  376. Namespace: "myns",
  377. Labels: map[string]string{
  378. "a": "1",
  379. "b": "2",
  380. v1.LabelZoneFailureDomain: "1__2__3",
  381. },
  382. },
  383. Spec: api.PersistentVolumeSpec{
  384. PersistentVolumeSource: api.PersistentVolumeSource{
  385. Cinder: &api.CinderPersistentVolumeSource{
  386. VolumeID: "123",
  387. },
  388. },
  389. NodeAffinity: &api.VolumeNodeAffinity{
  390. Required: &api.NodeSelector{
  391. NodeSelectorTerms: []api.NodeSelectorTerm{
  392. {
  393. MatchExpressions: []api.NodeSelectorRequirement{
  394. {
  395. Key: "a",
  396. Operator: api.NodeSelectorOpIn,
  397. Values: []string{"1"},
  398. },
  399. {
  400. Key: "b",
  401. Operator: api.NodeSelectorOpIn,
  402. Values: []string{"2"},
  403. },
  404. {
  405. Key: v1.LabelZoneFailureDomain,
  406. Operator: api.NodeSelectorOpIn,
  407. Values: []string{"1", "2", "3"},
  408. },
  409. },
  410. },
  411. },
  412. },
  413. },
  414. },
  415. },
  416. err: nil,
  417. },
  418. {
  419. name: "AWS EBS PV overrides user applied labels",
  420. handler: newPersistentVolumeLabel(),
  421. pvlabeler: mockVolumeLabels(map[string]string{
  422. "a": "1",
  423. "b": "2",
  424. v1.LabelZoneFailureDomain: "1__2__3",
  425. }),
  426. preAdmissionPV: &api.PersistentVolume{
  427. ObjectMeta: metav1.ObjectMeta{
  428. Name: "awsebs",
  429. Namespace: "myns",
  430. Labels: map[string]string{
  431. "a": "not1",
  432. },
  433. },
  434. Spec: api.PersistentVolumeSpec{
  435. PersistentVolumeSource: api.PersistentVolumeSource{
  436. AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
  437. VolumeID: "123",
  438. },
  439. },
  440. },
  441. },
  442. postAdmissionPV: &api.PersistentVolume{
  443. ObjectMeta: metav1.ObjectMeta{
  444. Name: "awsebs",
  445. Namespace: "myns",
  446. Labels: map[string]string{
  447. "a": "1",
  448. "b": "2",
  449. v1.LabelZoneFailureDomain: "1__2__3",
  450. },
  451. },
  452. Spec: api.PersistentVolumeSpec{
  453. PersistentVolumeSource: api.PersistentVolumeSource{
  454. AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
  455. VolumeID: "123",
  456. },
  457. },
  458. NodeAffinity: &api.VolumeNodeAffinity{
  459. Required: &api.NodeSelector{
  460. NodeSelectorTerms: []api.NodeSelectorTerm{
  461. {
  462. MatchExpressions: []api.NodeSelectorRequirement{
  463. {
  464. Key: "a",
  465. Operator: api.NodeSelectorOpIn,
  466. Values: []string{"1"},
  467. },
  468. {
  469. Key: "b",
  470. Operator: api.NodeSelectorOpIn,
  471. Values: []string{"2"},
  472. },
  473. {
  474. Key: v1.LabelZoneFailureDomain,
  475. Operator: api.NodeSelectorOpIn,
  476. Values: []string{"1", "2", "3"},
  477. },
  478. },
  479. },
  480. },
  481. },
  482. },
  483. },
  484. },
  485. err: nil,
  486. },
  487. {
  488. name: "AWS EBS PV conflicting affinity rules left in-tact",
  489. handler: newPersistentVolumeLabel(),
  490. pvlabeler: mockVolumeLabels(map[string]string{
  491. "a": "1",
  492. "b": "2",
  493. "c": "3",
  494. }),
  495. preAdmissionPV: &api.PersistentVolume{
  496. ObjectMeta: metav1.ObjectMeta{
  497. Name: "awsebs",
  498. Namespace: "myns",
  499. Labels: map[string]string{
  500. "c": "3",
  501. },
  502. },
  503. Spec: api.PersistentVolumeSpec{
  504. PersistentVolumeSource: api.PersistentVolumeSource{
  505. AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
  506. VolumeID: "123",
  507. },
  508. },
  509. NodeAffinity: &api.VolumeNodeAffinity{
  510. Required: &api.NodeSelector{
  511. NodeSelectorTerms: []api.NodeSelectorTerm{
  512. {
  513. MatchExpressions: []api.NodeSelectorRequirement{
  514. {
  515. Key: "c",
  516. Operator: api.NodeSelectorOpIn,
  517. Values: []string{"3"},
  518. },
  519. },
  520. },
  521. },
  522. },
  523. },
  524. },
  525. },
  526. postAdmissionPV: &api.PersistentVolume{
  527. ObjectMeta: metav1.ObjectMeta{
  528. Name: "awsebs",
  529. Namespace: "myns",
  530. Labels: map[string]string{
  531. "a": "1",
  532. "b": "2",
  533. "c": "3",
  534. },
  535. },
  536. Spec: api.PersistentVolumeSpec{
  537. PersistentVolumeSource: api.PersistentVolumeSource{
  538. AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
  539. VolumeID: "123",
  540. },
  541. },
  542. NodeAffinity: &api.VolumeNodeAffinity{
  543. Required: &api.NodeSelector{
  544. NodeSelectorTerms: []api.NodeSelectorTerm{
  545. {
  546. MatchExpressions: []api.NodeSelectorRequirement{
  547. {
  548. Key: "c",
  549. Operator: api.NodeSelectorOpIn,
  550. Values: []string{"3"},
  551. },
  552. },
  553. },
  554. },
  555. },
  556. },
  557. },
  558. },
  559. err: nil,
  560. },
  561. {
  562. name: "AWS EBS PV non-conflicting affinity rules added",
  563. handler: newPersistentVolumeLabel(),
  564. pvlabeler: mockVolumeLabels(map[string]string{
  565. "d": "1",
  566. "e": "2",
  567. "f": "3",
  568. }),
  569. preAdmissionPV: &api.PersistentVolume{
  570. ObjectMeta: metav1.ObjectMeta{
  571. Name: "awsebs",
  572. Namespace: "myns",
  573. Labels: map[string]string{
  574. "a": "1",
  575. "b": "2",
  576. "c": "3",
  577. },
  578. },
  579. Spec: api.PersistentVolumeSpec{
  580. PersistentVolumeSource: api.PersistentVolumeSource{
  581. AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
  582. VolumeID: "123",
  583. },
  584. },
  585. NodeAffinity: &api.VolumeNodeAffinity{
  586. Required: &api.NodeSelector{
  587. NodeSelectorTerms: []api.NodeSelectorTerm{
  588. {
  589. MatchExpressions: []api.NodeSelectorRequirement{
  590. {
  591. Key: "a",
  592. Operator: api.NodeSelectorOpIn,
  593. Values: []string{"1"},
  594. },
  595. {
  596. Key: "b",
  597. Operator: api.NodeSelectorOpIn,
  598. Values: []string{"2"},
  599. },
  600. {
  601. Key: "c",
  602. Operator: api.NodeSelectorOpIn,
  603. Values: []string{"3"},
  604. },
  605. },
  606. },
  607. },
  608. },
  609. },
  610. },
  611. },
  612. postAdmissionPV: &api.PersistentVolume{
  613. ObjectMeta: metav1.ObjectMeta{
  614. Name: "awsebs",
  615. Namespace: "myns",
  616. Labels: map[string]string{
  617. "a": "1",
  618. "b": "2",
  619. "c": "3",
  620. "d": "1",
  621. "e": "2",
  622. "f": "3",
  623. },
  624. },
  625. Spec: api.PersistentVolumeSpec{
  626. PersistentVolumeSource: api.PersistentVolumeSource{
  627. AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
  628. VolumeID: "123",
  629. },
  630. },
  631. NodeAffinity: &api.VolumeNodeAffinity{
  632. Required: &api.NodeSelector{
  633. NodeSelectorTerms: []api.NodeSelectorTerm{
  634. {
  635. MatchExpressions: []api.NodeSelectorRequirement{
  636. {
  637. Key: "a",
  638. Operator: api.NodeSelectorOpIn,
  639. Values: []string{"1"},
  640. },
  641. {
  642. Key: "b",
  643. Operator: api.NodeSelectorOpIn,
  644. Values: []string{"2"},
  645. },
  646. {
  647. Key: "c",
  648. Operator: api.NodeSelectorOpIn,
  649. Values: []string{"3"},
  650. },
  651. {
  652. Key: "d",
  653. Operator: api.NodeSelectorOpIn,
  654. Values: []string{"1"},
  655. },
  656. {
  657. Key: "e",
  658. Operator: api.NodeSelectorOpIn,
  659. Values: []string{"2"},
  660. },
  661. {
  662. Key: "f",
  663. Operator: api.NodeSelectorOpIn,
  664. Values: []string{"3"},
  665. },
  666. },
  667. },
  668. },
  669. },
  670. },
  671. },
  672. },
  673. err: nil,
  674. },
  675. {
  676. name: "vSphere PV labeled correctly",
  677. handler: newPersistentVolumeLabel(),
  678. pvlabeler: mockVolumeLabels(map[string]string{
  679. "a": "1",
  680. "b": "2",
  681. v1.LabelZoneFailureDomain: "1__2__3",
  682. }),
  683. preAdmissionPV: &api.PersistentVolume{
  684. ObjectMeta: metav1.ObjectMeta{
  685. Name: "vSpherePV",
  686. Namespace: "myns",
  687. },
  688. Spec: api.PersistentVolumeSpec{
  689. PersistentVolumeSource: api.PersistentVolumeSource{
  690. VsphereVolume: &api.VsphereVirtualDiskVolumeSource{
  691. VolumePath: "123",
  692. },
  693. },
  694. },
  695. },
  696. postAdmissionPV: &api.PersistentVolume{
  697. ObjectMeta: metav1.ObjectMeta{
  698. Name: "vSpherePV",
  699. Namespace: "myns",
  700. Labels: map[string]string{
  701. "a": "1",
  702. "b": "2",
  703. v1.LabelZoneFailureDomain: "1__2__3",
  704. },
  705. },
  706. Spec: api.PersistentVolumeSpec{
  707. PersistentVolumeSource: api.PersistentVolumeSource{
  708. VsphereVolume: &api.VsphereVirtualDiskVolumeSource{
  709. VolumePath: "123",
  710. },
  711. },
  712. NodeAffinity: &api.VolumeNodeAffinity{
  713. Required: &api.NodeSelector{
  714. NodeSelectorTerms: []api.NodeSelectorTerm{
  715. {
  716. MatchExpressions: []api.NodeSelectorRequirement{
  717. {
  718. Key: "a",
  719. Operator: api.NodeSelectorOpIn,
  720. Values: []string{"1"},
  721. },
  722. {
  723. Key: "b",
  724. Operator: api.NodeSelectorOpIn,
  725. Values: []string{"2"},
  726. },
  727. {
  728. Key: v1.LabelZoneFailureDomain,
  729. Operator: api.NodeSelectorOpIn,
  730. Values: []string{"1", "2", "3"},
  731. },
  732. },
  733. },
  734. },
  735. },
  736. },
  737. },
  738. },
  739. err: nil,
  740. },
  741. }
  742. for _, testcase := range testcases {
  743. t.Run(testcase.name, func(t *testing.T) {
  744. setPVLabeler(testcase.handler, testcase.pvlabeler)
  745. handler := admissiontesting.WithReinvocationTesting(t, admission.NewChainHandler(testcase.handler))
  746. err := handler.Admit(admission.NewAttributesRecord(testcase.preAdmissionPV, nil, api.Kind("PersistentVolume").WithVersion("version"), testcase.preAdmissionPV.Namespace, testcase.preAdmissionPV.Name, api.Resource("persistentvolumes").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil), nil)
  747. if !reflect.DeepEqual(err, testcase.err) {
  748. t.Logf("expected error: %q", testcase.err)
  749. t.Logf("actual error: %q", err)
  750. t.Error("unexpected error when admitting PV")
  751. }
  752. // sort node selector match expression by key because they are added out of order in the admission controller
  753. sortMatchExpressions(testcase.preAdmissionPV)
  754. if !reflect.DeepEqual(testcase.preAdmissionPV, testcase.postAdmissionPV) {
  755. t.Logf("expected PV: %+v", testcase.postAdmissionPV)
  756. t.Logf("actual PV: %+v", testcase.preAdmissionPV)
  757. t.Error("unexpected PV")
  758. }
  759. })
  760. }
  761. }
  762. // setPVLabler applies the given mock pvlabeler to implement PV labeling for all cloud providers.
  763. // Given we mock out the values of the labels anyways, assigning the same mock labeler for every
  764. // provider does not reduce test coverage but it does simplify/clean up the tests here because
  765. // the provider is then decided based on the type of PV (EBS, Cinder, GCEPD, Azure Disk, etc)
  766. func setPVLabeler(handler *persistentVolumeLabel, pvlabeler cloudprovider.PVLabeler) {
  767. handler.awsPVLabeler = pvlabeler
  768. handler.gcePVLabeler = pvlabeler
  769. handler.azurePVLabeler = pvlabeler
  770. handler.openStackPVLabeler = pvlabeler
  771. handler.vspherePVLabeler = pvlabeler
  772. }
  773. // sortMatchExpressions sorts a PV's node selector match expressions by key name if it is not nil
  774. func sortMatchExpressions(pv *api.PersistentVolume) {
  775. if pv.Spec.NodeAffinity == nil ||
  776. pv.Spec.NodeAffinity.Required == nil ||
  777. pv.Spec.NodeAffinity.Required.NodeSelectorTerms == nil {
  778. return
  779. }
  780. match := pv.Spec.NodeAffinity.Required.NodeSelectorTerms[0].MatchExpressions
  781. sort.Slice(match, func(i, j int) bool {
  782. return match[i].Key < match[j].Key
  783. })
  784. pv.Spec.NodeAffinity.Required.NodeSelectorTerms[0].MatchExpressions = match
  785. }