index_test.go 53 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678
  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/api/core/v1"
  18. "k8s.io/apimachinery/pkg/api/resource"
  19. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  20. utilfeature "k8s.io/apiserver/pkg/util/feature"
  21. "k8s.io/client-go/kubernetes/scheme"
  22. ref "k8s.io/client-go/tools/reference"
  23. featuregatetesting "k8s.io/component-base/featuregate/testing"
  24. "k8s.io/kubernetes/pkg/api/testapi"
  25. pvutil "k8s.io/kubernetes/pkg/controller/volume/persistentvolume/util"
  26. "k8s.io/kubernetes/pkg/features"
  27. "k8s.io/kubernetes/pkg/volume/util"
  28. )
  29. func makePVC(size string, modfn func(*v1.PersistentVolumeClaim)) *v1.PersistentVolumeClaim {
  30. fs := v1.PersistentVolumeFilesystem
  31. pvc := v1.PersistentVolumeClaim{
  32. ObjectMeta: metav1.ObjectMeta{
  33. Name: "claim01",
  34. Namespace: "myns",
  35. },
  36. Spec: v1.PersistentVolumeClaimSpec{
  37. AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany, v1.ReadWriteOnce},
  38. Resources: v1.ResourceRequirements{
  39. Requests: v1.ResourceList{
  40. v1.ResourceName(v1.ResourceStorage): resource.MustParse(size),
  41. },
  42. },
  43. VolumeMode: &fs,
  44. },
  45. }
  46. if modfn != nil {
  47. modfn(&pvc)
  48. }
  49. return &pvc
  50. }
  51. func makeVolumeModePVC(size string, mode *v1.PersistentVolumeMode, modfn func(*v1.PersistentVolumeClaim)) *v1.PersistentVolumeClaim {
  52. pvc := v1.PersistentVolumeClaim{
  53. ObjectMeta: metav1.ObjectMeta{
  54. Name: "claim01",
  55. Namespace: "myns",
  56. },
  57. Spec: v1.PersistentVolumeClaimSpec{
  58. VolumeMode: mode,
  59. AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
  60. Resources: v1.ResourceRequirements{
  61. Requests: v1.ResourceList{
  62. v1.ResourceName(v1.ResourceStorage): resource.MustParse(size),
  63. },
  64. },
  65. },
  66. }
  67. if modfn != nil {
  68. modfn(&pvc)
  69. }
  70. return &pvc
  71. }
  72. func TestMatchVolume(t *testing.T) {
  73. volList := newPersistentVolumeOrderedIndex()
  74. for _, pv := range createTestVolumes() {
  75. volList.store.Add(pv)
  76. }
  77. scenarios := map[string]struct {
  78. expectedMatch string
  79. claim *v1.PersistentVolumeClaim
  80. }{
  81. "successful-match-gce-10": {
  82. expectedMatch: "gce-pd-10",
  83. claim: makePVC("8G", nil),
  84. },
  85. "successful-match-nfs-5": {
  86. expectedMatch: "nfs-5",
  87. claim: makePVC("5G", func(pvc *v1.PersistentVolumeClaim) {
  88. pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany, v1.ReadWriteOnce, v1.ReadWriteMany}
  89. }),
  90. },
  91. "successful-skip-1g-bound-volume": {
  92. expectedMatch: "gce-pd-5",
  93. claim: makePVC("1G", nil),
  94. },
  95. "successful-no-match": {
  96. expectedMatch: "",
  97. claim: makePVC("999G", nil),
  98. },
  99. "successful-no-match-due-to-label": {
  100. expectedMatch: "",
  101. claim: makePVC("999G", func(pvc *v1.PersistentVolumeClaim) {
  102. pvc.Spec.Selector = &metav1.LabelSelector{
  103. MatchLabels: map[string]string{
  104. "should-not-exist": "true",
  105. },
  106. }
  107. }),
  108. },
  109. "successful-no-match-due-to-size-constraint-with-label-selector": {
  110. expectedMatch: "",
  111. claim: makePVC("20000G", func(pvc *v1.PersistentVolumeClaim) {
  112. pvc.Spec.Selector = &metav1.LabelSelector{
  113. MatchLabels: map[string]string{
  114. "should-exist": "true",
  115. },
  116. }
  117. pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany, v1.ReadWriteOnce}
  118. }),
  119. },
  120. "successful-match-due-with-constraint-and-label-selector": {
  121. expectedMatch: "gce-pd-2",
  122. claim: makePVC("20000G", func(pvc *v1.PersistentVolumeClaim) {
  123. pvc.Spec.Selector = &metav1.LabelSelector{
  124. MatchLabels: map[string]string{
  125. "should-exist": "true",
  126. },
  127. }
  128. pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}
  129. }),
  130. },
  131. "successful-match-with-class": {
  132. expectedMatch: "gce-pd-silver1",
  133. claim: makePVC("1G", func(pvc *v1.PersistentVolumeClaim) {
  134. pvc.Spec.Selector = &metav1.LabelSelector{
  135. MatchLabels: map[string]string{
  136. "should-exist": "true",
  137. },
  138. }
  139. pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}
  140. pvc.Spec.StorageClassName = &classSilver
  141. }),
  142. },
  143. "successful-match-with-class-and-labels": {
  144. expectedMatch: "gce-pd-silver2",
  145. claim: makePVC("1G", func(pvc *v1.PersistentVolumeClaim) {
  146. pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}
  147. pvc.Spec.StorageClassName = &classSilver
  148. }),
  149. },
  150. "successful-match-very-large": {
  151. expectedMatch: "local-pd-very-large",
  152. // we keep the pvc size less than int64 so that in case the pv overflows
  153. // the pvc does not overflow equally and give us false matching signals.
  154. claim: makePVC("1E", func(pvc *v1.PersistentVolumeClaim) {
  155. pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}
  156. pvc.Spec.StorageClassName = &classLarge
  157. }),
  158. },
  159. "successful-match-exact-extremely-large": {
  160. expectedMatch: "local-pd-extremely-large",
  161. claim: makePVC("800E", func(pvc *v1.PersistentVolumeClaim) {
  162. pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}
  163. pvc.Spec.StorageClassName = &classLarge
  164. }),
  165. },
  166. "successful-no-match-way-too-large": {
  167. expectedMatch: "",
  168. claim: makePVC("950E", func(pvc *v1.PersistentVolumeClaim) {
  169. pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}
  170. pvc.Spec.StorageClassName = &classLarge
  171. }),
  172. },
  173. }
  174. for name, scenario := range scenarios {
  175. volume, err := volList.findBestMatchForClaim(scenario.claim, false)
  176. if err != nil {
  177. t.Errorf("Unexpected error matching volume by claim: %v", err)
  178. }
  179. if len(scenario.expectedMatch) != 0 && volume == nil {
  180. t.Errorf("Expected match but received nil volume for scenario: %s", name)
  181. }
  182. if len(scenario.expectedMatch) != 0 && volume != nil && string(volume.UID) != scenario.expectedMatch {
  183. t.Errorf("Expected %s but got volume %s in scenario %s", scenario.expectedMatch, volume.UID, name)
  184. }
  185. if len(scenario.expectedMatch) == 0 && volume != nil {
  186. t.Errorf("Unexpected match for scenario: %s, matched with %s instead", name, volume.UID)
  187. }
  188. }
  189. }
  190. func TestMatchingWithBoundVolumes(t *testing.T) {
  191. fs := v1.PersistentVolumeFilesystem
  192. volumeIndex := newPersistentVolumeOrderedIndex()
  193. // two similar volumes, one is bound
  194. pv1 := &v1.PersistentVolume{
  195. ObjectMeta: metav1.ObjectMeta{
  196. UID: "gce-pd-1",
  197. Name: "gce001",
  198. },
  199. Spec: v1.PersistentVolumeSpec{
  200. Capacity: v1.ResourceList{
  201. v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G"),
  202. },
  203. PersistentVolumeSource: v1.PersistentVolumeSource{
  204. GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{},
  205. },
  206. AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce, v1.ReadOnlyMany},
  207. // this one we're pretending is already bound
  208. ClaimRef: &v1.ObjectReference{UID: "abc123"},
  209. VolumeMode: &fs,
  210. },
  211. Status: v1.PersistentVolumeStatus{
  212. Phase: v1.VolumeBound,
  213. },
  214. }
  215. pv2 := &v1.PersistentVolume{
  216. ObjectMeta: metav1.ObjectMeta{
  217. UID: "gce-pd-2",
  218. Name: "gce002",
  219. },
  220. Spec: v1.PersistentVolumeSpec{
  221. Capacity: v1.ResourceList{
  222. v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G"),
  223. },
  224. PersistentVolumeSource: v1.PersistentVolumeSource{
  225. GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{},
  226. },
  227. AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce, v1.ReadOnlyMany},
  228. VolumeMode: &fs,
  229. },
  230. Status: v1.PersistentVolumeStatus{
  231. Phase: v1.VolumeAvailable,
  232. },
  233. }
  234. volumeIndex.store.Add(pv1)
  235. volumeIndex.store.Add(pv2)
  236. claim := &v1.PersistentVolumeClaim{
  237. ObjectMeta: metav1.ObjectMeta{
  238. Name: "claim01",
  239. Namespace: "myns",
  240. },
  241. Spec: v1.PersistentVolumeClaimSpec{
  242. AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany, v1.ReadWriteOnce},
  243. Resources: v1.ResourceRequirements{
  244. Requests: v1.ResourceList{
  245. v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G"),
  246. },
  247. },
  248. VolumeMode: &fs,
  249. },
  250. }
  251. volume, err := volumeIndex.findBestMatchForClaim(claim, false)
  252. if err != nil {
  253. t.Fatalf("Unexpected error matching volume by claim: %v", err)
  254. }
  255. if volume == nil {
  256. t.Fatalf("Unexpected nil volume. Expected %s", pv2.Name)
  257. }
  258. if pv2.Name != volume.Name {
  259. t.Errorf("Expected %s but got volume %s instead", pv2.Name, volume.Name)
  260. }
  261. }
  262. func TestListByAccessModes(t *testing.T) {
  263. volList := newPersistentVolumeOrderedIndex()
  264. for _, pv := range createTestVolumes() {
  265. volList.store.Add(pv)
  266. }
  267. volumes, err := volList.listByAccessModes([]v1.PersistentVolumeAccessMode{v1.ReadWriteOnce, v1.ReadOnlyMany})
  268. if err != nil {
  269. t.Error("Unexpected error retrieving volumes by access modes:", err)
  270. }
  271. sort.Sort(byCapacity{volumes})
  272. for i, expected := range []string{"gce-pd-1", "gce-pd-5", "gce-pd-10"} {
  273. if string(volumes[i].UID) != expected {
  274. t.Errorf("Incorrect ordering of persistent volumes. Expected %s but got %s", expected, volumes[i].UID)
  275. }
  276. }
  277. volumes, err = volList.listByAccessModes([]v1.PersistentVolumeAccessMode{v1.ReadWriteOnce, v1.ReadOnlyMany, v1.ReadWriteMany})
  278. if err != nil {
  279. t.Error("Unexpected error retrieving volumes by access modes:", err)
  280. }
  281. sort.Sort(byCapacity{volumes})
  282. for i, expected := range []string{"nfs-1", "nfs-5", "nfs-10", "local-pd-very-large", "local-pd-extremely-large"} {
  283. if string(volumes[i].UID) != expected {
  284. t.Errorf("Incorrect ordering of persistent volumes. Expected %s but got %s", expected, volumes[i].UID)
  285. }
  286. }
  287. }
  288. func TestAllPossibleAccessModes(t *testing.T) {
  289. index := newPersistentVolumeOrderedIndex()
  290. for _, pv := range createTestVolumes() {
  291. index.store.Add(pv)
  292. }
  293. // the mock PVs creates contain 2 types of accessmodes: RWO+ROX and RWO+ROW+RWX
  294. possibleModes := index.allPossibleMatchingAccessModes([]v1.PersistentVolumeAccessMode{v1.ReadWriteOnce})
  295. if len(possibleModes) != 3 {
  296. t.Errorf("Expected 3 arrays of modes that match RWO, but got %v", len(possibleModes))
  297. }
  298. for _, m := range possibleModes {
  299. if !util.AccessModesContains(m, v1.ReadWriteOnce) {
  300. t.Errorf("AccessModes does not contain %s", v1.ReadWriteOnce)
  301. }
  302. }
  303. possibleModes = index.allPossibleMatchingAccessModes([]v1.PersistentVolumeAccessMode{v1.ReadWriteMany})
  304. if len(possibleModes) != 1 {
  305. t.Errorf("Expected 1 array of modes that match RWX, but got %v", len(possibleModes))
  306. }
  307. if !util.AccessModesContains(possibleModes[0], v1.ReadWriteMany) {
  308. t.Errorf("AccessModes does not contain %s", v1.ReadWriteOnce)
  309. }
  310. }
  311. func TestFindingVolumeWithDifferentAccessModes(t *testing.T) {
  312. fs := v1.PersistentVolumeFilesystem
  313. gce := &v1.PersistentVolume{
  314. ObjectMeta: metav1.ObjectMeta{UID: "001", Name: "gce"},
  315. Spec: v1.PersistentVolumeSpec{
  316. Capacity: v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G")},
  317. PersistentVolumeSource: v1.PersistentVolumeSource{GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{}},
  318. AccessModes: []v1.PersistentVolumeAccessMode{
  319. v1.ReadWriteOnce,
  320. v1.ReadOnlyMany,
  321. },
  322. VolumeMode: &fs,
  323. },
  324. Status: v1.PersistentVolumeStatus{
  325. Phase: v1.VolumeAvailable,
  326. },
  327. }
  328. ebs := &v1.PersistentVolume{
  329. ObjectMeta: metav1.ObjectMeta{UID: "002", Name: "ebs"},
  330. Spec: v1.PersistentVolumeSpec{
  331. Capacity: v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G")},
  332. PersistentVolumeSource: v1.PersistentVolumeSource{AWSElasticBlockStore: &v1.AWSElasticBlockStoreVolumeSource{}},
  333. AccessModes: []v1.PersistentVolumeAccessMode{
  334. v1.ReadWriteOnce,
  335. },
  336. VolumeMode: &fs,
  337. },
  338. Status: v1.PersistentVolumeStatus{
  339. Phase: v1.VolumeAvailable,
  340. },
  341. }
  342. nfs := &v1.PersistentVolume{
  343. ObjectMeta: metav1.ObjectMeta{UID: "003", Name: "nfs"},
  344. Spec: v1.PersistentVolumeSpec{
  345. Capacity: v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G")},
  346. PersistentVolumeSource: v1.PersistentVolumeSource{NFS: &v1.NFSVolumeSource{}},
  347. AccessModes: []v1.PersistentVolumeAccessMode{
  348. v1.ReadWriteOnce,
  349. v1.ReadOnlyMany,
  350. v1.ReadWriteMany,
  351. },
  352. VolumeMode: &fs,
  353. },
  354. Status: v1.PersistentVolumeStatus{
  355. Phase: v1.VolumeAvailable,
  356. },
  357. }
  358. claim := &v1.PersistentVolumeClaim{
  359. ObjectMeta: metav1.ObjectMeta{
  360. Name: "claim01",
  361. Namespace: "myns",
  362. },
  363. Spec: v1.PersistentVolumeClaimSpec{
  364. AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
  365. Resources: v1.ResourceRequirements{Requests: v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G")}},
  366. VolumeMode: &fs,
  367. },
  368. }
  369. index := newPersistentVolumeOrderedIndex()
  370. index.store.Add(gce)
  371. index.store.Add(ebs)
  372. index.store.Add(nfs)
  373. volume, _ := index.findBestMatchForClaim(claim, false)
  374. if volume.Name != ebs.Name {
  375. t.Errorf("Expected %s but got volume %s instead", ebs.Name, volume.Name)
  376. }
  377. claim.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce, v1.ReadOnlyMany}
  378. volume, _ = index.findBestMatchForClaim(claim, false)
  379. if volume.Name != gce.Name {
  380. t.Errorf("Expected %s but got volume %s instead", gce.Name, volume.Name)
  381. }
  382. // order of the requested modes should not matter
  383. claim.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteMany, v1.ReadWriteOnce, v1.ReadOnlyMany}
  384. volume, _ = index.findBestMatchForClaim(claim, false)
  385. if volume.Name != nfs.Name {
  386. t.Errorf("Expected %s but got volume %s instead", nfs.Name, volume.Name)
  387. }
  388. // fewer modes requested should still match
  389. claim.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteMany}
  390. volume, _ = index.findBestMatchForClaim(claim, false)
  391. if volume.Name != nfs.Name {
  392. t.Errorf("Expected %s but got volume %s instead", nfs.Name, volume.Name)
  393. }
  394. // pretend the exact match is bound. should get the next level up of modes.
  395. ebs.Spec.ClaimRef = &v1.ObjectReference{}
  396. claim.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}
  397. volume, _ = index.findBestMatchForClaim(claim, false)
  398. if volume.Name != gce.Name {
  399. t.Errorf("Expected %s but got volume %s instead", gce.Name, volume.Name)
  400. }
  401. // continue up the levels of modes.
  402. gce.Spec.ClaimRef = &v1.ObjectReference{}
  403. claim.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}
  404. volume, _ = index.findBestMatchForClaim(claim, false)
  405. if volume.Name != nfs.Name {
  406. t.Errorf("Expected %s but got volume %s instead", nfs.Name, volume.Name)
  407. }
  408. // partial mode request
  409. gce.Spec.ClaimRef = nil
  410. claim.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany}
  411. volume, _ = index.findBestMatchForClaim(claim, false)
  412. if volume.Name != gce.Name {
  413. t.Errorf("Expected %s but got volume %s instead", gce.Name, volume.Name)
  414. }
  415. }
  416. func createTestVolumes() []*v1.PersistentVolume {
  417. fs := v1.PersistentVolumeFilesystem
  418. // these volumes are deliberately out-of-order to test indexing and sorting
  419. return []*v1.PersistentVolume{
  420. {
  421. ObjectMeta: metav1.ObjectMeta{
  422. UID: "gce-pd-10",
  423. Name: "gce003",
  424. },
  425. Spec: v1.PersistentVolumeSpec{
  426. Capacity: v1.ResourceList{
  427. v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G"),
  428. },
  429. PersistentVolumeSource: v1.PersistentVolumeSource{
  430. GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{},
  431. },
  432. AccessModes: []v1.PersistentVolumeAccessMode{
  433. v1.ReadWriteOnce,
  434. v1.ReadOnlyMany,
  435. },
  436. VolumeMode: &fs,
  437. },
  438. Status: v1.PersistentVolumeStatus{
  439. Phase: v1.VolumeAvailable,
  440. },
  441. },
  442. {
  443. ObjectMeta: metav1.ObjectMeta{
  444. UID: "gce-pd-20",
  445. Name: "gce004",
  446. },
  447. Spec: v1.PersistentVolumeSpec{
  448. Capacity: v1.ResourceList{
  449. v1.ResourceName(v1.ResourceStorage): resource.MustParse("20G"),
  450. },
  451. PersistentVolumeSource: v1.PersistentVolumeSource{
  452. GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{},
  453. },
  454. AccessModes: []v1.PersistentVolumeAccessMode{
  455. v1.ReadWriteOnce,
  456. v1.ReadOnlyMany,
  457. },
  458. // this one we're pretending is already bound
  459. ClaimRef: &v1.ObjectReference{UID: "def456"},
  460. VolumeMode: &fs,
  461. },
  462. Status: v1.PersistentVolumeStatus{
  463. Phase: v1.VolumeBound,
  464. },
  465. },
  466. {
  467. ObjectMeta: metav1.ObjectMeta{
  468. UID: "nfs-5",
  469. Name: "nfs002",
  470. },
  471. Spec: v1.PersistentVolumeSpec{
  472. Capacity: v1.ResourceList{
  473. v1.ResourceName(v1.ResourceStorage): resource.MustParse("5G"),
  474. },
  475. PersistentVolumeSource: v1.PersistentVolumeSource{
  476. Glusterfs: &v1.GlusterfsPersistentVolumeSource{},
  477. },
  478. AccessModes: []v1.PersistentVolumeAccessMode{
  479. v1.ReadWriteOnce,
  480. v1.ReadOnlyMany,
  481. v1.ReadWriteMany,
  482. },
  483. VolumeMode: &fs,
  484. },
  485. Status: v1.PersistentVolumeStatus{
  486. Phase: v1.VolumeAvailable,
  487. },
  488. },
  489. {
  490. ObjectMeta: metav1.ObjectMeta{
  491. UID: "gce-pd-1",
  492. Name: "gce001",
  493. },
  494. Spec: v1.PersistentVolumeSpec{
  495. Capacity: v1.ResourceList{
  496. v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G"),
  497. },
  498. PersistentVolumeSource: v1.PersistentVolumeSource{
  499. GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{},
  500. },
  501. AccessModes: []v1.PersistentVolumeAccessMode{
  502. v1.ReadWriteOnce,
  503. v1.ReadOnlyMany,
  504. },
  505. // this one we're pretending is already bound
  506. ClaimRef: &v1.ObjectReference{UID: "abc123"},
  507. VolumeMode: &fs,
  508. },
  509. Status: v1.PersistentVolumeStatus{
  510. Phase: v1.VolumeBound,
  511. },
  512. },
  513. {
  514. ObjectMeta: metav1.ObjectMeta{
  515. UID: "nfs-10",
  516. Name: "nfs003",
  517. },
  518. Spec: v1.PersistentVolumeSpec{
  519. Capacity: v1.ResourceList{
  520. v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G"),
  521. },
  522. PersistentVolumeSource: v1.PersistentVolumeSource{
  523. Glusterfs: &v1.GlusterfsPersistentVolumeSource{},
  524. },
  525. AccessModes: []v1.PersistentVolumeAccessMode{
  526. v1.ReadWriteOnce,
  527. v1.ReadOnlyMany,
  528. v1.ReadWriteMany,
  529. },
  530. VolumeMode: &fs,
  531. },
  532. Status: v1.PersistentVolumeStatus{
  533. Phase: v1.VolumeAvailable,
  534. },
  535. },
  536. {
  537. ObjectMeta: metav1.ObjectMeta{
  538. UID: "gce-pd-5",
  539. Name: "gce002",
  540. },
  541. Spec: v1.PersistentVolumeSpec{
  542. Capacity: v1.ResourceList{
  543. v1.ResourceName(v1.ResourceStorage): resource.MustParse("5G"),
  544. },
  545. PersistentVolumeSource: v1.PersistentVolumeSource{
  546. GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{},
  547. },
  548. AccessModes: []v1.PersistentVolumeAccessMode{
  549. v1.ReadWriteOnce,
  550. v1.ReadOnlyMany,
  551. },
  552. VolumeMode: &fs,
  553. },
  554. Status: v1.PersistentVolumeStatus{
  555. Phase: v1.VolumeAvailable,
  556. },
  557. },
  558. {
  559. ObjectMeta: metav1.ObjectMeta{
  560. UID: "nfs-1",
  561. Name: "nfs001",
  562. },
  563. Spec: v1.PersistentVolumeSpec{
  564. Capacity: v1.ResourceList{
  565. v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G"),
  566. },
  567. PersistentVolumeSource: v1.PersistentVolumeSource{
  568. Glusterfs: &v1.GlusterfsPersistentVolumeSource{},
  569. },
  570. AccessModes: []v1.PersistentVolumeAccessMode{
  571. v1.ReadWriteOnce,
  572. v1.ReadOnlyMany,
  573. v1.ReadWriteMany,
  574. },
  575. VolumeMode: &fs,
  576. },
  577. Status: v1.PersistentVolumeStatus{
  578. Phase: v1.VolumeAvailable,
  579. },
  580. },
  581. {
  582. ObjectMeta: metav1.ObjectMeta{
  583. UID: "gce-pd-2",
  584. Name: "gce0022",
  585. Labels: map[string]string{
  586. "should-exist": "true",
  587. },
  588. },
  589. Spec: v1.PersistentVolumeSpec{
  590. Capacity: v1.ResourceList{
  591. v1.ResourceName(v1.ResourceStorage): resource.MustParse("20000G"),
  592. },
  593. PersistentVolumeSource: v1.PersistentVolumeSource{
  594. GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{},
  595. },
  596. AccessModes: []v1.PersistentVolumeAccessMode{
  597. v1.ReadWriteOnce,
  598. },
  599. VolumeMode: &fs,
  600. },
  601. Status: v1.PersistentVolumeStatus{
  602. Phase: v1.VolumeAvailable,
  603. },
  604. },
  605. {
  606. ObjectMeta: metav1.ObjectMeta{
  607. UID: "gce-pd-silver1",
  608. Name: "gce0023",
  609. Labels: map[string]string{
  610. "should-exist": "true",
  611. },
  612. },
  613. Spec: v1.PersistentVolumeSpec{
  614. Capacity: v1.ResourceList{
  615. v1.ResourceName(v1.ResourceStorage): resource.MustParse("10000G"),
  616. },
  617. PersistentVolumeSource: v1.PersistentVolumeSource{
  618. GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{},
  619. },
  620. AccessModes: []v1.PersistentVolumeAccessMode{
  621. v1.ReadWriteOnce,
  622. },
  623. StorageClassName: classSilver,
  624. VolumeMode: &fs,
  625. },
  626. Status: v1.PersistentVolumeStatus{
  627. Phase: v1.VolumeAvailable,
  628. },
  629. },
  630. {
  631. ObjectMeta: metav1.ObjectMeta{
  632. UID: "gce-pd-silver2",
  633. Name: "gce0024",
  634. },
  635. Spec: v1.PersistentVolumeSpec{
  636. Capacity: v1.ResourceList{
  637. v1.ResourceName(v1.ResourceStorage): resource.MustParse("100G"),
  638. },
  639. PersistentVolumeSource: v1.PersistentVolumeSource{
  640. GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{},
  641. },
  642. AccessModes: []v1.PersistentVolumeAccessMode{
  643. v1.ReadWriteOnce,
  644. },
  645. StorageClassName: classSilver,
  646. VolumeMode: &fs,
  647. },
  648. Status: v1.PersistentVolumeStatus{
  649. Phase: v1.VolumeAvailable,
  650. },
  651. },
  652. {
  653. ObjectMeta: metav1.ObjectMeta{
  654. UID: "gce-pd-gold",
  655. Name: "gce0025",
  656. },
  657. Spec: v1.PersistentVolumeSpec{
  658. Capacity: v1.ResourceList{
  659. v1.ResourceName(v1.ResourceStorage): resource.MustParse("50G"),
  660. },
  661. PersistentVolumeSource: v1.PersistentVolumeSource{
  662. GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{},
  663. },
  664. AccessModes: []v1.PersistentVolumeAccessMode{
  665. v1.ReadWriteOnce,
  666. },
  667. StorageClassName: classGold,
  668. VolumeMode: &fs,
  669. },
  670. Status: v1.PersistentVolumeStatus{
  671. Phase: v1.VolumeAvailable,
  672. },
  673. },
  674. {
  675. ObjectMeta: metav1.ObjectMeta{
  676. UID: "local-pd-very-large",
  677. Name: "local001",
  678. },
  679. Spec: v1.PersistentVolumeSpec{
  680. Capacity: v1.ResourceList{
  681. v1.ResourceName(v1.ResourceStorage): resource.MustParse("200E"),
  682. },
  683. PersistentVolumeSource: v1.PersistentVolumeSource{
  684. Local: &v1.LocalVolumeSource{},
  685. },
  686. AccessModes: []v1.PersistentVolumeAccessMode{
  687. v1.ReadWriteOnce,
  688. v1.ReadOnlyMany,
  689. v1.ReadWriteMany,
  690. },
  691. StorageClassName: classLarge,
  692. VolumeMode: &fs,
  693. },
  694. Status: v1.PersistentVolumeStatus{
  695. Phase: v1.VolumeAvailable,
  696. },
  697. },
  698. {
  699. ObjectMeta: metav1.ObjectMeta{
  700. UID: "local-pd-extremely-large",
  701. Name: "local002",
  702. },
  703. Spec: v1.PersistentVolumeSpec{
  704. Capacity: v1.ResourceList{
  705. v1.ResourceName(v1.ResourceStorage): resource.MustParse("800E"),
  706. },
  707. PersistentVolumeSource: v1.PersistentVolumeSource{
  708. Local: &v1.LocalVolumeSource{},
  709. },
  710. AccessModes: []v1.PersistentVolumeAccessMode{
  711. v1.ReadWriteOnce,
  712. v1.ReadOnlyMany,
  713. v1.ReadWriteMany,
  714. },
  715. StorageClassName: classLarge,
  716. VolumeMode: &fs,
  717. },
  718. Status: v1.PersistentVolumeStatus{
  719. Phase: v1.VolumeAvailable,
  720. },
  721. },
  722. {
  723. ObjectMeta: metav1.ObjectMeta{
  724. UID: "affinity-pv",
  725. Name: "affinity001",
  726. },
  727. Spec: v1.PersistentVolumeSpec{
  728. Capacity: v1.ResourceList{
  729. v1.ResourceName(v1.ResourceStorage): resource.MustParse("100G"),
  730. },
  731. PersistentVolumeSource: v1.PersistentVolumeSource{
  732. Local: &v1.LocalVolumeSource{},
  733. },
  734. AccessModes: []v1.PersistentVolumeAccessMode{
  735. v1.ReadWriteOnce,
  736. v1.ReadOnlyMany,
  737. },
  738. StorageClassName: classWait,
  739. NodeAffinity: pvutil.GetVolumeNodeAffinity("key1", "value1"),
  740. VolumeMode: &fs,
  741. },
  742. Status: v1.PersistentVolumeStatus{
  743. Phase: v1.VolumeAvailable,
  744. },
  745. },
  746. {
  747. ObjectMeta: metav1.ObjectMeta{
  748. UID: "affinity-pv2",
  749. Name: "affinity002",
  750. },
  751. Spec: v1.PersistentVolumeSpec{
  752. Capacity: v1.ResourceList{
  753. v1.ResourceName(v1.ResourceStorage): resource.MustParse("150G"),
  754. },
  755. PersistentVolumeSource: v1.PersistentVolumeSource{
  756. Local: &v1.LocalVolumeSource{},
  757. },
  758. AccessModes: []v1.PersistentVolumeAccessMode{
  759. v1.ReadWriteOnce,
  760. v1.ReadOnlyMany,
  761. },
  762. StorageClassName: classWait,
  763. NodeAffinity: pvutil.GetVolumeNodeAffinity("key1", "value1"),
  764. VolumeMode: &fs,
  765. },
  766. Status: v1.PersistentVolumeStatus{
  767. Phase: v1.VolumeAvailable,
  768. },
  769. },
  770. {
  771. ObjectMeta: metav1.ObjectMeta{
  772. UID: "affinity-prebound",
  773. Name: "affinity003",
  774. },
  775. Spec: v1.PersistentVolumeSpec{
  776. Capacity: v1.ResourceList{
  777. v1.ResourceName(v1.ResourceStorage): resource.MustParse("100G"),
  778. },
  779. PersistentVolumeSource: v1.PersistentVolumeSource{
  780. Local: &v1.LocalVolumeSource{},
  781. },
  782. AccessModes: []v1.PersistentVolumeAccessMode{
  783. v1.ReadWriteOnce,
  784. v1.ReadOnlyMany,
  785. },
  786. StorageClassName: classWait,
  787. ClaimRef: &v1.ObjectReference{Name: "claim02", Namespace: "myns"},
  788. NodeAffinity: pvutil.GetVolumeNodeAffinity("key1", "value1"),
  789. VolumeMode: &fs,
  790. },
  791. Status: v1.PersistentVolumeStatus{
  792. Phase: v1.VolumeAvailable,
  793. },
  794. },
  795. {
  796. ObjectMeta: metav1.ObjectMeta{
  797. UID: "affinity-pv3",
  798. Name: "affinity003",
  799. },
  800. Spec: v1.PersistentVolumeSpec{
  801. Capacity: v1.ResourceList{
  802. v1.ResourceName(v1.ResourceStorage): resource.MustParse("200G"),
  803. },
  804. PersistentVolumeSource: v1.PersistentVolumeSource{
  805. Local: &v1.LocalVolumeSource{},
  806. },
  807. AccessModes: []v1.PersistentVolumeAccessMode{
  808. v1.ReadWriteOnce,
  809. v1.ReadOnlyMany,
  810. },
  811. StorageClassName: classWait,
  812. NodeAffinity: pvutil.GetVolumeNodeAffinity("key1", "value3"),
  813. VolumeMode: &fs,
  814. },
  815. Status: v1.PersistentVolumeStatus{
  816. Phase: v1.VolumeAvailable,
  817. },
  818. },
  819. {
  820. ObjectMeta: metav1.ObjectMeta{
  821. UID: "affinity-pv4-pending",
  822. Name: "affinity004-pending",
  823. },
  824. Spec: v1.PersistentVolumeSpec{
  825. Capacity: v1.ResourceList{
  826. v1.ResourceName(v1.ResourceStorage): resource.MustParse("200G"),
  827. },
  828. PersistentVolumeSource: v1.PersistentVolumeSource{
  829. Local: &v1.LocalVolumeSource{},
  830. },
  831. AccessModes: []v1.PersistentVolumeAccessMode{
  832. v1.ReadWriteOnce,
  833. v1.ReadOnlyMany,
  834. },
  835. StorageClassName: classWait,
  836. NodeAffinity: pvutil.GetVolumeNodeAffinity("key1", "value4"),
  837. VolumeMode: &fs,
  838. },
  839. Status: v1.PersistentVolumeStatus{
  840. Phase: v1.VolumePending,
  841. },
  842. },
  843. {
  844. ObjectMeta: metav1.ObjectMeta{
  845. UID: "affinity-pv4-failed",
  846. Name: "affinity004-failed",
  847. },
  848. Spec: v1.PersistentVolumeSpec{
  849. Capacity: v1.ResourceList{
  850. v1.ResourceName(v1.ResourceStorage): resource.MustParse("200G"),
  851. },
  852. PersistentVolumeSource: v1.PersistentVolumeSource{
  853. Local: &v1.LocalVolumeSource{},
  854. },
  855. AccessModes: []v1.PersistentVolumeAccessMode{
  856. v1.ReadWriteOnce,
  857. v1.ReadOnlyMany,
  858. },
  859. StorageClassName: classWait,
  860. NodeAffinity: pvutil.GetVolumeNodeAffinity("key1", "value4"),
  861. VolumeMode: &fs,
  862. },
  863. Status: v1.PersistentVolumeStatus{
  864. Phase: v1.VolumeFailed,
  865. },
  866. },
  867. {
  868. ObjectMeta: metav1.ObjectMeta{
  869. UID: "affinity-pv4-released",
  870. Name: "affinity004-released",
  871. },
  872. Spec: v1.PersistentVolumeSpec{
  873. Capacity: v1.ResourceList{
  874. v1.ResourceName(v1.ResourceStorage): resource.MustParse("200G"),
  875. },
  876. PersistentVolumeSource: v1.PersistentVolumeSource{
  877. Local: &v1.LocalVolumeSource{},
  878. },
  879. AccessModes: []v1.PersistentVolumeAccessMode{
  880. v1.ReadWriteOnce,
  881. v1.ReadOnlyMany,
  882. },
  883. StorageClassName: classWait,
  884. NodeAffinity: pvutil.GetVolumeNodeAffinity("key1", "value4"),
  885. VolumeMode: &fs,
  886. },
  887. Status: v1.PersistentVolumeStatus{
  888. Phase: v1.VolumeReleased,
  889. },
  890. },
  891. {
  892. ObjectMeta: metav1.ObjectMeta{
  893. UID: "affinity-pv4-empty",
  894. Name: "affinity004-empty",
  895. },
  896. Spec: v1.PersistentVolumeSpec{
  897. Capacity: v1.ResourceList{
  898. v1.ResourceName(v1.ResourceStorage): resource.MustParse("200G"),
  899. },
  900. PersistentVolumeSource: v1.PersistentVolumeSource{
  901. Local: &v1.LocalVolumeSource{},
  902. },
  903. AccessModes: []v1.PersistentVolumeAccessMode{
  904. v1.ReadWriteOnce,
  905. v1.ReadOnlyMany,
  906. },
  907. StorageClassName: classWait,
  908. NodeAffinity: pvutil.GetVolumeNodeAffinity("key1", "value4"),
  909. VolumeMode: &fs,
  910. },
  911. },
  912. }
  913. }
  914. func testVolume(name, size string) *v1.PersistentVolume {
  915. fs := v1.PersistentVolumeFilesystem
  916. return &v1.PersistentVolume{
  917. ObjectMeta: metav1.ObjectMeta{
  918. Name: name,
  919. Annotations: map[string]string{},
  920. },
  921. Spec: v1.PersistentVolumeSpec{
  922. Capacity: v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse(size)},
  923. PersistentVolumeSource: v1.PersistentVolumeSource{HostPath: &v1.HostPathVolumeSource{}},
  924. AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
  925. VolumeMode: &fs,
  926. },
  927. Status: v1.PersistentVolumeStatus{
  928. Phase: v1.VolumeAvailable,
  929. },
  930. }
  931. }
  932. func createVolumeModeBlockTestVolume() *v1.PersistentVolume {
  933. blockMode := v1.PersistentVolumeBlock
  934. return &v1.PersistentVolume{
  935. ObjectMeta: metav1.ObjectMeta{
  936. UID: "local-1",
  937. Name: "block",
  938. },
  939. Spec: v1.PersistentVolumeSpec{
  940. Capacity: v1.ResourceList{
  941. v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G"),
  942. },
  943. PersistentVolumeSource: v1.PersistentVolumeSource{
  944. Local: &v1.LocalVolumeSource{},
  945. },
  946. AccessModes: []v1.PersistentVolumeAccessMode{
  947. v1.ReadWriteOnce,
  948. },
  949. VolumeMode: &blockMode,
  950. },
  951. Status: v1.PersistentVolumeStatus{
  952. Phase: v1.VolumeAvailable,
  953. },
  954. }
  955. }
  956. func createVolumeModeFilesystemTestVolume() *v1.PersistentVolume {
  957. filesystemMode := v1.PersistentVolumeFilesystem
  958. return &v1.PersistentVolume{
  959. ObjectMeta: metav1.ObjectMeta{
  960. UID: "local-1",
  961. Name: "block",
  962. },
  963. Spec: v1.PersistentVolumeSpec{
  964. Capacity: v1.ResourceList{
  965. v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G"),
  966. },
  967. PersistentVolumeSource: v1.PersistentVolumeSource{
  968. Local: &v1.LocalVolumeSource{},
  969. },
  970. AccessModes: []v1.PersistentVolumeAccessMode{
  971. v1.ReadWriteOnce,
  972. },
  973. VolumeMode: &filesystemMode,
  974. },
  975. Status: v1.PersistentVolumeStatus{
  976. Phase: v1.VolumeAvailable,
  977. },
  978. }
  979. }
  980. func createVolumeModeNilTestVolume() *v1.PersistentVolume {
  981. return &v1.PersistentVolume{
  982. ObjectMeta: metav1.ObjectMeta{
  983. UID: "local-1",
  984. Name: "nil-mode",
  985. },
  986. Spec: v1.PersistentVolumeSpec{
  987. Capacity: v1.ResourceList{
  988. v1.ResourceName(v1.ResourceStorage): resource.MustParse("10G"),
  989. },
  990. PersistentVolumeSource: v1.PersistentVolumeSource{
  991. Local: &v1.LocalVolumeSource{},
  992. },
  993. AccessModes: []v1.PersistentVolumeAccessMode{
  994. v1.ReadWriteOnce,
  995. },
  996. },
  997. Status: v1.PersistentVolumeStatus{
  998. Phase: v1.VolumeAvailable,
  999. },
  1000. }
  1001. }
  1002. func createTestVolOrderedIndex(pv *v1.PersistentVolume) persistentVolumeOrderedIndex {
  1003. volFile := newPersistentVolumeOrderedIndex()
  1004. volFile.store.Add(pv)
  1005. return volFile
  1006. }
  1007. func TestVolumeModeCheck(t *testing.T) {
  1008. blockMode := v1.PersistentVolumeBlock
  1009. filesystemMode := v1.PersistentVolumeFilesystem
  1010. // If feature gate is enabled, VolumeMode will always be defaulted
  1011. // If feature gate is disabled, VolumeMode is dropped by API and ignored
  1012. scenarios := map[string]struct {
  1013. isExpectedMismatch bool
  1014. vol *v1.PersistentVolume
  1015. pvc *v1.PersistentVolumeClaim
  1016. enableBlock bool
  1017. }{
  1018. "feature enabled - pvc block and pv filesystem": {
  1019. isExpectedMismatch: true,
  1020. vol: createVolumeModeFilesystemTestVolume(),
  1021. pvc: makeVolumeModePVC("8G", &blockMode, nil),
  1022. enableBlock: true,
  1023. },
  1024. "feature enabled - pvc filesystem and pv block": {
  1025. isExpectedMismatch: true,
  1026. vol: createVolumeModeBlockTestVolume(),
  1027. pvc: makeVolumeModePVC("8G", &filesystemMode, nil),
  1028. enableBlock: true,
  1029. },
  1030. "feature enabled - pvc block and pv block": {
  1031. isExpectedMismatch: false,
  1032. vol: createVolumeModeBlockTestVolume(),
  1033. pvc: makeVolumeModePVC("8G", &blockMode, nil),
  1034. enableBlock: true,
  1035. },
  1036. "feature enabled - pvc filesystem and pv filesystem": {
  1037. isExpectedMismatch: false,
  1038. vol: createVolumeModeFilesystemTestVolume(),
  1039. pvc: makeVolumeModePVC("8G", &filesystemMode, nil),
  1040. enableBlock: true,
  1041. },
  1042. "feature enabled - pvc filesystem and pv nil": {
  1043. isExpectedMismatch: false,
  1044. vol: createVolumeModeNilTestVolume(),
  1045. pvc: makeVolumeModePVC("8G", &filesystemMode, nil),
  1046. enableBlock: true,
  1047. },
  1048. "feature enabled - pvc nil and pv filesytem": {
  1049. isExpectedMismatch: false,
  1050. vol: createVolumeModeFilesystemTestVolume(),
  1051. pvc: makeVolumeModePVC("8G", nil, nil),
  1052. enableBlock: true,
  1053. },
  1054. "feature enabled - pvc nil and pv nil": {
  1055. isExpectedMismatch: false,
  1056. vol: createVolumeModeNilTestVolume(),
  1057. pvc: makeVolumeModePVC("8G", nil, nil),
  1058. enableBlock: true,
  1059. },
  1060. "feature enabled - pvc nil and pv block": {
  1061. isExpectedMismatch: true,
  1062. vol: createVolumeModeBlockTestVolume(),
  1063. pvc: makeVolumeModePVC("8G", nil, nil),
  1064. enableBlock: true,
  1065. },
  1066. "feature enabled - pvc block and pv nil": {
  1067. isExpectedMismatch: true,
  1068. vol: createVolumeModeNilTestVolume(),
  1069. pvc: makeVolumeModePVC("8G", &blockMode, nil),
  1070. enableBlock: true,
  1071. },
  1072. "feature disabled - pvc block and pv filesystem": {
  1073. isExpectedMismatch: false,
  1074. vol: createVolumeModeFilesystemTestVolume(),
  1075. pvc: makeVolumeModePVC("8G", &blockMode, nil),
  1076. enableBlock: false,
  1077. },
  1078. "feature disabled - pvc filesystem and pv block": {
  1079. isExpectedMismatch: false,
  1080. vol: createVolumeModeBlockTestVolume(),
  1081. pvc: makeVolumeModePVC("8G", &filesystemMode, nil),
  1082. enableBlock: false,
  1083. },
  1084. "feature disabled - pvc block and pv block": {
  1085. isExpectedMismatch: false,
  1086. vol: createVolumeModeBlockTestVolume(),
  1087. pvc: makeVolumeModePVC("8G", &blockMode, nil),
  1088. enableBlock: false,
  1089. },
  1090. "feature disabled - pvc filesystem and pv filesystem": {
  1091. isExpectedMismatch: false,
  1092. vol: createVolumeModeFilesystemTestVolume(),
  1093. pvc: makeVolumeModePVC("8G", &filesystemMode, nil),
  1094. enableBlock: false,
  1095. },
  1096. }
  1097. for name, scenario := range scenarios {
  1098. t.Run(name, func(t *testing.T) {
  1099. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.BlockVolume, scenario.enableBlock)()
  1100. expectedMismatch, err := pvutil.CheckVolumeModeMismatches(&scenario.pvc.Spec, &scenario.vol.Spec)
  1101. if err != nil {
  1102. t.Errorf("Unexpected failure for checkVolumeModeMismatches: %v", err)
  1103. }
  1104. // expected to match but either got an error or no returned pvmatch
  1105. if expectedMismatch && !scenario.isExpectedMismatch {
  1106. t.Errorf("Unexpected failure for scenario, expected not to mismatch on modes but did: %s", name)
  1107. }
  1108. if !expectedMismatch && scenario.isExpectedMismatch {
  1109. t.Errorf("Unexpected failure for scenario, did not mismatch on mode when expected to mismatch: %s", name)
  1110. }
  1111. })
  1112. }
  1113. }
  1114. func TestFilteringVolumeModes(t *testing.T) {
  1115. blockMode := v1.PersistentVolumeBlock
  1116. filesystemMode := v1.PersistentVolumeFilesystem
  1117. // If feature gate is enabled, VolumeMode will always be defaulted
  1118. // If feature gate is disabled, VolumeMode is dropped by API and ignored
  1119. scenarios := map[string]struct {
  1120. isExpectedMatch bool
  1121. vol persistentVolumeOrderedIndex
  1122. pvc *v1.PersistentVolumeClaim
  1123. enableBlock bool
  1124. }{
  1125. "1-1 feature enabled - pvc block and pv filesystem": {
  1126. isExpectedMatch: false,
  1127. vol: createTestVolOrderedIndex(createVolumeModeFilesystemTestVolume()),
  1128. pvc: makeVolumeModePVC("8G", &blockMode, nil),
  1129. enableBlock: true,
  1130. },
  1131. "1-2 feature enabled - pvc filesystem and pv block": {
  1132. isExpectedMatch: false,
  1133. vol: createTestVolOrderedIndex(createVolumeModeBlockTestVolume()),
  1134. pvc: makeVolumeModePVC("8G", &filesystemMode, nil),
  1135. enableBlock: true,
  1136. },
  1137. "1-3 feature enabled - pvc block and pv no mode with default filesystem": {
  1138. isExpectedMatch: false,
  1139. vol: createTestVolOrderedIndex(createVolumeModeFilesystemTestVolume()),
  1140. pvc: makeVolumeModePVC("8G", &blockMode, nil),
  1141. enableBlock: true,
  1142. },
  1143. "1-4 feature enabled - pvc no mode defaulted to filesystem and pv block": {
  1144. isExpectedMatch: false,
  1145. vol: createTestVolOrderedIndex(createVolumeModeBlockTestVolume()),
  1146. pvc: makeVolumeModePVC("8G", &filesystemMode, nil),
  1147. enableBlock: true,
  1148. },
  1149. "1-5 feature enabled - pvc block and pv block": {
  1150. isExpectedMatch: true,
  1151. vol: createTestVolOrderedIndex(createVolumeModeBlockTestVolume()),
  1152. pvc: makeVolumeModePVC("8G", &blockMode, nil),
  1153. enableBlock: true,
  1154. },
  1155. "1-6 feature enabled - pvc filesystem and pv filesystem": {
  1156. isExpectedMatch: true,
  1157. vol: createTestVolOrderedIndex(createVolumeModeFilesystemTestVolume()),
  1158. pvc: makeVolumeModePVC("8G", &filesystemMode, nil),
  1159. enableBlock: true,
  1160. },
  1161. "1-7 feature enabled - pvc mode is nil and defaulted and pv mode is nil and defaulted": {
  1162. isExpectedMatch: true,
  1163. vol: createTestVolOrderedIndex(createVolumeModeFilesystemTestVolume()),
  1164. pvc: makeVolumeModePVC("8G", &filesystemMode, nil),
  1165. enableBlock: true,
  1166. },
  1167. "2-1 feature disabled - pvc mode is nil and pv mode is nil": {
  1168. isExpectedMatch: true,
  1169. vol: createTestVolOrderedIndex(testVolume("nomode-1", "8G")),
  1170. pvc: makeVolumeModePVC("8G", nil, nil),
  1171. enableBlock: false,
  1172. },
  1173. "2-2 feature disabled - pvc mode is block and pv mode is block - fields should be dropped by api and not analyzed with gate disabled": {
  1174. isExpectedMatch: true,
  1175. vol: createTestVolOrderedIndex(createVolumeModeBlockTestVolume()),
  1176. pvc: makeVolumeModePVC("8G", &blockMode, nil),
  1177. enableBlock: false,
  1178. },
  1179. "2-3 feature disabled - pvc mode is filesystem and pv mode is filesystem - fields should be dropped by api and not analyzed with gate disabled": {
  1180. isExpectedMatch: true,
  1181. vol: createTestVolOrderedIndex(createVolumeModeFilesystemTestVolume()),
  1182. pvc: makeVolumeModePVC("8G", &filesystemMode, nil),
  1183. enableBlock: false,
  1184. },
  1185. }
  1186. for name, scenario := range scenarios {
  1187. t.Run(name, func(t *testing.T) {
  1188. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.BlockVolume, scenario.enableBlock)()
  1189. pvmatch, err := scenario.vol.findBestMatchForClaim(scenario.pvc, false)
  1190. // expected to match but either got an error or no returned pvmatch
  1191. if pvmatch == nil && scenario.isExpectedMatch {
  1192. t.Errorf("Unexpected failure for scenario, no matching volume: %s", name)
  1193. }
  1194. if err != nil && scenario.isExpectedMatch {
  1195. t.Errorf("Unexpected failure for scenario: %s - %+v", name, err)
  1196. }
  1197. // expected to not match but either got an error or a returned pvmatch
  1198. if pvmatch != nil && !scenario.isExpectedMatch {
  1199. t.Errorf("Unexpected failure for scenario, expected no matching volume: %s", name)
  1200. }
  1201. if err != nil && !scenario.isExpectedMatch {
  1202. t.Errorf("Unexpected failure for scenario: %s - %+v", name, err)
  1203. }
  1204. })
  1205. }
  1206. }
  1207. func TestStorageObjectInUseProtectionFiltering(t *testing.T) {
  1208. fs := v1.PersistentVolumeFilesystem
  1209. pv := &v1.PersistentVolume{
  1210. ObjectMeta: metav1.ObjectMeta{
  1211. Name: "pv1",
  1212. Annotations: map[string]string{},
  1213. },
  1214. Spec: v1.PersistentVolumeSpec{
  1215. Capacity: v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G")},
  1216. PersistentVolumeSource: v1.PersistentVolumeSource{HostPath: &v1.HostPathVolumeSource{}},
  1217. AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
  1218. VolumeMode: &fs,
  1219. },
  1220. Status: v1.PersistentVolumeStatus{
  1221. Phase: v1.VolumeAvailable,
  1222. },
  1223. }
  1224. pvToDelete := pv.DeepCopy()
  1225. now := metav1.Now()
  1226. pvToDelete.ObjectMeta.DeletionTimestamp = &now
  1227. pvc := &v1.PersistentVolumeClaim{
  1228. ObjectMeta: metav1.ObjectMeta{
  1229. Name: "pvc1",
  1230. Namespace: "myns",
  1231. },
  1232. Spec: v1.PersistentVolumeClaimSpec{
  1233. AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
  1234. Resources: v1.ResourceRequirements{Requests: v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse("1G")}},
  1235. VolumeMode: &fs,
  1236. },
  1237. }
  1238. satisfyingTestCases := map[string]struct {
  1239. isExpectedMatch bool
  1240. vol *v1.PersistentVolume
  1241. pvc *v1.PersistentVolumeClaim
  1242. enableStorageObjectInUseProtection bool
  1243. }{
  1244. "feature enabled - pv deletionTimeStamp not set": {
  1245. isExpectedMatch: true,
  1246. vol: pv,
  1247. pvc: pvc,
  1248. enableStorageObjectInUseProtection: true,
  1249. },
  1250. "feature enabled - pv deletionTimeStamp set": {
  1251. isExpectedMatch: false,
  1252. vol: pvToDelete,
  1253. pvc: pvc,
  1254. enableStorageObjectInUseProtection: true,
  1255. },
  1256. "feature disabled - pv deletionTimeStamp not set": {
  1257. isExpectedMatch: true,
  1258. vol: pv,
  1259. pvc: pvc,
  1260. enableStorageObjectInUseProtection: false,
  1261. },
  1262. "feature disabled - pv deletionTimeStamp set": {
  1263. isExpectedMatch: true,
  1264. vol: pvToDelete,
  1265. pvc: pvc,
  1266. enableStorageObjectInUseProtection: false,
  1267. },
  1268. }
  1269. for name, testCase := range satisfyingTestCases {
  1270. t.Run(name, func(t *testing.T) {
  1271. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StorageObjectInUseProtection, testCase.enableStorageObjectInUseProtection)()
  1272. err := checkVolumeSatisfyClaim(testCase.vol, testCase.pvc)
  1273. // expected to match but got an error
  1274. if err != nil && testCase.isExpectedMatch {
  1275. t.Errorf("%s: expected to match but got an error: %v", name, err)
  1276. }
  1277. // not expected to match but did
  1278. if err == nil && !testCase.isExpectedMatch {
  1279. t.Errorf("%s: not expected to match but did", name)
  1280. }
  1281. })
  1282. }
  1283. filteringTestCases := map[string]struct {
  1284. isExpectedMatch bool
  1285. vol persistentVolumeOrderedIndex
  1286. pvc *v1.PersistentVolumeClaim
  1287. enableStorageObjectInUseProtection bool
  1288. }{
  1289. "feature enabled - pv deletionTimeStamp not set": {
  1290. isExpectedMatch: true,
  1291. vol: createTestVolOrderedIndex(pv),
  1292. pvc: pvc,
  1293. enableStorageObjectInUseProtection: true,
  1294. },
  1295. "feature enabled - pv deletionTimeStamp set": {
  1296. isExpectedMatch: false,
  1297. vol: createTestVolOrderedIndex(pvToDelete),
  1298. pvc: pvc,
  1299. enableStorageObjectInUseProtection: true,
  1300. },
  1301. "feature disabled - pv deletionTimeStamp not set": {
  1302. isExpectedMatch: true,
  1303. vol: createTestVolOrderedIndex(pv),
  1304. pvc: pvc,
  1305. enableStorageObjectInUseProtection: false,
  1306. },
  1307. "feature disabled - pv deletionTimeStamp set": {
  1308. isExpectedMatch: true,
  1309. vol: createTestVolOrderedIndex(pvToDelete),
  1310. pvc: pvc,
  1311. enableStorageObjectInUseProtection: false,
  1312. },
  1313. }
  1314. for name, testCase := range filteringTestCases {
  1315. t.Run(name, func(t *testing.T) {
  1316. defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StorageObjectInUseProtection, testCase.enableStorageObjectInUseProtection)()
  1317. pvmatch, err := testCase.vol.findBestMatchForClaim(testCase.pvc, false)
  1318. // expected to match but either got an error or no returned pvmatch
  1319. if pvmatch == nil && testCase.isExpectedMatch {
  1320. t.Errorf("Unexpected failure for testcase, no matching volume: %s", name)
  1321. }
  1322. if err != nil && testCase.isExpectedMatch {
  1323. t.Errorf("Unexpected failure for testcase: %s - %+v", name, err)
  1324. }
  1325. // expected to not match but either got an error or a returned pvmatch
  1326. if pvmatch != nil && !testCase.isExpectedMatch {
  1327. t.Errorf("Unexpected failure for testcase, expected no matching volume: %s", name)
  1328. }
  1329. if err != nil && !testCase.isExpectedMatch {
  1330. t.Errorf("Unexpected failure for testcase: %s - %+v", name, err)
  1331. }
  1332. })
  1333. }
  1334. }
  1335. func TestFindingPreboundVolumes(t *testing.T) {
  1336. fs := v1.PersistentVolumeFilesystem
  1337. claim := &v1.PersistentVolumeClaim{
  1338. ObjectMeta: metav1.ObjectMeta{
  1339. Name: "claim01",
  1340. Namespace: "myns",
  1341. SelfLink: testapi.Default.SelfLink("pvc", ""),
  1342. },
  1343. Spec: v1.PersistentVolumeClaimSpec{
  1344. AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
  1345. Resources: v1.ResourceRequirements{Requests: v1.ResourceList{v1.ResourceName(v1.ResourceStorage): resource.MustParse("1Gi")}},
  1346. VolumeMode: &fs,
  1347. },
  1348. }
  1349. claimRef, err := ref.GetReference(scheme.Scheme, claim)
  1350. if err != nil {
  1351. t.Errorf("error getting claimRef: %v", err)
  1352. }
  1353. pv1 := testVolume("pv1", "1Gi")
  1354. pv5 := testVolume("pv5", "5Gi")
  1355. pv8 := testVolume("pv8", "8Gi")
  1356. pvBadSize := testVolume("pvBadSize", "1Mi")
  1357. pvBadMode := testVolume("pvBadMode", "1Gi")
  1358. pvBadMode.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany}
  1359. index := newPersistentVolumeOrderedIndex()
  1360. index.store.Add(pv1)
  1361. index.store.Add(pv5)
  1362. index.store.Add(pv8)
  1363. index.store.Add(pvBadSize)
  1364. index.store.Add(pvBadMode)
  1365. // expected exact match on size
  1366. volume, _ := index.findBestMatchForClaim(claim, false)
  1367. if volume.Name != pv1.Name {
  1368. t.Errorf("Expected %s but got volume %s instead", pv1.Name, volume.Name)
  1369. }
  1370. // pretend the exact match is pre-bound. should get the next size up.
  1371. pv1.Spec.ClaimRef = &v1.ObjectReference{Name: "foo", Namespace: "bar"}
  1372. volume, _ = index.findBestMatchForClaim(claim, false)
  1373. if volume.Name != pv5.Name {
  1374. t.Errorf("Expected %s but got volume %s instead", pv5.Name, volume.Name)
  1375. }
  1376. // pretend the exact match is available but the largest volume is pre-bound to the claim.
  1377. pv1.Spec.ClaimRef = nil
  1378. pv8.Spec.ClaimRef = claimRef
  1379. volume, _ = index.findBestMatchForClaim(claim, false)
  1380. if volume.Name != pv8.Name {
  1381. t.Errorf("Expected %s but got volume %s instead", pv8.Name, volume.Name)
  1382. }
  1383. // pretend the volume with too small a size is pre-bound to the claim. should get the exact match.
  1384. pv8.Spec.ClaimRef = nil
  1385. pvBadSize.Spec.ClaimRef = claimRef
  1386. volume, _ = index.findBestMatchForClaim(claim, false)
  1387. if volume.Name != pv1.Name {
  1388. t.Errorf("Expected %s but got volume %s instead", pv1.Name, volume.Name)
  1389. }
  1390. // pretend the volume without the right access mode is pre-bound to the claim. should get the exact match.
  1391. pvBadSize.Spec.ClaimRef = nil
  1392. pvBadMode.Spec.ClaimRef = claimRef
  1393. volume, _ = index.findBestMatchForClaim(claim, false)
  1394. if volume.Name != pv1.Name {
  1395. t.Errorf("Expected %s but got volume %s instead", pv1.Name, volume.Name)
  1396. }
  1397. }
  1398. func TestBestMatchDelayed(t *testing.T) {
  1399. volList := newPersistentVolumeOrderedIndex()
  1400. for _, pv := range createTestVolumes() {
  1401. volList.store.Add(pv)
  1402. }
  1403. // binding through PV controller should be delayed
  1404. claim := makePVC("8G", nil)
  1405. volume, err := volList.findBestMatchForClaim(claim, true)
  1406. if err != nil {
  1407. t.Errorf("Unexpected error matching volume by claim: %v", err)
  1408. }
  1409. if volume != nil {
  1410. t.Errorf("Unexpected match with %q", volume.UID)
  1411. }
  1412. }
  1413. func TestFindMatchVolumeWithNode(t *testing.T) {
  1414. volumes := createTestVolumes()
  1415. node1 := &v1.Node{
  1416. ObjectMeta: metav1.ObjectMeta{
  1417. Labels: map[string]string{"key1": "value1"},
  1418. },
  1419. }
  1420. node2 := &v1.Node{
  1421. ObjectMeta: metav1.ObjectMeta{
  1422. Labels: map[string]string{"key1": "value2"},
  1423. },
  1424. }
  1425. node3 := &v1.Node{
  1426. ObjectMeta: metav1.ObjectMeta{
  1427. Labels: map[string]string{"key1": "value3"},
  1428. },
  1429. }
  1430. node4 := &v1.Node{
  1431. ObjectMeta: metav1.ObjectMeta{
  1432. Labels: map[string]string{"key1": "value4"},
  1433. },
  1434. }
  1435. scenarios := map[string]struct {
  1436. expectedMatch string
  1437. claim *v1.PersistentVolumeClaim
  1438. node *v1.Node
  1439. excludedVolumes map[string]*v1.PersistentVolume
  1440. }{
  1441. "success-match": {
  1442. expectedMatch: "affinity-pv",
  1443. claim: makePVC("100G", func(pvc *v1.PersistentVolumeClaim) {
  1444. pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}
  1445. pvc.Spec.StorageClassName = &classWait
  1446. }),
  1447. node: node1,
  1448. },
  1449. "success-prebound": {
  1450. expectedMatch: "affinity-prebound",
  1451. claim: makePVC("100G", func(pvc *v1.PersistentVolumeClaim) {
  1452. pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}
  1453. pvc.Spec.StorageClassName = &classWait
  1454. pvc.Name = "claim02"
  1455. }),
  1456. node: node1,
  1457. },
  1458. "success-exclusion": {
  1459. expectedMatch: "affinity-pv2",
  1460. claim: makePVC("100G", func(pvc *v1.PersistentVolumeClaim) {
  1461. pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}
  1462. pvc.Spec.StorageClassName = &classWait
  1463. }),
  1464. node: node1,
  1465. excludedVolumes: map[string]*v1.PersistentVolume{"affinity001": nil},
  1466. },
  1467. "fail-exclusion": {
  1468. expectedMatch: "",
  1469. claim: makePVC("100G", func(pvc *v1.PersistentVolumeClaim) {
  1470. pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}
  1471. pvc.Spec.StorageClassName = &classWait
  1472. }),
  1473. node: node1,
  1474. excludedVolumes: map[string]*v1.PersistentVolume{"affinity001": nil, "affinity002": nil},
  1475. },
  1476. "fail-accessmode": {
  1477. expectedMatch: "",
  1478. claim: makePVC("100G", func(pvc *v1.PersistentVolumeClaim) {
  1479. pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteMany}
  1480. pvc.Spec.StorageClassName = &classWait
  1481. }),
  1482. node: node1,
  1483. },
  1484. "fail-nodeaffinity": {
  1485. expectedMatch: "",
  1486. claim: makePVC("100G", func(pvc *v1.PersistentVolumeClaim) {
  1487. pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}
  1488. pvc.Spec.StorageClassName = &classWait
  1489. }),
  1490. node: node2,
  1491. },
  1492. "fail-prebound-node-affinity": {
  1493. expectedMatch: "",
  1494. claim: makePVC("100G", func(pvc *v1.PersistentVolumeClaim) {
  1495. pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}
  1496. pvc.Spec.StorageClassName = &classWait
  1497. pvc.Name = "claim02"
  1498. }),
  1499. node: node3,
  1500. },
  1501. "fail-nonavaiable": {
  1502. expectedMatch: "",
  1503. claim: makePVC("100G", func(pvc *v1.PersistentVolumeClaim) {
  1504. pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}
  1505. pvc.Spec.StorageClassName = &classWait
  1506. pvc.Name = "claim04"
  1507. }),
  1508. node: node4,
  1509. },
  1510. "success-bad-and-good-node-affinity": {
  1511. expectedMatch: "affinity-pv3",
  1512. claim: makePVC("100G", func(pvc *v1.PersistentVolumeClaim) {
  1513. pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}
  1514. pvc.Spec.StorageClassName = &classWait
  1515. pvc.Name = "claim03"
  1516. }),
  1517. node: node3,
  1518. },
  1519. }
  1520. for name, scenario := range scenarios {
  1521. volume, err := pvutil.FindMatchingVolume(scenario.claim, volumes, scenario.node, scenario.excludedVolumes, true)
  1522. if err != nil {
  1523. t.Errorf("Unexpected error matching volume by claim: %v", err)
  1524. }
  1525. if len(scenario.expectedMatch) != 0 && volume == nil {
  1526. t.Errorf("Expected match but received nil volume for scenario: %s", name)
  1527. }
  1528. if len(scenario.expectedMatch) != 0 && volume != nil && string(volume.UID) != scenario.expectedMatch {
  1529. t.Errorf("Expected %s but got volume %s in scenario %s", scenario.expectedMatch, volume.UID, name)
  1530. }
  1531. if len(scenario.expectedMatch) == 0 && volume != nil {
  1532. t.Errorf("Unexpected match for scenario: %s, matched with %s instead", name, volume.UID)
  1533. }
  1534. }
  1535. }
  1536. func TestCheckAccessModes(t *testing.T) {
  1537. volume := &v1.PersistentVolume{
  1538. Spec: v1.PersistentVolumeSpec{
  1539. AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce, v1.ReadWriteMany},
  1540. },
  1541. }
  1542. scenarios := map[string]struct {
  1543. shouldSucceed bool
  1544. claim *v1.PersistentVolumeClaim
  1545. }{
  1546. "success-single-mode": {
  1547. shouldSucceed: true,
  1548. claim: makePVC("100G", func(pvc *v1.PersistentVolumeClaim) {
  1549. pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteMany}
  1550. }),
  1551. },
  1552. "success-many-modes": {
  1553. shouldSucceed: true,
  1554. claim: makePVC("100G", func(pvc *v1.PersistentVolumeClaim) {
  1555. pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteMany, v1.ReadWriteOnce}
  1556. }),
  1557. },
  1558. "fail-single-mode": {
  1559. shouldSucceed: false,
  1560. claim: makePVC("100G", func(pvc *v1.PersistentVolumeClaim) {
  1561. pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadOnlyMany}
  1562. }),
  1563. },
  1564. "fail-many-modes": {
  1565. shouldSucceed: false,
  1566. claim: makePVC("100G", func(pvc *v1.PersistentVolumeClaim) {
  1567. pvc.Spec.AccessModes = []v1.PersistentVolumeAccessMode{v1.ReadWriteMany, v1.ReadOnlyMany}
  1568. }),
  1569. },
  1570. }
  1571. for name, scenario := range scenarios {
  1572. result := pvutil.CheckAccessModes(scenario.claim, volume)
  1573. if result != scenario.shouldSucceed {
  1574. t.Errorf("Test %q failed: Expected %v, got %v", name, scenario.shouldSucceed, result)
  1575. }
  1576. }
  1577. }
  1578. // byCapacity is used to order volumes by ascending storage size
  1579. type byCapacity struct {
  1580. volumes []*v1.PersistentVolume
  1581. }
  1582. func (c byCapacity) Less(i, j int) bool {
  1583. return matchStorageCapacity(c.volumes[i], c.volumes[j])
  1584. }
  1585. func (c byCapacity) Swap(i, j int) {
  1586. c.volumes[i], c.volumes[j] = c.volumes[j], c.volumes[i]
  1587. }
  1588. func (c byCapacity) Len() int {
  1589. return len(c.volumes)
  1590. }
  1591. // matchStorageCapacity is a matchPredicate used to sort and find volumes
  1592. func matchStorageCapacity(pvA, pvB *v1.PersistentVolume) bool {
  1593. aQty := pvA.Spec.Capacity[v1.ResourceStorage]
  1594. bQty := pvB.Spec.Capacity[v1.ResourceStorage]
  1595. return aQty.Cmp(bQty) <= 0
  1596. }