provision_test.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  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. "k8s.io/kubernetes/pkg/api"
  18. "k8s.io/kubernetes/pkg/api/unversioned"
  19. "k8s.io/kubernetes/pkg/apis/extensions"
  20. )
  21. var class1Parameters = map[string]string{
  22. "param1": "value1",
  23. }
  24. var class2Parameters = map[string]string{
  25. "param2": "value2",
  26. }
  27. var storageClasses = []*extensions.StorageClass{
  28. {
  29. TypeMeta: unversioned.TypeMeta{
  30. Kind: "StorageClass",
  31. },
  32. ObjectMeta: api.ObjectMeta{
  33. Name: "gold",
  34. },
  35. Provisioner: mockPluginName,
  36. Parameters: class1Parameters,
  37. },
  38. {
  39. TypeMeta: unversioned.TypeMeta{
  40. Kind: "StorageClass",
  41. },
  42. ObjectMeta: api.ObjectMeta{
  43. Name: "silver",
  44. },
  45. Provisioner: mockPluginName,
  46. Parameters: class2Parameters,
  47. },
  48. }
  49. // call to storageClass 1, returning an error
  50. var provision1Error = provisionCall{
  51. ret: errors.New("Moc provisioner error"),
  52. expectedParameters: class1Parameters,
  53. }
  54. // call to storageClass 1, returning a valid PV
  55. var provision1Success = provisionCall{
  56. ret: nil,
  57. expectedParameters: class1Parameters,
  58. }
  59. // call to storageClass 2, returning a valid PV
  60. var provision2Success = provisionCall{
  61. ret: nil,
  62. expectedParameters: class2Parameters,
  63. }
  64. var provisionAlphaSuccess = provisionCall{
  65. ret: nil,
  66. }
  67. // Test single call to syncVolume, expecting provisioning to happen.
  68. // 1. Fill in the controller with initial data
  69. // 2. Call the syncVolume *once*.
  70. // 3. Compare resulting volumes with expected volumes.
  71. func TestProvisionSync(t *testing.T) {
  72. tests := []controllerTest{
  73. {
  74. // Provision a volume (with a default class)
  75. "11-1 - successful provision with storage class 1",
  76. novolumes,
  77. newVolumeArray("pvc-uid11-1", "1Gi", "uid11-1", "claim11-1", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned, annClass),
  78. newClaimArray("claim11-1", "uid11-1", "1Gi", "", api.ClaimPending, annClass),
  79. // Binding will be completed in the next syncClaim
  80. newClaimArray("claim11-1", "uid11-1", "1Gi", "", api.ClaimPending, annClass),
  81. noevents, noerrors, wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim),
  82. },
  83. {
  84. // Provision failure - plugin not found
  85. "11-2 - plugin not found",
  86. novolumes,
  87. novolumes,
  88. newClaimArray("claim11-2", "uid11-2", "1Gi", "", api.ClaimPending, annClass),
  89. newClaimArray("claim11-2", "uid11-2", "1Gi", "", api.ClaimPending, annClass),
  90. []string{"Warning ProvisioningFailed"}, noerrors,
  91. testSyncClaim,
  92. },
  93. {
  94. // Provision failure - newProvisioner returns error
  95. "11-3 - newProvisioner failure",
  96. novolumes,
  97. novolumes,
  98. newClaimArray("claim11-3", "uid11-3", "1Gi", "", api.ClaimPending, annClass),
  99. newClaimArray("claim11-3", "uid11-3", "1Gi", "", api.ClaimPending, annClass),
  100. []string{"Warning ProvisioningFailed"}, noerrors,
  101. wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim),
  102. },
  103. {
  104. // Provision failure - Provision returns error
  105. "11-4 - provision failure",
  106. novolumes,
  107. novolumes,
  108. newClaimArray("claim11-4", "uid11-4", "1Gi", "", api.ClaimPending, annClass),
  109. newClaimArray("claim11-4", "uid11-4", "1Gi", "", api.ClaimPending, annClass),
  110. []string{"Warning ProvisioningFailed"}, noerrors,
  111. wrapTestWithProvisionCalls([]provisionCall{provision1Error}, testSyncClaim),
  112. },
  113. {
  114. // No provisioning if there is a matching volume available
  115. "11-6 - provisioning when there is a volume available",
  116. newVolumeArray("volume11-6", "1Gi", "", "", api.VolumePending, api.PersistentVolumeReclaimRetain, annClass),
  117. newVolumeArray("volume11-6", "1Gi", "uid11-6", "claim11-6", api.VolumeBound, api.PersistentVolumeReclaimRetain, annBoundByController, annClass),
  118. newClaimArray("claim11-6", "uid11-6", "1Gi", "", api.ClaimPending, annClass),
  119. newClaimArray("claim11-6", "uid11-6", "1Gi", "volume11-6", api.ClaimBound, annClass, annBoundByController, annBindCompleted),
  120. noevents, noerrors,
  121. // No provisioning plugin confingure - makes the test fail when
  122. // the controller errorneously tries to provision something
  123. wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim),
  124. },
  125. {
  126. // Provision success? - claim is bound before provisioner creates
  127. // a volume.
  128. "11-7 - claim is bound before provisioning",
  129. novolumes,
  130. newVolumeArray("pvc-uid11-7", "1Gi", "uid11-7", "claim11-7", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned, annClass),
  131. newClaimArray("claim11-7", "uid11-7", "1Gi", "", api.ClaimPending, annClass),
  132. // The claim would be bound in next syncClaim
  133. newClaimArray("claim11-7", "uid11-7", "1Gi", "", api.ClaimPending, annClass),
  134. noevents, noerrors,
  135. wrapTestWithInjectedOperation(wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim), func(ctrl *PersistentVolumeController, reactor *volumeReactor) {
  136. // Create a volume before provisionClaimOperation starts.
  137. // This similates a parallel controller provisioning the volume.
  138. reactor.lock.Lock()
  139. volume := newVolume("pvc-uid11-7", "1Gi", "uid11-7", "claim11-7", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned, annClass)
  140. reactor.volumes[volume.Name] = volume
  141. reactor.lock.Unlock()
  142. }),
  143. },
  144. {
  145. // Provision success - cannot save provisioned PV once,
  146. // second retry succeeds
  147. "11-8 - cannot save provisioned volume",
  148. novolumes,
  149. newVolumeArray("pvc-uid11-8", "1Gi", "uid11-8", "claim11-8", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned, annClass),
  150. newClaimArray("claim11-8", "uid11-8", "1Gi", "", api.ClaimPending, annClass),
  151. // Binding will be completed in the next syncClaim
  152. newClaimArray("claim11-8", "uid11-8", "1Gi", "", api.ClaimPending, annClass),
  153. noevents,
  154. []reactorError{
  155. // Inject error to the first
  156. // kubeclient.PersistentVolumes.Create() call. All other calls
  157. // will succeed.
  158. {"create", "persistentvolumes", errors.New("Mock creation error")},
  159. },
  160. wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim),
  161. },
  162. {
  163. // Provision success? - cannot save provisioned PV five times,
  164. // volume is deleted and delete succeeds
  165. "11-9 - cannot save provisioned volume, delete succeeds",
  166. novolumes,
  167. novolumes,
  168. newClaimArray("claim11-9", "uid11-9", "1Gi", "", api.ClaimPending, annClass),
  169. newClaimArray("claim11-9", "uid11-9", "1Gi", "", api.ClaimPending, annClass),
  170. []string{"Warning ProvisioningFailed"},
  171. []reactorError{
  172. // Inject error to five kubeclient.PersistentVolumes.Create()
  173. // calls
  174. {"create", "persistentvolumes", errors.New("Mock creation error1")},
  175. {"create", "persistentvolumes", errors.New("Mock creation error2")},
  176. {"create", "persistentvolumes", errors.New("Mock creation error3")},
  177. {"create", "persistentvolumes", errors.New("Mock creation error4")},
  178. {"create", "persistentvolumes", errors.New("Mock creation error5")},
  179. },
  180. wrapTestWithPluginCalls(
  181. nil, // recycle calls
  182. []error{nil}, // delete calls
  183. []provisionCall{provision1Success}, // provision calls
  184. testSyncClaim,
  185. ),
  186. },
  187. {
  188. // Provision failure - cannot save provisioned PV five times,
  189. // volume delete failed - no plugin found
  190. "11-10 - cannot save provisioned volume, no delete plugin found",
  191. novolumes,
  192. novolumes,
  193. newClaimArray("claim11-10", "uid11-10", "1Gi", "", api.ClaimPending, annClass),
  194. newClaimArray("claim11-10", "uid11-10", "1Gi", "", api.ClaimPending, annClass),
  195. []string{"Warning ProvisioningFailed", "Warning ProvisioningCleanupFailed"},
  196. []reactorError{
  197. // Inject error to five kubeclient.PersistentVolumes.Create()
  198. // calls
  199. {"create", "persistentvolumes", errors.New("Mock creation error1")},
  200. {"create", "persistentvolumes", errors.New("Mock creation error2")},
  201. {"create", "persistentvolumes", errors.New("Mock creation error3")},
  202. {"create", "persistentvolumes", errors.New("Mock creation error4")},
  203. {"create", "persistentvolumes", errors.New("Mock creation error5")},
  204. },
  205. // No deleteCalls are configured, which results into no deleter plugin available for the volume
  206. wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim),
  207. },
  208. {
  209. // Provision failure - cannot save provisioned PV five times,
  210. // volume delete failed - deleter returns error five times
  211. "11-11 - cannot save provisioned volume, deleter fails",
  212. novolumes,
  213. novolumes,
  214. newClaimArray("claim11-11", "uid11-11", "1Gi", "", api.ClaimPending, annClass),
  215. newClaimArray("claim11-11", "uid11-11", "1Gi", "", api.ClaimPending, annClass),
  216. []string{"Warning ProvisioningFailed", "Warning ProvisioningCleanupFailed"},
  217. []reactorError{
  218. // Inject error to five kubeclient.PersistentVolumes.Create()
  219. // calls
  220. {"create", "persistentvolumes", errors.New("Mock creation error1")},
  221. {"create", "persistentvolumes", errors.New("Mock creation error2")},
  222. {"create", "persistentvolumes", errors.New("Mock creation error3")},
  223. {"create", "persistentvolumes", errors.New("Mock creation error4")},
  224. {"create", "persistentvolumes", errors.New("Mock creation error5")},
  225. },
  226. wrapTestWithPluginCalls(
  227. nil, // recycle calls
  228. []error{ // delete calls
  229. errors.New("Mock deletion error1"),
  230. errors.New("Mock deletion error2"),
  231. errors.New("Mock deletion error3"),
  232. errors.New("Mock deletion error4"),
  233. errors.New("Mock deletion error5"),
  234. },
  235. []provisionCall{provision1Success}, // provision calls
  236. testSyncClaim),
  237. },
  238. {
  239. // Provision failure - cannot save provisioned PV five times,
  240. // volume delete succeeds 2nd time
  241. "11-12 - cannot save provisioned volume, delete succeeds 2nd time",
  242. novolumes,
  243. novolumes,
  244. newClaimArray("claim11-12", "uid11-12", "1Gi", "", api.ClaimPending, annClass),
  245. newClaimArray("claim11-12", "uid11-12", "1Gi", "", api.ClaimPending, annClass),
  246. []string{"Warning ProvisioningFailed"},
  247. []reactorError{
  248. // Inject error to five kubeclient.PersistentVolumes.Create()
  249. // calls
  250. {"create", "persistentvolumes", errors.New("Mock creation error1")},
  251. {"create", "persistentvolumes", errors.New("Mock creation error2")},
  252. {"create", "persistentvolumes", errors.New("Mock creation error3")},
  253. {"create", "persistentvolumes", errors.New("Mock creation error4")},
  254. {"create", "persistentvolumes", errors.New("Mock creation error5")},
  255. },
  256. wrapTestWithPluginCalls(
  257. nil, // recycle calls
  258. []error{ // delete calls
  259. errors.New("Mock deletion error1"),
  260. nil,
  261. }, // provison calls
  262. []provisionCall{provision1Success},
  263. testSyncClaim,
  264. ),
  265. },
  266. {
  267. // Provision a volume (with non-default class)
  268. "11-13 - successful provision with storage class 2",
  269. novolumes,
  270. volumeWithClass("silver", newVolumeArray("pvc-uid11-13", "1Gi", "uid11-13", "claim11-13", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned)),
  271. claimWithClass("silver", newClaimArray("claim11-13", "uid11-13", "1Gi", "", api.ClaimPending)),
  272. // Binding will be completed in the next syncClaim
  273. claimWithClass("silver", newClaimArray("claim11-13", "uid11-13", "1Gi", "", api.ClaimPending)),
  274. noevents, noerrors, wrapTestWithProvisionCalls([]provisionCall{provision2Success}, testSyncClaim),
  275. },
  276. {
  277. // Provision error - non existing class
  278. "11-14 - fail due to non-existing class",
  279. novolumes,
  280. novolumes,
  281. claimWithClass("non-existing", newClaimArray("claim11-14", "uid11-14", "1Gi", "", api.ClaimPending)),
  282. claimWithClass("non-existing", newClaimArray("claim11-14", "uid11-14", "1Gi", "", api.ClaimPending)),
  283. noevents, noerrors, wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim),
  284. },
  285. {
  286. // No provisioning with class=""
  287. "11-15 - no provisioning with class=''",
  288. novolumes,
  289. novolumes,
  290. claimWithClass("", newClaimArray("claim11-15", "uid11-15", "1Gi", "", api.ClaimPending)),
  291. claimWithClass("", newClaimArray("claim11-15", "uid11-15", "1Gi", "", api.ClaimPending)),
  292. noevents, noerrors, wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim),
  293. },
  294. {
  295. // No provisioning with class=nil
  296. "11-16 - no provisioning with class=nil",
  297. novolumes,
  298. novolumes,
  299. newClaimArray("claim11-15", "uid11-15", "1Gi", "", api.ClaimPending),
  300. newClaimArray("claim11-15", "uid11-15", "1Gi", "", api.ClaimPending),
  301. noevents, noerrors, wrapTestWithProvisionCalls([]provisionCall{}, testSyncClaim),
  302. },
  303. }
  304. runSyncTests(t, tests, storageClasses)
  305. }
  306. func TestAlphaProvisionSync(t *testing.T) {
  307. tests := []controllerTest{
  308. {
  309. // Provision a volume with alpha annotation
  310. "14-1 - successful alpha provisioning",
  311. novolumes,
  312. newVolumeArray("pvc-uid14-1", "1Gi", "uid14-1", "claim14-1", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned),
  313. newClaimArray("claim14-1", "uid14-1", "1Gi", "", api.ClaimPending, annAlphaClass),
  314. // Binding will be completed in the next syncClaim
  315. newClaimArray("claim14-1", "uid14-1", "1Gi", "", api.ClaimPending, annAlphaClass),
  316. noevents, noerrors, wrapTestWithProvisionCalls([]provisionCall{provisionAlphaSuccess}, testSyncClaim),
  317. },
  318. {
  319. // Provision success - there is already a volume available, still
  320. // we provision a new one when requested.
  321. "14-2 - no alpha provisioning when there is a volume available",
  322. newVolumeArray("volume14-2", "1Gi", "", "", api.VolumePending, api.PersistentVolumeReclaimRetain),
  323. []*api.PersistentVolume{
  324. newVolume("volume14-2", "1Gi", "", "", api.VolumePending, api.PersistentVolumeReclaimRetain),
  325. newVolume("pvc-uid14-2", "1Gi", "uid14-2", "claim14-2", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned),
  326. },
  327. newClaimArray("claim14-2", "uid14-2", "1Gi", "", api.ClaimPending, annAlphaClass),
  328. // Binding will be completed in the next syncClaim
  329. newClaimArray("claim14-2", "uid14-2", "1Gi", "", api.ClaimPending, annAlphaClass),
  330. noevents, noerrors, wrapTestWithProvisionCalls([]provisionCall{provisionAlphaSuccess}, testSyncClaim),
  331. },
  332. }
  333. runSyncTests(t, tests, []*extensions.StorageClass{})
  334. }
  335. // Test multiple calls to syncClaim/syncVolume and periodic sync of all
  336. // volume/claims. The test follows this pattern:
  337. // 0. Load the controller with initial data.
  338. // 1. Call controllerTest.testCall() once as in TestSync()
  339. // 2. For all volumes/claims changed by previous syncVolume/syncClaim calls,
  340. // call appropriate syncVolume/syncClaim (simulating "volume/claim changed"
  341. // events). Go to 2. if these calls change anything.
  342. // 3. When all changes are processed and no new changes were made, call
  343. // syncVolume/syncClaim on all volumes/claims (simulating "periodic sync").
  344. // 4. If some changes were done by step 3., go to 2. (simulation of
  345. // "volume/claim updated" events, eventually performing step 3. again)
  346. // 5. When 3. does not do any changes, finish the tests and compare final set
  347. // of volumes/claims with expected claims/volumes and report differences.
  348. // Some limit of calls in enforced to prevent endless loops.
  349. func TestProvisionMultiSync(t *testing.T) {
  350. tests := []controllerTest{
  351. {
  352. // Provision a volume with binding
  353. "12-1 - successful provision",
  354. novolumes,
  355. newVolumeArray("pvc-uid12-1", "1Gi", "uid12-1", "claim12-1", api.VolumeBound, api.PersistentVolumeReclaimDelete, annBoundByController, annDynamicallyProvisioned, annClass),
  356. newClaimArray("claim12-1", "uid12-1", "1Gi", "", api.ClaimPending, annClass),
  357. // Binding will be completed in the next syncClaim
  358. newClaimArray("claim12-1", "uid12-1", "1Gi", "pvc-uid12-1", api.ClaimBound, annClass, annBoundByController, annBindCompleted),
  359. noevents, noerrors, wrapTestWithProvisionCalls([]provisionCall{provision1Success}, testSyncClaim),
  360. },
  361. }
  362. runMultisyncTests(t, tests, storageClasses, storageClasses[0].Name)
  363. }
  364. // When provisioning is disabled, provisioning a claim should instantly return nil
  365. func TestDisablingDynamicProvisioner(t *testing.T) {
  366. ctrl := newTestController(nil, nil, nil, nil, false)
  367. retVal := ctrl.provisionClaim(nil)
  368. if retVal != nil {
  369. t.Errorf("Expected nil return but got %v", retVal)
  370. }
  371. }