index_test.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785
  1. /*
  2. Copyright 2014 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 persistentvolume
  14. import (
  15. "sort"
  16. "testing"
  17. "k8s.io/kubernetes/pkg/api"
  18. "k8s.io/kubernetes/pkg/api/resource"
  19. "k8s.io/kubernetes/pkg/api/testapi"
  20. "k8s.io/kubernetes/pkg/api/unversioned"
  21. )
  22. func TestMatchVolume(t *testing.T) {
  23. volList := newPersistentVolumeOrderedIndex()
  24. for _, pv := range createTestVolumes() {
  25. volList.store.Add(pv)
  26. }
  27. scenarios := map[string]struct {
  28. expectedMatch string
  29. claim *api.PersistentVolumeClaim
  30. }{
  31. "successful-match-gce-10": {
  32. expectedMatch: "gce-pd-10",
  33. claim: &api.PersistentVolumeClaim{
  34. ObjectMeta: api.ObjectMeta{
  35. Name: "claim01",
  36. Namespace: "myns",
  37. },
  38. Spec: api.PersistentVolumeClaimSpec{
  39. AccessModes: []api.PersistentVolumeAccessMode{api.ReadOnlyMany, api.ReadWriteOnce},
  40. Resources: api.ResourceRequirements{
  41. Requests: api.ResourceList{
  42. api.ResourceName(api.ResourceStorage): resource.MustParse("8G"),
  43. },
  44. },
  45. },
  46. },
  47. },
  48. "successful-match-nfs-5": {
  49. expectedMatch: "nfs-5",
  50. claim: &api.PersistentVolumeClaim{
  51. ObjectMeta: api.ObjectMeta{
  52. Name: "claim01",
  53. Namespace: "myns",
  54. },
  55. Spec: api.PersistentVolumeClaimSpec{
  56. AccessModes: []api.PersistentVolumeAccessMode{api.ReadOnlyMany, api.ReadWriteOnce, api.ReadWriteMany},
  57. Resources: api.ResourceRequirements{
  58. Requests: api.ResourceList{
  59. api.ResourceName(api.ResourceStorage): resource.MustParse("5G"),
  60. },
  61. },
  62. },
  63. },
  64. },
  65. "successful-skip-1g-bound-volume": {
  66. expectedMatch: "gce-pd-5",
  67. claim: &api.PersistentVolumeClaim{
  68. ObjectMeta: api.ObjectMeta{
  69. Name: "claim01",
  70. Namespace: "myns",
  71. },
  72. Spec: api.PersistentVolumeClaimSpec{
  73. AccessModes: []api.PersistentVolumeAccessMode{api.ReadOnlyMany, api.ReadWriteOnce},
  74. Resources: api.ResourceRequirements{
  75. Requests: api.ResourceList{
  76. api.ResourceName(api.ResourceStorage): resource.MustParse("1G"),
  77. },
  78. },
  79. },
  80. },
  81. },
  82. "successful-no-match": {
  83. expectedMatch: "",
  84. claim: &api.PersistentVolumeClaim{
  85. ObjectMeta: api.ObjectMeta{
  86. Name: "claim01",
  87. Namespace: "myns",
  88. },
  89. Spec: api.PersistentVolumeClaimSpec{
  90. AccessModes: []api.PersistentVolumeAccessMode{api.ReadOnlyMany, api.ReadWriteOnce},
  91. Resources: api.ResourceRequirements{
  92. Requests: api.ResourceList{
  93. api.ResourceName(api.ResourceStorage): resource.MustParse("999G"),
  94. },
  95. },
  96. },
  97. },
  98. },
  99. "successful-no-match-due-to-label": {
  100. expectedMatch: "",
  101. claim: &api.PersistentVolumeClaim{
  102. ObjectMeta: api.ObjectMeta{
  103. Name: "claim01",
  104. Namespace: "myns",
  105. },
  106. Spec: api.PersistentVolumeClaimSpec{
  107. Selector: &unversioned.LabelSelector{
  108. MatchLabels: map[string]string{
  109. "should-not-exist": "true",
  110. },
  111. },
  112. AccessModes: []api.PersistentVolumeAccessMode{api.ReadOnlyMany, api.ReadWriteOnce},
  113. Resources: api.ResourceRequirements{
  114. Requests: api.ResourceList{
  115. api.ResourceName(api.ResourceStorage): resource.MustParse("999G"),
  116. },
  117. },
  118. },
  119. },
  120. },
  121. "successful-no-match-due-to-size-constraint-with-label-selector": {
  122. expectedMatch: "",
  123. claim: &api.PersistentVolumeClaim{
  124. ObjectMeta: api.ObjectMeta{
  125. Name: "claim01",
  126. Namespace: "myns",
  127. },
  128. Spec: api.PersistentVolumeClaimSpec{
  129. Selector: &unversioned.LabelSelector{
  130. MatchLabels: map[string]string{
  131. "should-exist": "true",
  132. },
  133. },
  134. AccessModes: []api.PersistentVolumeAccessMode{api.ReadOnlyMany, api.ReadWriteOnce},
  135. Resources: api.ResourceRequirements{
  136. Requests: api.ResourceList{
  137. api.ResourceName(api.ResourceStorage): resource.MustParse("20000G"),
  138. },
  139. },
  140. },
  141. },
  142. },
  143. "successful-match-due-with-constraint-and-label-selector": {
  144. expectedMatch: "gce-pd-2",
  145. claim: &api.PersistentVolumeClaim{
  146. ObjectMeta: api.ObjectMeta{
  147. Name: "claim01",
  148. Namespace: "myns",
  149. },
  150. Spec: api.PersistentVolumeClaimSpec{
  151. Selector: &unversioned.LabelSelector{
  152. MatchLabels: map[string]string{
  153. "should-exist": "true",
  154. },
  155. },
  156. AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
  157. Resources: api.ResourceRequirements{
  158. Requests: api.ResourceList{
  159. api.ResourceName(api.ResourceStorage): resource.MustParse("20000G"),
  160. },
  161. },
  162. },
  163. },
  164. },
  165. "successful-match-with-class": {
  166. expectedMatch: "gce-pd-silver1",
  167. claim: &api.PersistentVolumeClaim{
  168. ObjectMeta: api.ObjectMeta{
  169. Name: "claim01",
  170. Namespace: "myns",
  171. Annotations: map[string]string{
  172. annClass: "silver",
  173. },
  174. },
  175. Spec: api.PersistentVolumeClaimSpec{
  176. AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
  177. Selector: &unversioned.LabelSelector{
  178. MatchLabels: map[string]string{
  179. "should-exist": "true",
  180. },
  181. },
  182. Resources: api.ResourceRequirements{
  183. Requests: api.ResourceList{
  184. api.ResourceName(api.ResourceStorage): resource.MustParse("1G"),
  185. },
  186. },
  187. },
  188. },
  189. },
  190. "successful-match-with-class-and-labels": {
  191. expectedMatch: "gce-pd-silver2",
  192. claim: &api.PersistentVolumeClaim{
  193. ObjectMeta: api.ObjectMeta{
  194. Name: "claim01",
  195. Namespace: "myns",
  196. Annotations: map[string]string{
  197. annClass: "silver",
  198. },
  199. },
  200. Spec: api.PersistentVolumeClaimSpec{
  201. AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
  202. Resources: api.ResourceRequirements{
  203. Requests: api.ResourceList{
  204. api.ResourceName(api.ResourceStorage): resource.MustParse("1G"),
  205. },
  206. },
  207. },
  208. },
  209. },
  210. }
  211. for name, scenario := range scenarios {
  212. volume, err := volList.findBestMatchForClaim(scenario.claim)
  213. if err != nil {
  214. t.Errorf("Unexpected error matching volume by claim: %v", err)
  215. }
  216. if len(scenario.expectedMatch) != 0 && volume == nil {
  217. t.Errorf("Expected match but received nil volume for scenario: %s", name)
  218. }
  219. if len(scenario.expectedMatch) != 0 && volume != nil && string(volume.UID) != scenario.expectedMatch {
  220. t.Errorf("Expected %s but got volume %s in scenario %s", scenario.expectedMatch, volume.UID, name)
  221. }
  222. if len(scenario.expectedMatch) == 0 && volume != nil {
  223. t.Errorf("Unexpected match for scenario: %s", name)
  224. }
  225. }
  226. }
  227. func TestMatchingWithBoundVolumes(t *testing.T) {
  228. volumeIndex := newPersistentVolumeOrderedIndex()
  229. // two similar volumes, one is bound
  230. pv1 := &api.PersistentVolume{
  231. ObjectMeta: api.ObjectMeta{
  232. UID: "gce-pd-1",
  233. Name: "gce001",
  234. },
  235. Spec: api.PersistentVolumeSpec{
  236. Capacity: api.ResourceList{
  237. api.ResourceName(api.ResourceStorage): resource.MustParse("1G"),
  238. },
  239. PersistentVolumeSource: api.PersistentVolumeSource{
  240. GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{},
  241. },
  242. AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce, api.ReadOnlyMany},
  243. // this one we're pretending is already bound
  244. ClaimRef: &api.ObjectReference{UID: "abc123"},
  245. },
  246. }
  247. pv2 := &api.PersistentVolume{
  248. ObjectMeta: api.ObjectMeta{
  249. UID: "gce-pd-2",
  250. Name: "gce002",
  251. },
  252. Spec: api.PersistentVolumeSpec{
  253. Capacity: api.ResourceList{
  254. api.ResourceName(api.ResourceStorage): resource.MustParse("1G"),
  255. },
  256. PersistentVolumeSource: api.PersistentVolumeSource{
  257. GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{},
  258. },
  259. AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce, api.ReadOnlyMany},
  260. },
  261. }
  262. volumeIndex.store.Add(pv1)
  263. volumeIndex.store.Add(pv2)
  264. claim := &api.PersistentVolumeClaim{
  265. ObjectMeta: api.ObjectMeta{
  266. Name: "claim01",
  267. Namespace: "myns",
  268. },
  269. Spec: api.PersistentVolumeClaimSpec{
  270. AccessModes: []api.PersistentVolumeAccessMode{api.ReadOnlyMany, api.ReadWriteOnce},
  271. Resources: api.ResourceRequirements{
  272. Requests: api.ResourceList{
  273. api.ResourceName(api.ResourceStorage): resource.MustParse("1G"),
  274. },
  275. },
  276. },
  277. }
  278. volume, err := volumeIndex.findBestMatchForClaim(claim)
  279. if err != nil {
  280. t.Fatalf("Unexpected error matching volume by claim: %v", err)
  281. }
  282. if volume == nil {
  283. t.Fatalf("Unexpected nil volume. Expected %s", pv2.Name)
  284. }
  285. if pv2.Name != volume.Name {
  286. t.Errorf("Expected %s but got volume %s instead", pv2.Name, volume.Name)
  287. }
  288. }
  289. func TestListByAccessModes(t *testing.T) {
  290. volList := newPersistentVolumeOrderedIndex()
  291. for _, pv := range createTestVolumes() {
  292. volList.store.Add(pv)
  293. }
  294. volumes, err := volList.listByAccessModes([]api.PersistentVolumeAccessMode{api.ReadWriteOnce, api.ReadOnlyMany})
  295. if err != nil {
  296. t.Error("Unexpected error retrieving volumes by access modes:", err)
  297. }
  298. sort.Sort(byCapacity{volumes})
  299. for i, expected := range []string{"gce-pd-1", "gce-pd-5", "gce-pd-10"} {
  300. if string(volumes[i].UID) != expected {
  301. t.Errorf("Incorrect ordering of persistent volumes. Expected %s but got %s", expected, volumes[i].UID)
  302. }
  303. }
  304. volumes, err = volList.listByAccessModes([]api.PersistentVolumeAccessMode{api.ReadWriteOnce, api.ReadOnlyMany, api.ReadWriteMany})
  305. if err != nil {
  306. t.Error("Unexpected error retrieving volumes by access modes:", err)
  307. }
  308. sort.Sort(byCapacity{volumes})
  309. for i, expected := range []string{"nfs-1", "nfs-5", "nfs-10"} {
  310. if string(volumes[i].UID) != expected {
  311. t.Errorf("Incorrect ordering of persistent volumes. Expected %s but got %s", expected, volumes[i].UID)
  312. }
  313. }
  314. }
  315. func TestAllPossibleAccessModes(t *testing.T) {
  316. index := newPersistentVolumeOrderedIndex()
  317. for _, pv := range createTestVolumes() {
  318. index.store.Add(pv)
  319. }
  320. // the mock PVs creates contain 2 types of accessmodes: RWO+ROX and RWO+ROW+RWX
  321. possibleModes := index.allPossibleMatchingAccessModes([]api.PersistentVolumeAccessMode{api.ReadWriteOnce})
  322. if len(possibleModes) != 3 {
  323. t.Errorf("Expected 3 arrays of modes that match RWO, but got %v", len(possibleModes))
  324. }
  325. for _, m := range possibleModes {
  326. if !contains(m, api.ReadWriteOnce) {
  327. t.Errorf("AccessModes does not contain %s", api.ReadWriteOnce)
  328. }
  329. }
  330. possibleModes = index.allPossibleMatchingAccessModes([]api.PersistentVolumeAccessMode{api.ReadWriteMany})
  331. if len(possibleModes) != 1 {
  332. t.Errorf("Expected 1 array of modes that match RWX, but got %v", len(possibleModes))
  333. }
  334. if !contains(possibleModes[0], api.ReadWriteMany) {
  335. t.Errorf("AccessModes does not contain %s", api.ReadWriteOnce)
  336. }
  337. }
  338. func TestFindingVolumeWithDifferentAccessModes(t *testing.T) {
  339. gce := &api.PersistentVolume{
  340. ObjectMeta: api.ObjectMeta{UID: "001", Name: "gce"},
  341. Spec: api.PersistentVolumeSpec{
  342. Capacity: api.ResourceList{api.ResourceName(api.ResourceStorage): resource.MustParse("10G")},
  343. PersistentVolumeSource: api.PersistentVolumeSource{GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{}},
  344. AccessModes: []api.PersistentVolumeAccessMode{
  345. api.ReadWriteOnce,
  346. api.ReadOnlyMany,
  347. },
  348. },
  349. }
  350. ebs := &api.PersistentVolume{
  351. ObjectMeta: api.ObjectMeta{UID: "002", Name: "ebs"},
  352. Spec: api.PersistentVolumeSpec{
  353. Capacity: api.ResourceList{api.ResourceName(api.ResourceStorage): resource.MustParse("10G")},
  354. PersistentVolumeSource: api.PersistentVolumeSource{AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{}},
  355. AccessModes: []api.PersistentVolumeAccessMode{
  356. api.ReadWriteOnce,
  357. },
  358. },
  359. }
  360. nfs := &api.PersistentVolume{
  361. ObjectMeta: api.ObjectMeta{UID: "003", Name: "nfs"},
  362. Spec: api.PersistentVolumeSpec{
  363. Capacity: api.ResourceList{api.ResourceName(api.ResourceStorage): resource.MustParse("10G")},
  364. PersistentVolumeSource: api.PersistentVolumeSource{NFS: &api.NFSVolumeSource{}},
  365. AccessModes: []api.PersistentVolumeAccessMode{
  366. api.ReadWriteOnce,
  367. api.ReadOnlyMany,
  368. api.ReadWriteMany,
  369. },
  370. },
  371. }
  372. claim := &api.PersistentVolumeClaim{
  373. ObjectMeta: api.ObjectMeta{
  374. Name: "claim01",
  375. Namespace: "myns",
  376. },
  377. Spec: api.PersistentVolumeClaimSpec{
  378. AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
  379. Resources: api.ResourceRequirements{Requests: api.ResourceList{api.ResourceName(api.ResourceStorage): resource.MustParse("1G")}},
  380. },
  381. }
  382. index := newPersistentVolumeOrderedIndex()
  383. index.store.Add(gce)
  384. index.store.Add(ebs)
  385. index.store.Add(nfs)
  386. volume, _ := index.findBestMatchForClaim(claim)
  387. if volume.Name != ebs.Name {
  388. t.Errorf("Expected %s but got volume %s instead", ebs.Name, volume.Name)
  389. }
  390. claim.Spec.AccessModes = []api.PersistentVolumeAccessMode{api.ReadWriteOnce, api.ReadOnlyMany}
  391. volume, _ = index.findBestMatchForClaim(claim)
  392. if volume.Name != gce.Name {
  393. t.Errorf("Expected %s but got volume %s instead", gce.Name, volume.Name)
  394. }
  395. // order of the requested modes should not matter
  396. claim.Spec.AccessModes = []api.PersistentVolumeAccessMode{api.ReadWriteMany, api.ReadWriteOnce, api.ReadOnlyMany}
  397. volume, _ = index.findBestMatchForClaim(claim)
  398. if volume.Name != nfs.Name {
  399. t.Errorf("Expected %s but got volume %s instead", nfs.Name, volume.Name)
  400. }
  401. // fewer modes requested should still match
  402. claim.Spec.AccessModes = []api.PersistentVolumeAccessMode{api.ReadWriteMany}
  403. volume, _ = index.findBestMatchForClaim(claim)
  404. if volume.Name != nfs.Name {
  405. t.Errorf("Expected %s but got volume %s instead", nfs.Name, volume.Name)
  406. }
  407. // pretend the exact match is bound. should get the next level up of modes.
  408. ebs.Spec.ClaimRef = &api.ObjectReference{}
  409. claim.Spec.AccessModes = []api.PersistentVolumeAccessMode{api.ReadWriteOnce}
  410. volume, _ = index.findBestMatchForClaim(claim)
  411. if volume.Name != gce.Name {
  412. t.Errorf("Expected %s but got volume %s instead", gce.Name, volume.Name)
  413. }
  414. // continue up the levels of modes.
  415. gce.Spec.ClaimRef = &api.ObjectReference{}
  416. claim.Spec.AccessModes = []api.PersistentVolumeAccessMode{api.ReadWriteOnce}
  417. volume, _ = index.findBestMatchForClaim(claim)
  418. if volume.Name != nfs.Name {
  419. t.Errorf("Expected %s but got volume %s instead", nfs.Name, volume.Name)
  420. }
  421. // partial mode request
  422. gce.Spec.ClaimRef = nil
  423. claim.Spec.AccessModes = []api.PersistentVolumeAccessMode{api.ReadOnlyMany}
  424. volume, _ = index.findBestMatchForClaim(claim)
  425. if volume.Name != gce.Name {
  426. t.Errorf("Expected %s but got volume %s instead", gce.Name, volume.Name)
  427. }
  428. }
  429. func createTestVolumes() []*api.PersistentVolume {
  430. // these volumes are deliberately out-of-order to test indexing and sorting
  431. return []*api.PersistentVolume{
  432. {
  433. ObjectMeta: api.ObjectMeta{
  434. UID: "gce-pd-10",
  435. Name: "gce003",
  436. },
  437. Spec: api.PersistentVolumeSpec{
  438. Capacity: api.ResourceList{
  439. api.ResourceName(api.ResourceStorage): resource.MustParse("10G"),
  440. },
  441. PersistentVolumeSource: api.PersistentVolumeSource{
  442. GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{},
  443. },
  444. AccessModes: []api.PersistentVolumeAccessMode{
  445. api.ReadWriteOnce,
  446. api.ReadOnlyMany,
  447. },
  448. },
  449. },
  450. {
  451. ObjectMeta: api.ObjectMeta{
  452. UID: "gce-pd-20",
  453. Name: "gce004",
  454. },
  455. Spec: api.PersistentVolumeSpec{
  456. Capacity: api.ResourceList{
  457. api.ResourceName(api.ResourceStorage): resource.MustParse("20G"),
  458. },
  459. PersistentVolumeSource: api.PersistentVolumeSource{
  460. GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{},
  461. },
  462. AccessModes: []api.PersistentVolumeAccessMode{
  463. api.ReadWriteOnce,
  464. api.ReadOnlyMany,
  465. },
  466. // this one we're pretending is already bound
  467. ClaimRef: &api.ObjectReference{UID: "def456"},
  468. },
  469. },
  470. {
  471. ObjectMeta: api.ObjectMeta{
  472. UID: "nfs-5",
  473. Name: "nfs002",
  474. },
  475. Spec: api.PersistentVolumeSpec{
  476. Capacity: api.ResourceList{
  477. api.ResourceName(api.ResourceStorage): resource.MustParse("5G"),
  478. },
  479. PersistentVolumeSource: api.PersistentVolumeSource{
  480. Glusterfs: &api.GlusterfsVolumeSource{},
  481. },
  482. AccessModes: []api.PersistentVolumeAccessMode{
  483. api.ReadWriteOnce,
  484. api.ReadOnlyMany,
  485. api.ReadWriteMany,
  486. },
  487. },
  488. },
  489. {
  490. ObjectMeta: api.ObjectMeta{
  491. UID: "gce-pd-1",
  492. Name: "gce001",
  493. },
  494. Spec: api.PersistentVolumeSpec{
  495. Capacity: api.ResourceList{
  496. api.ResourceName(api.ResourceStorage): resource.MustParse("1G"),
  497. },
  498. PersistentVolumeSource: api.PersistentVolumeSource{
  499. GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{},
  500. },
  501. AccessModes: []api.PersistentVolumeAccessMode{
  502. api.ReadWriteOnce,
  503. api.ReadOnlyMany,
  504. },
  505. // this one we're pretending is already bound
  506. ClaimRef: &api.ObjectReference{UID: "abc123"},
  507. },
  508. },
  509. {
  510. ObjectMeta: api.ObjectMeta{
  511. UID: "nfs-10",
  512. Name: "nfs003",
  513. },
  514. Spec: api.PersistentVolumeSpec{
  515. Capacity: api.ResourceList{
  516. api.ResourceName(api.ResourceStorage): resource.MustParse("10G"),
  517. },
  518. PersistentVolumeSource: api.PersistentVolumeSource{
  519. Glusterfs: &api.GlusterfsVolumeSource{},
  520. },
  521. AccessModes: []api.PersistentVolumeAccessMode{
  522. api.ReadWriteOnce,
  523. api.ReadOnlyMany,
  524. api.ReadWriteMany,
  525. },
  526. },
  527. },
  528. {
  529. ObjectMeta: api.ObjectMeta{
  530. UID: "gce-pd-5",
  531. Name: "gce002",
  532. },
  533. Spec: api.PersistentVolumeSpec{
  534. Capacity: api.ResourceList{
  535. api.ResourceName(api.ResourceStorage): resource.MustParse("5G"),
  536. },
  537. PersistentVolumeSource: api.PersistentVolumeSource{
  538. GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{},
  539. },
  540. AccessModes: []api.PersistentVolumeAccessMode{
  541. api.ReadWriteOnce,
  542. api.ReadOnlyMany,
  543. },
  544. },
  545. },
  546. {
  547. ObjectMeta: api.ObjectMeta{
  548. UID: "nfs-1",
  549. Name: "nfs001",
  550. },
  551. Spec: api.PersistentVolumeSpec{
  552. Capacity: api.ResourceList{
  553. api.ResourceName(api.ResourceStorage): resource.MustParse("1G"),
  554. },
  555. PersistentVolumeSource: api.PersistentVolumeSource{
  556. Glusterfs: &api.GlusterfsVolumeSource{},
  557. },
  558. AccessModes: []api.PersistentVolumeAccessMode{
  559. api.ReadWriteOnce,
  560. api.ReadOnlyMany,
  561. api.ReadWriteMany,
  562. },
  563. },
  564. },
  565. {
  566. ObjectMeta: api.ObjectMeta{
  567. UID: "gce-pd-2",
  568. Name: "gce0022",
  569. Labels: map[string]string{
  570. "should-exist": "true",
  571. },
  572. },
  573. Spec: api.PersistentVolumeSpec{
  574. Capacity: api.ResourceList{
  575. api.ResourceName(api.ResourceStorage): resource.MustParse("20000G"),
  576. },
  577. PersistentVolumeSource: api.PersistentVolumeSource{
  578. GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{},
  579. },
  580. AccessModes: []api.PersistentVolumeAccessMode{
  581. api.ReadWriteOnce,
  582. },
  583. },
  584. },
  585. {
  586. ObjectMeta: api.ObjectMeta{
  587. UID: "gce-pd-silver1",
  588. Name: "gce0023",
  589. Labels: map[string]string{
  590. "should-exist": "true",
  591. },
  592. Annotations: map[string]string{
  593. annClass: "silver",
  594. },
  595. },
  596. Spec: api.PersistentVolumeSpec{
  597. Capacity: api.ResourceList{
  598. api.ResourceName(api.ResourceStorage): resource.MustParse("10000G"),
  599. },
  600. PersistentVolumeSource: api.PersistentVolumeSource{
  601. GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{},
  602. },
  603. AccessModes: []api.PersistentVolumeAccessMode{
  604. api.ReadWriteOnce,
  605. },
  606. },
  607. },
  608. {
  609. ObjectMeta: api.ObjectMeta{
  610. UID: "gce-pd-silver2",
  611. Name: "gce0024",
  612. Annotations: map[string]string{
  613. annClass: "silver",
  614. },
  615. },
  616. Spec: api.PersistentVolumeSpec{
  617. Capacity: api.ResourceList{
  618. api.ResourceName(api.ResourceStorage): resource.MustParse("100G"),
  619. },
  620. PersistentVolumeSource: api.PersistentVolumeSource{
  621. GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{},
  622. },
  623. AccessModes: []api.PersistentVolumeAccessMode{
  624. api.ReadWriteOnce,
  625. },
  626. },
  627. },
  628. {
  629. ObjectMeta: api.ObjectMeta{
  630. UID: "gce-pd-gold",
  631. Name: "gce0025",
  632. Annotations: map[string]string{
  633. annClass: "gold",
  634. },
  635. },
  636. Spec: api.PersistentVolumeSpec{
  637. Capacity: api.ResourceList{
  638. api.ResourceName(api.ResourceStorage): resource.MustParse("50G"),
  639. },
  640. PersistentVolumeSource: api.PersistentVolumeSource{
  641. GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{},
  642. },
  643. AccessModes: []api.PersistentVolumeAccessMode{
  644. api.ReadWriteOnce,
  645. },
  646. },
  647. },
  648. }
  649. }
  650. func testVolume(name, size string) *api.PersistentVolume {
  651. return &api.PersistentVolume{
  652. ObjectMeta: api.ObjectMeta{
  653. Name: name,
  654. Annotations: map[string]string{},
  655. },
  656. Spec: api.PersistentVolumeSpec{
  657. Capacity: api.ResourceList{api.ResourceName(api.ResourceStorage): resource.MustParse(size)},
  658. PersistentVolumeSource: api.PersistentVolumeSource{HostPath: &api.HostPathVolumeSource{}},
  659. AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
  660. },
  661. }
  662. }
  663. func TestFindingPreboundVolumes(t *testing.T) {
  664. claim := &api.PersistentVolumeClaim{
  665. ObjectMeta: api.ObjectMeta{
  666. Name: "claim01",
  667. Namespace: "myns",
  668. SelfLink: testapi.Default.SelfLink("pvc", ""),
  669. },
  670. Spec: api.PersistentVolumeClaimSpec{
  671. AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
  672. Resources: api.ResourceRequirements{Requests: api.ResourceList{api.ResourceName(api.ResourceStorage): resource.MustParse("1Gi")}},
  673. },
  674. }
  675. claimRef, err := api.GetReference(claim)
  676. if err != nil {
  677. t.Errorf("error getting claimRef: %v", err)
  678. }
  679. pv1 := testVolume("pv1", "1Gi")
  680. pv5 := testVolume("pv5", "5Gi")
  681. pv8 := testVolume("pv8", "8Gi")
  682. pvBadSize := testVolume("pvBadSize", "1Mi")
  683. pvBadMode := testVolume("pvBadMode", "1Gi")
  684. pvBadMode.Spec.AccessModes = []api.PersistentVolumeAccessMode{api.ReadOnlyMany}
  685. index := newPersistentVolumeOrderedIndex()
  686. index.store.Add(pv1)
  687. index.store.Add(pv5)
  688. index.store.Add(pv8)
  689. index.store.Add(pvBadSize)
  690. index.store.Add(pvBadMode)
  691. // expected exact match on size
  692. volume, _ := index.findBestMatchForClaim(claim)
  693. if volume.Name != pv1.Name {
  694. t.Errorf("Expected %s but got volume %s instead", pv1.Name, volume.Name)
  695. }
  696. // pretend the exact match is pre-bound. should get the next size up.
  697. pv1.Spec.ClaimRef = &api.ObjectReference{Name: "foo", Namespace: "bar"}
  698. volume, _ = index.findBestMatchForClaim(claim)
  699. if volume.Name != pv5.Name {
  700. t.Errorf("Expected %s but got volume %s instead", pv5.Name, volume.Name)
  701. }
  702. // pretend the exact match is available but the largest volume is pre-bound to the claim.
  703. pv1.Spec.ClaimRef = nil
  704. pv8.Spec.ClaimRef = claimRef
  705. volume, _ = index.findBestMatchForClaim(claim)
  706. if volume.Name != pv8.Name {
  707. t.Errorf("Expected %s but got volume %s instead", pv8.Name, volume.Name)
  708. }
  709. // pretend the volume with too small a size is pre-bound to the claim. should get the exact match.
  710. pv8.Spec.ClaimRef = nil
  711. pvBadSize.Spec.ClaimRef = claimRef
  712. volume, _ = index.findBestMatchForClaim(claim)
  713. if volume.Name != pv1.Name {
  714. t.Errorf("Expected %s but got volume %s instead", pv1.Name, volume.Name)
  715. }
  716. // pretend the volume without the right access mode is pre-bound to the claim. should get the exact match.
  717. pvBadSize.Spec.ClaimRef = nil
  718. pvBadMode.Spec.ClaimRef = claimRef
  719. volume, _ = index.findBestMatchForClaim(claim)
  720. if volume.Name != pv1.Name {
  721. t.Errorf("Expected %s but got volume %s instead", pv1.Name, volume.Name)
  722. }
  723. }
  724. // byCapacity is used to order volumes by ascending storage size
  725. type byCapacity struct {
  726. volumes []*api.PersistentVolume
  727. }
  728. func (c byCapacity) Less(i, j int) bool {
  729. return matchStorageCapacity(c.volumes[i], c.volumes[j])
  730. }
  731. func (c byCapacity) Swap(i, j int) {
  732. c.volumes[i], c.volumes[j] = c.volumes[j], c.volumes[i]
  733. }
  734. func (c byCapacity) Len() int {
  735. return len(c.volumes)
  736. }