provision_test.go 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  1. /*
  2. Copyright 2016 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. "errors"
  16. "testing"
  17. v1 "k8s.io/api/core/v1"
  18. storage "k8s.io/api/storage/v1"
  19. apierrs "k8s.io/apimachinery/pkg/api/errors"
  20. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  21. api "k8s.io/kubernetes/pkg/apis/core"
  22. pvtesting "k8s.io/kubernetes/pkg/controller/volume/persistentvolume/testing"
  23. pvutil "k8s.io/kubernetes/pkg/controller/volume/persistentvolume/util"
  24. )
  25. var class1Parameters = map[string]string{
  26. "param1": "value1",
  27. }
  28. var class2Parameters = map[string]string{
  29. "param2": "value2",
  30. }
  31. var deleteReclaimPolicy = v1.PersistentVolumeReclaimDelete
  32. var modeImmediate = storage.VolumeBindingImmediate
  33. var storageClasses = []*storage.StorageClass{
  34. {
  35. TypeMeta: metav1.TypeMeta{
  36. Kind: "StorageClass",
  37. },
  38. ObjectMeta: metav1.ObjectMeta{
  39. Name: "gold",
  40. },
  41. Provisioner: mockPluginName,
  42. Parameters: class1Parameters,
  43. ReclaimPolicy: &deleteReclaimPolicy,
  44. VolumeBindingMode: &modeImmediate,
  45. },
  46. {
  47. TypeMeta: metav1.TypeMeta{
  48. Kind: "StorageClass",
  49. },
  50. ObjectMeta: metav1.ObjectMeta{
  51. Name: "silver",
  52. },
  53. Provisioner: mockPluginName,
  54. Parameters: class2Parameters,
  55. ReclaimPolicy: &deleteReclaimPolicy,
  56. VolumeBindingMode: &modeImmediate,
  57. },
  58. {
  59. TypeMeta: metav1.TypeMeta{
  60. Kind: "StorageClass",
  61. },
  62. ObjectMeta: metav1.ObjectMeta{
  63. Name: "external",
  64. },
  65. Provisioner: "vendor.com/my-volume",
  66. Parameters: class1Parameters,
  67. ReclaimPolicy: &deleteReclaimPolicy,
  68. VolumeBindingMode: &modeImmediate,
  69. },
  70. {
  71. TypeMeta: metav1.TypeMeta{
  72. Kind: "StorageClass",
  73. },
  74. ObjectMeta: metav1.ObjectMeta{
  75. Name: "unknown-internal",
  76. },
  77. Provisioner: "kubernetes.io/unknown",
  78. Parameters: class1Parameters,
  79. ReclaimPolicy: &deleteReclaimPolicy,
  80. VolumeBindingMode: &modeImmediate,
  81. },
  82. {
  83. TypeMeta: metav1.TypeMeta{
  84. Kind: "StorageClass",
  85. },
  86. ObjectMeta: metav1.ObjectMeta{
  87. Name: "unsupported-mountoptions",
  88. },
  89. Provisioner: mockPluginName,
  90. Parameters: class1Parameters,
  91. ReclaimPolicy: &deleteReclaimPolicy,
  92. MountOptions: []string{"foo"},
  93. VolumeBindingMode: &modeImmediate,
  94. },
  95. }
  96. // call to storageClass 1, returning an error
  97. var provision1Error = provisionCall{
  98. ret: errors.New("Mock provisioner error"),
  99. expectedParameters: class1Parameters,
  100. }
  101. // call to storageClass 1, returning a valid PV
  102. var provision1Success = provisionCall{
  103. ret: nil,
  104. expectedParameters: class1Parameters,
  105. }
  106. // call to storageClass 2, returning a valid PV
  107. var provision2Success = provisionCall{
  108. ret: nil,
  109. expectedParameters: class2Parameters,
  110. }
  111. var provisionAlphaSuccess = provisionCall{
  112. ret: nil,
  113. }
  114. // Test single call to syncVolume, expecting provisioning to happen.
  115. // 1. Fill in the controller with initial data
  116. // 2. Call the syncVolume *once*.
  117. // 3. Compare resulting volumes with expected volumes.
  118. func TestProvisionSync(t *testing.T) {
  119. tests := []controllerTest{
  120. {
  121. // Provision a volume (with a default class)
  122. "11-1 - successful provision with storage class 1",
  123. novolumes,
  124. newVolumeArray("pvc-uid11-1", "1Gi", "uid11-1", "claim11-1", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classGold, pvutil.AnnBoundByController, pvutil.AnnDynamicallyProvisioned),
  125. newClaimArray("claim11-1", "uid11-1", "1Gi", "", v1.ClaimPending, &classGold),
  126. // Binding will be completed in the next syncClaim
  127. newClaimArray("claim11-1", "uid11-1", "1Gi", "", v1.ClaimPending, &classGold, pvutil.AnnStorageProvisioner),
  128. []string{"Normal ProvisioningSucceeded"}, noerrors, wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim),
  129. },
  130. {
  131. // Provision failure - plugin not found
  132. "11-2 - plugin not found",
  133. novolumes,
  134. novolumes,
  135. newClaimArray("claim11-2", "uid11-2", "1Gi", "", v1.ClaimPending, &classGold),
  136. newClaimArray("claim11-2", "uid11-2", "1Gi", "", v1.ClaimPending, &classGold),
  137. []string{"Warning ProvisioningFailed"}, noerrors,
  138. testSyncClaim,
  139. },
  140. {
  141. // Provision failure - newProvisioner returns error
  142. "11-3 - newProvisioner failure",
  143. novolumes,
  144. novolumes,
  145. newClaimArray("claim11-3", "uid11-3", "1Gi", "", v1.ClaimPending, &classGold),
  146. newClaimArray("claim11-3", "uid11-3", "1Gi", "", v1.ClaimPending, &classGold, pvutil.AnnStorageProvisioner),
  147. []string{"Warning ProvisioningFailed"}, noerrors,
  148. wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim),
  149. },
  150. {
  151. // Provision failure - Provision returns error
  152. "11-4 - provision failure",
  153. novolumes,
  154. novolumes,
  155. newClaimArray("claim11-4", "uid11-4", "1Gi", "", v1.ClaimPending, &classGold),
  156. newClaimArray("claim11-4", "uid11-4", "1Gi", "", v1.ClaimPending, &classGold, pvutil.AnnStorageProvisioner),
  157. []string{"Warning ProvisioningFailed"}, noerrors,
  158. wrapTestWithProvisionCalls([]provisionCall{provision1Error}, testSyncClaim),
  159. },
  160. {
  161. // No provisioning if there is a matching volume available
  162. "11-6 - provisioning when there is a volume available",
  163. newVolumeArray("volume11-6", "1Gi", "", "", v1.VolumeAvailable, v1.PersistentVolumeReclaimRetain, classGold),
  164. newVolumeArray("volume11-6", "1Gi", "uid11-6", "claim11-6", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, classGold, pvutil.AnnBoundByController),
  165. newClaimArray("claim11-6", "uid11-6", "1Gi", "", v1.ClaimPending, &classGold),
  166. newClaimArray("claim11-6", "uid11-6", "1Gi", "volume11-6", v1.ClaimBound, &classGold, pvutil.AnnBoundByController, pvutil.AnnBindCompleted),
  167. noevents, noerrors,
  168. // No provisioning plugin confingure - makes the test fail when
  169. // the controller erroneously tries to provision something
  170. wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim),
  171. },
  172. {
  173. // Provision success? - claim is bound before provisioner creates
  174. // a volume.
  175. "11-7 - claim is bound before provisioning",
  176. novolumes,
  177. newVolumeArray("pvc-uid11-7", "1Gi", "uid11-7", "claim11-7", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classGold, pvutil.AnnBoundByController, pvutil.AnnDynamicallyProvisioned),
  178. newClaimArray("claim11-7", "uid11-7", "1Gi", "", v1.ClaimPending, &classGold),
  179. // The claim would be bound in next syncClaim
  180. newClaimArray("claim11-7", "uid11-7", "1Gi", "", v1.ClaimPending, &classGold, pvutil.AnnStorageProvisioner),
  181. noevents, noerrors,
  182. wrapTestWithInjectedOperation(wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim), func(ctrl *PersistentVolumeController, reactor *pvtesting.VolumeReactor) {
  183. // Create a volume before provisionClaimOperation starts.
  184. // This similates a parallel controller provisioning the volume.
  185. volume := newVolume("pvc-uid11-7", "1Gi", "uid11-7", "claim11-7", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classGold, pvutil.AnnBoundByController, pvutil.AnnDynamicallyProvisioned)
  186. reactor.AddVolume(volume)
  187. }),
  188. },
  189. {
  190. // Provision success - cannot save provisioned PV once,
  191. // second retry succeeds
  192. "11-8 - cannot save provisioned volume",
  193. novolumes,
  194. newVolumeArray("pvc-uid11-8", "1Gi", "uid11-8", "claim11-8", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classGold, pvutil.AnnBoundByController, pvutil.AnnDynamicallyProvisioned),
  195. newClaimArray("claim11-8", "uid11-8", "1Gi", "", v1.ClaimPending, &classGold),
  196. // Binding will be completed in the next syncClaim
  197. newClaimArray("claim11-8", "uid11-8", "1Gi", "", v1.ClaimPending, &classGold, pvutil.AnnStorageProvisioner),
  198. []string{"Normal ProvisioningSucceeded"},
  199. []pvtesting.ReactorError{
  200. // Inject error to the first
  201. // kubeclient.PersistentVolumes.Create() call. All other calls
  202. // will succeed.
  203. {Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error")},
  204. },
  205. wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim),
  206. },
  207. {
  208. // Provision success? - cannot save provisioned PV five times,
  209. // volume is deleted and delete succeeds
  210. "11-9 - cannot save provisioned volume, delete succeeds",
  211. novolumes,
  212. novolumes,
  213. newClaimArray("claim11-9", "uid11-9", "1Gi", "", v1.ClaimPending, &classGold),
  214. newClaimArray("claim11-9", "uid11-9", "1Gi", "", v1.ClaimPending, &classGold, pvutil.AnnStorageProvisioner),
  215. []string{"Warning ProvisioningFailed"},
  216. []pvtesting.ReactorError{
  217. // Inject error to five kubeclient.PersistentVolumes.Create()
  218. // calls
  219. {Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error1")},
  220. {Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error2")},
  221. {Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error3")},
  222. {Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error4")},
  223. {Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error5")},
  224. },
  225. wrapTestWithPluginCalls(
  226. nil, // recycle calls
  227. []error{nil}, // delete calls
  228. []provisionCall{provision1Success}, // provision calls
  229. testSyncClaim,
  230. ),
  231. },
  232. {
  233. // Provision failure - cannot save provisioned PV five times,
  234. // volume delete failed - no plugin found
  235. "11-10 - cannot save provisioned volume, no delete plugin found",
  236. novolumes,
  237. novolumes,
  238. newClaimArray("claim11-10", "uid11-10", "1Gi", "", v1.ClaimPending, &classGold),
  239. newClaimArray("claim11-10", "uid11-10", "1Gi", "", v1.ClaimPending, &classGold, pvutil.AnnStorageProvisioner),
  240. []string{"Warning ProvisioningFailed", "Warning ProvisioningCleanupFailed"},
  241. []pvtesting.ReactorError{
  242. // Inject error to five kubeclient.PersistentVolumes.Create()
  243. // calls
  244. {Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error1")},
  245. {Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error2")},
  246. {Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error3")},
  247. {Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error4")},
  248. {Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error5")},
  249. },
  250. // No deleteCalls are configured, which results into no deleter plugin available for the volume
  251. wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim),
  252. },
  253. {
  254. // Provision failure - cannot save provisioned PV five times,
  255. // volume delete failed - deleter returns error five times
  256. "11-11 - cannot save provisioned volume, deleter fails",
  257. novolumes,
  258. novolumes,
  259. newClaimArray("claim11-11", "uid11-11", "1Gi", "", v1.ClaimPending, &classGold),
  260. newClaimArray("claim11-11", "uid11-11", "1Gi", "", v1.ClaimPending, &classGold, pvutil.AnnStorageProvisioner),
  261. []string{"Warning ProvisioningFailed", "Warning ProvisioningCleanupFailed"},
  262. []pvtesting.ReactorError{
  263. // Inject error to five kubeclient.PersistentVolumes.Create()
  264. // calls
  265. {Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error1")},
  266. {Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error2")},
  267. {Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error3")},
  268. {Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error4")},
  269. {Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error5")},
  270. },
  271. wrapTestWithPluginCalls(
  272. nil, // recycle calls
  273. []error{ // delete calls
  274. errors.New("Mock deletion error1"),
  275. errors.New("Mock deletion error2"),
  276. errors.New("Mock deletion error3"),
  277. errors.New("Mock deletion error4"),
  278. errors.New("Mock deletion error5"),
  279. },
  280. []provisionCall{provision1Success}, // provision calls
  281. testSyncClaim),
  282. },
  283. {
  284. // Provision failure - cannot save provisioned PV five times,
  285. // volume delete succeeds 2nd time
  286. "11-12 - cannot save provisioned volume, delete succeeds 2nd time",
  287. novolumes,
  288. novolumes,
  289. newClaimArray("claim11-12", "uid11-12", "1Gi", "", v1.ClaimPending, &classGold),
  290. newClaimArray("claim11-12", "uid11-12", "1Gi", "", v1.ClaimPending, &classGold, pvutil.AnnStorageProvisioner),
  291. []string{"Warning ProvisioningFailed"},
  292. []pvtesting.ReactorError{
  293. // Inject error to five kubeclient.PersistentVolumes.Create()
  294. // calls
  295. {Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error1")},
  296. {Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error2")},
  297. {Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error3")},
  298. {Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error4")},
  299. {Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error5")},
  300. },
  301. wrapTestWithPluginCalls(
  302. nil, // recycle calls
  303. []error{ // delete calls
  304. errors.New("Mock deletion error1"),
  305. nil,
  306. }, // provison calls
  307. []provisionCall{provision1Success},
  308. testSyncClaim,
  309. ),
  310. },
  311. {
  312. // Provision a volume (with non-default class)
  313. "11-13 - successful provision with storage class 2",
  314. novolumes,
  315. newVolumeArray("pvc-uid11-13", "1Gi", "uid11-13", "claim11-13", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classSilver, pvutil.AnnBoundByController, pvutil.AnnDynamicallyProvisioned),
  316. newClaimArray("claim11-13", "uid11-13", "1Gi", "", v1.ClaimPending, &classSilver),
  317. // Binding will be completed in the next syncClaim
  318. newClaimArray("claim11-13", "uid11-13", "1Gi", "", v1.ClaimPending, &classSilver, pvutil.AnnStorageProvisioner),
  319. []string{"Normal ProvisioningSucceeded"}, noerrors, wrapTestWithProvisionCalls([]provisionCall{provision2Success}, testSyncClaim),
  320. },
  321. {
  322. // Provision error - non existing class
  323. "11-14 - fail due to non-existing class",
  324. novolumes,
  325. novolumes,
  326. newClaimArray("claim11-14", "uid11-14", "1Gi", "", v1.ClaimPending, &classNonExisting),
  327. newClaimArray("claim11-14", "uid11-14", "1Gi", "", v1.ClaimPending, &classNonExisting),
  328. noevents, noerrors, wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim),
  329. },
  330. {
  331. // No provisioning with class=""
  332. "11-15 - no provisioning with class=''",
  333. novolumes,
  334. novolumes,
  335. newClaimArray("claim11-15", "uid11-15", "1Gi", "", v1.ClaimPending, &classEmpty),
  336. newClaimArray("claim11-15", "uid11-15", "1Gi", "", v1.ClaimPending, &classEmpty),
  337. noevents, noerrors, wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim),
  338. },
  339. {
  340. // No provisioning with class=nil
  341. "11-16 - no provisioning with class=nil",
  342. novolumes,
  343. novolumes,
  344. newClaimArray("claim11-15", "uid11-15", "1Gi", "", v1.ClaimPending, nil),
  345. newClaimArray("claim11-15", "uid11-15", "1Gi", "", v1.ClaimPending, nil),
  346. noevents, noerrors, wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim),
  347. },
  348. {
  349. // No provisioning + normal event with external provisioner
  350. "11-17 - external provisioner",
  351. novolumes,
  352. novolumes,
  353. newClaimArray("claim11-17", "uid11-17", "1Gi", "", v1.ClaimPending, &classExternal),
  354. claimWithAnnotation(pvutil.AnnStorageProvisioner, "vendor.com/my-volume",
  355. newClaimArray("claim11-17", "uid11-17", "1Gi", "", v1.ClaimPending, &classExternal)),
  356. []string{"Normal ExternalProvisioning"},
  357. noerrors, wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim),
  358. },
  359. {
  360. // No provisioning + warning event with unknown internal provisioner
  361. "11-18 - unknown internal provisioner",
  362. novolumes,
  363. novolumes,
  364. newClaimArray("claim11-18", "uid11-18", "1Gi", "", v1.ClaimPending, &classUnknownInternal),
  365. newClaimArray("claim11-18", "uid11-18", "1Gi", "", v1.ClaimPending, &classUnknownInternal),
  366. []string{"Warning ProvisioningFailed"},
  367. noerrors, wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim),
  368. },
  369. {
  370. // Provision success - first save of a PV to API server fails (API
  371. // server has written the object to etcd, but crashed before sending
  372. // 200 OK response to the controller). Controller retries and the
  373. // second save of the PV returns "AlreadyExists" because the PV
  374. // object already is in the API server.
  375. //
  376. "11-19 - provisioned volume saved but API server crashed",
  377. novolumes,
  378. // We don't actually simulate API server saving the object and
  379. // crashing afterwards, Create() just returns error without saving
  380. // the volume in this test. So the set of expected volumes at the
  381. // end of the test is empty.
  382. novolumes,
  383. newClaimArray("claim11-19", "uid11-19", "1Gi", "", v1.ClaimPending, &classGold),
  384. newClaimArray("claim11-19", "uid11-19", "1Gi", "", v1.ClaimPending, &classGold, pvutil.AnnStorageProvisioner),
  385. noevents,
  386. []pvtesting.ReactorError{
  387. // Inject errors to simulate crashed API server during
  388. // kubeclient.PersistentVolumes.Create()
  389. {Verb: "create", Resource: "persistentvolumes", Error: errors.New("Mock creation error1")},
  390. {Verb: "create", Resource: "persistentvolumes", Error: apierrs.NewAlreadyExists(api.Resource("persistentvolumes"), "")},
  391. },
  392. wrapTestWithPluginCalls(
  393. nil, // recycle calls
  394. nil, // delete calls - if Delete was called the test would fail
  395. []provisionCall{provision1Success},
  396. testSyncClaim,
  397. ),
  398. },
  399. {
  400. // No provisioning + warning event with unsupported storageClass.mountOptions
  401. "11-20 - unsupported storageClass.mountOptions",
  402. novolumes,
  403. novolumes,
  404. newClaimArray("claim11-20", "uid11-20", "1Gi", "", v1.ClaimPending, &classUnsupportedMountOptions),
  405. newClaimArray("claim11-20", "uid11-20", "1Gi", "", v1.ClaimPending, &classUnsupportedMountOptions, pvutil.AnnStorageProvisioner),
  406. // Expect event to be prefixed with "Mount options" because saving PV will fail anyway
  407. []string{"Warning ProvisioningFailed Mount options"},
  408. noerrors, wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim),
  409. },
  410. {
  411. // No provisioning due to CSI migration + normal event with external provisioner
  412. "11-21 - external provisioner for CSI migration",
  413. novolumes,
  414. novolumes,
  415. newClaimArray("claim11-21", "uid11-21", "1Gi", "", v1.ClaimPending, &classGold),
  416. claimWithAnnotation(pvutil.AnnStorageProvisioner, "vendor.com/MockCSIPlugin",
  417. newClaimArray("claim11-21", "uid11-21", "1Gi", "", v1.ClaimPending, &classGold)),
  418. []string{"Normal ExternalProvisioning"},
  419. noerrors, wrapTestWithCSIMigrationProvisionCalls(testSyncClaim),
  420. },
  421. {
  422. // volume provisioned and available
  423. // in this case, NO normal event with external provisioner should be issued
  424. "11-22 - external provisioner with volume available",
  425. newVolumeArray("volume11-22", "1Gi", "", "", v1.VolumeAvailable, v1.PersistentVolumeReclaimRetain, classExternal),
  426. newVolumeArray("volume11-22", "1Gi", "uid11-22", "claim11-22", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, classExternal, pvutil.AnnBoundByController),
  427. newClaimArray("claim11-22", "uid11-22", "1Gi", "", v1.ClaimPending, &classExternal),
  428. newClaimArray("claim11-22", "uid11-22", "1Gi", "volume11-22", v1.ClaimBound, &classExternal, pvutil.AnnBoundByController, pvutil.AnnBindCompleted),
  429. noevents,
  430. noerrors, wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim),
  431. },
  432. }
  433. runSyncTests(t, tests, storageClasses, []*v1.Pod{})
  434. }
  435. // Test multiple calls to syncClaim/syncVolume and periodic sync of all
  436. // volume/claims. The test follows this pattern:
  437. // 0. Load the controller with initial data.
  438. // 1. Call controllerTest.testCall() once as in TestSync()
  439. // 2. For all volumes/claims changed by previous syncVolume/syncClaim calls,
  440. // call appropriate syncVolume/syncClaim (simulating "volume/claim changed"
  441. // events). Go to 2. if these calls change anything.
  442. // 3. When all changes are processed and no new changes were made, call
  443. // syncVolume/syncClaim on all volumes/claims (simulating "periodic sync").
  444. // 4. If some changes were done by step 3., go to 2. (simulation of
  445. // "volume/claim updated" events, eventually performing step 3. again)
  446. // 5. When 3. does not do any changes, finish the tests and compare final set
  447. // of volumes/claims with expected claims/volumes and report differences.
  448. // Some limit of calls in enforced to prevent endless loops.
  449. func TestProvisionMultiSync(t *testing.T) {
  450. tests := []controllerTest{
  451. {
  452. // Provision a volume with binding
  453. "12-1 - successful provision",
  454. novolumes,
  455. newVolumeArray("pvc-uid12-1", "1Gi", "uid12-1", "claim12-1", v1.VolumeBound, v1.PersistentVolumeReclaimDelete, classGold, pvutil.AnnBoundByController, pvutil.AnnDynamicallyProvisioned),
  456. newClaimArray("claim12-1", "uid12-1", "1Gi", "", v1.ClaimPending, &classGold),
  457. newClaimArray("claim12-1", "uid12-1", "1Gi", "pvc-uid12-1", v1.ClaimBound, &classGold, pvutil.AnnBoundByController, pvutil.AnnBindCompleted, pvutil.AnnStorageProvisioner),
  458. noevents, noerrors, wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim),
  459. },
  460. {
  461. // provision a volume (external provisioner) and binding + normal event with external provisioner
  462. "12-2 - external provisioner with volume provisioned success",
  463. novolumes,
  464. newVolumeArray("pvc-uid12-2", "1Gi", "uid12-2", "claim12-2", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, classExternal, pvutil.AnnBoundByController),
  465. newClaimArray("claim12-2", "uid12-2", "1Gi", "", v1.ClaimPending, &classExternal),
  466. claimWithAnnotation(pvutil.AnnStorageProvisioner, "vendor.com/my-volume",
  467. newClaimArray("claim12-2", "uid12-2", "1Gi", "pvc-uid12-2", v1.ClaimBound, &classExternal, pvutil.AnnBoundByController, pvutil.AnnBindCompleted)),
  468. []string{"Normal ExternalProvisioning"},
  469. noerrors,
  470. wrapTestWithInjectedOperation(wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim), func(ctrl *PersistentVolumeController, reactor *pvtesting.VolumeReactor) {
  471. // Create a volume before syncClaim tries to bind a PV to PVC
  472. // This simulates external provisioner creating a volume while the controller
  473. // is waiting for a volume to bind to the existed claim
  474. // the external provisioner workflow implemented in "provisionClaimOperationCSI"
  475. // should issue an ExternalProvisioning event to signal that some external provisioner
  476. // is working on provisioning the PV, also add the operation start timestamp into local cache
  477. // operationTimestamps. Rely on the existences of the start time stamp to create a PV for binding
  478. if ctrl.operationTimestamps.Has("default/claim12-2") {
  479. volume := newVolume("pvc-uid12-2", "1Gi", "", "", v1.VolumeAvailable, v1.PersistentVolumeReclaimRetain, classExternal)
  480. ctrl.volumes.store.Add(volume) // add the volume to controller
  481. reactor.AddVolume(volume)
  482. }
  483. }),
  484. },
  485. {
  486. // provision a volume (external provisioner) but binding will not happen + normal event with external provisioner
  487. "12-3 - external provisioner with volume to be provisioned",
  488. novolumes,
  489. novolumes,
  490. newClaimArray("claim12-3", "uid12-3", "1Gi", "", v1.ClaimPending, &classExternal),
  491. claimWithAnnotation(pvutil.AnnStorageProvisioner, "vendor.com/my-volume",
  492. newClaimArray("claim12-3", "uid12-3", "1Gi", "", v1.ClaimPending, &classExternal)),
  493. []string{"Normal ExternalProvisioning"},
  494. noerrors,
  495. wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim),
  496. },
  497. {
  498. // provision a volume (external provisioner) and binding + normal event with external provisioner
  499. "12-4 - external provisioner with volume provisioned/bound success",
  500. novolumes,
  501. newVolumeArray("pvc-uid12-4", "1Gi", "uid12-4", "claim12-4", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, classExternal, pvutil.AnnBoundByController),
  502. newClaimArray("claim12-4", "uid12-4", "1Gi", "", v1.ClaimPending, &classExternal),
  503. claimWithAnnotation(pvutil.AnnStorageProvisioner, "vendor.com/my-volume",
  504. newClaimArray("claim12-4", "uid12-4", "1Gi", "pvc-uid12-4", v1.ClaimBound, &classExternal, pvutil.AnnBoundByController, pvutil.AnnBindCompleted)),
  505. []string{"Normal ExternalProvisioning"},
  506. noerrors,
  507. wrapTestWithInjectedOperation(wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim), func(ctrl *PersistentVolumeController, reactor *pvtesting.VolumeReactor) {
  508. // Create a volume before syncClaim tries to bind a PV to PVC
  509. // This simulates external provisioner creating a volume while the controller
  510. // is waiting for a volume to bind to the existed claim
  511. // the external provisioner workflow implemented in "provisionClaimOperationCSI"
  512. // should issue an ExternalProvisioning event to signal that some external provisioner
  513. // is working on provisioning the PV, also add the operation start timestamp into local cache
  514. // operationTimestamps. Rely on the existences of the start time stamp to create a PV for binding
  515. if ctrl.operationTimestamps.Has("default/claim12-4") {
  516. volume := newVolume("pvc-uid12-4", "1Gi", "uid12-4", "claim12-4", v1.VolumeBound, v1.PersistentVolumeReclaimRetain, classExternal, pvutil.AnnBoundByController)
  517. ctrl.volumes.store.Add(volume) // add the volume to controller
  518. reactor.AddVolume(volume)
  519. }
  520. }),
  521. },
  522. }
  523. runMultisyncTests(t, tests, storageClasses, storageClasses[0].Name)
  524. }
  525. // When provisioning is disabled, provisioning a claim should instantly return nil
  526. func TestDisablingDynamicProvisioner(t *testing.T) {
  527. ctrl, err := newTestController(nil, nil, false)
  528. if err != nil {
  529. t.Fatalf("Construct PersistentVolume controller failed: %v", err)
  530. }
  531. retVal := ctrl.provisionClaim(nil)
  532. if retVal != nil {
  533. t.Errorf("Expected nil return but got %v", retVal)
  534. }
  535. }