volume_expand.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. /*
  2. Copyright 2017 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 testsuites
  14. import (
  15. "context"
  16. "fmt"
  17. "time"
  18. "github.com/onsi/ginkgo"
  19. "github.com/onsi/gomega"
  20. v1 "k8s.io/api/core/v1"
  21. "k8s.io/apimachinery/pkg/api/resource"
  22. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  23. "k8s.io/apimachinery/pkg/util/errors"
  24. "k8s.io/apimachinery/pkg/util/wait"
  25. clientset "k8s.io/client-go/kubernetes"
  26. "k8s.io/kubernetes/test/e2e/framework"
  27. e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
  28. e2epv "k8s.io/kubernetes/test/e2e/framework/pv"
  29. e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
  30. "k8s.io/kubernetes/test/e2e/framework/volume"
  31. "k8s.io/kubernetes/test/e2e/storage/testpatterns"
  32. )
  33. const (
  34. resizePollInterval = 2 * time.Second
  35. // total time to wait for cloudprovider or file system resize to finish
  36. totalResizeWaitPeriod = 10 * time.Minute
  37. // time to wait for PVC conditions to sync
  38. pvcConditionSyncPeriod = 2 * time.Minute
  39. )
  40. type volumeExpandTestSuite struct {
  41. tsInfo TestSuiteInfo
  42. }
  43. var _ TestSuite = &volumeExpandTestSuite{}
  44. // InitVolumeExpandTestSuite returns volumeExpandTestSuite that implements TestSuite interface
  45. func InitVolumeExpandTestSuite() TestSuite {
  46. return &volumeExpandTestSuite{
  47. tsInfo: TestSuiteInfo{
  48. Name: "volume-expand",
  49. TestPatterns: []testpatterns.TestPattern{
  50. testpatterns.DefaultFsDynamicPV,
  51. testpatterns.BlockVolModeDynamicPV,
  52. testpatterns.DefaultFsDynamicPVAllowExpansion,
  53. testpatterns.BlockVolModeDynamicPVAllowExpansion,
  54. },
  55. SupportedSizeRange: volume.SizeRange{
  56. Min: "1Mi",
  57. },
  58. },
  59. }
  60. }
  61. func (v *volumeExpandTestSuite) GetTestSuiteInfo() TestSuiteInfo {
  62. return v.tsInfo
  63. }
  64. func (v *volumeExpandTestSuite) SkipRedundantSuite(driver TestDriver, pattern testpatterns.TestPattern) {
  65. }
  66. func (v *volumeExpandTestSuite) DefineTests(driver TestDriver, pattern testpatterns.TestPattern) {
  67. type local struct {
  68. config *PerTestConfig
  69. driverCleanup func()
  70. resource *VolumeResource
  71. pod *v1.Pod
  72. pod2 *v1.Pod
  73. intreeOps opCounts
  74. migratedOps opCounts
  75. }
  76. var l local
  77. ginkgo.BeforeEach(func() {
  78. // Check preconditions.
  79. if !driver.GetDriverInfo().Capabilities[CapBlock] && pattern.VolMode == v1.PersistentVolumeBlock {
  80. e2eskipper.Skipf("Driver %q does not support block volume mode - skipping", driver.GetDriverInfo().Name)
  81. }
  82. if !driver.GetDriverInfo().Capabilities[CapControllerExpansion] {
  83. e2eskipper.Skipf("Driver %q does not support volume expansion - skipping", driver.GetDriverInfo().Name)
  84. }
  85. })
  86. // This intentionally comes after checking the preconditions because it
  87. // registers its own BeforeEach which creates the namespace. Beware that it
  88. // also registers an AfterEach which renders f unusable. Any code using
  89. // f must run inside an It or Context callback.
  90. f := framework.NewDefaultFramework("volume-expand")
  91. init := func() {
  92. l = local{}
  93. // Now do the more expensive test initialization.
  94. l.config, l.driverCleanup = driver.PrepareTest(f)
  95. l.intreeOps, l.migratedOps = getMigrationVolumeOpCounts(f.ClientSet, driver.GetDriverInfo().InTreePluginName)
  96. testVolumeSizeRange := v.GetTestSuiteInfo().SupportedSizeRange
  97. l.resource = CreateVolumeResource(driver, l.config, pattern, testVolumeSizeRange)
  98. }
  99. cleanup := func() {
  100. var errs []error
  101. if l.pod != nil {
  102. ginkgo.By("Deleting pod")
  103. err := e2epod.DeletePodWithWait(f.ClientSet, l.pod)
  104. errs = append(errs, err)
  105. l.pod = nil
  106. }
  107. if l.pod2 != nil {
  108. ginkgo.By("Deleting pod2")
  109. err := e2epod.DeletePodWithWait(f.ClientSet, l.pod2)
  110. errs = append(errs, err)
  111. l.pod2 = nil
  112. }
  113. if l.resource != nil {
  114. errs = append(errs, l.resource.CleanupResource())
  115. l.resource = nil
  116. }
  117. errs = append(errs, tryFunc(l.driverCleanup))
  118. l.driverCleanup = nil
  119. framework.ExpectNoError(errors.NewAggregate(errs), "while cleaning up resource")
  120. validateMigrationVolumeOpCounts(f.ClientSet, driver.GetDriverInfo().InTreePluginName, l.intreeOps, l.migratedOps)
  121. }
  122. if !pattern.AllowExpansion {
  123. ginkgo.It("should not allow expansion of pvcs without AllowVolumeExpansion property", func() {
  124. init()
  125. defer cleanup()
  126. var err error
  127. gomega.Expect(l.resource.Sc.AllowVolumeExpansion).To(gomega.BeNil())
  128. ginkgo.By("Expanding non-expandable pvc")
  129. currentPvcSize := l.resource.Pvc.Spec.Resources.Requests[v1.ResourceStorage]
  130. newSize := currentPvcSize.DeepCopy()
  131. newSize.Add(resource.MustParse("1Gi"))
  132. framework.Logf("currentPvcSize %v, newSize %v", currentPvcSize, newSize)
  133. _, err = ExpandPVCSize(l.resource.Pvc, newSize, f.ClientSet)
  134. framework.ExpectError(err, "While updating non-expandable PVC")
  135. })
  136. } else {
  137. ginkgo.It("Verify if offline PVC expansion works", func() {
  138. init()
  139. defer cleanup()
  140. var err error
  141. ginkgo.By("Creating a pod with dynamically provisioned volume")
  142. l.pod, err = e2epod.CreateSecPodWithNodeSelection(f.ClientSet, f.Namespace.Name, []*v1.PersistentVolumeClaim{l.resource.Pvc}, nil, false, "", false, false, e2epv.SELinuxLabel, nil, l.config.ClientNodeSelection, framework.PodStartTimeout)
  143. defer func() {
  144. err = e2epod.DeletePodWithWait(f.ClientSet, l.pod)
  145. framework.ExpectNoError(err, "while cleaning up pod already deleted in resize test")
  146. }()
  147. framework.ExpectNoError(err, "While creating pods for resizing")
  148. ginkgo.By("Deleting the previously created pod")
  149. err = e2epod.DeletePodWithWait(f.ClientSet, l.pod)
  150. framework.ExpectNoError(err, "while deleting pod for resizing")
  151. // We expand the PVC while no pod is using it to ensure offline expansion
  152. ginkgo.By("Expanding current pvc")
  153. currentPvcSize := l.resource.Pvc.Spec.Resources.Requests[v1.ResourceStorage]
  154. newSize := currentPvcSize.DeepCopy()
  155. newSize.Add(resource.MustParse("1Gi"))
  156. framework.Logf("currentPvcSize %v, newSize %v", currentPvcSize, newSize)
  157. newPVC, err := ExpandPVCSize(l.resource.Pvc, newSize, f.ClientSet)
  158. framework.ExpectNoError(err, "While updating pvc for more size")
  159. l.resource.Pvc = newPVC
  160. gomega.Expect(l.resource.Pvc).NotTo(gomega.BeNil())
  161. pvcSize := l.resource.Pvc.Spec.Resources.Requests[v1.ResourceStorage]
  162. if pvcSize.Cmp(newSize) != 0 {
  163. framework.Failf("error updating pvc size %q", l.resource.Pvc.Name)
  164. }
  165. ginkgo.By("Waiting for cloudprovider resize to finish")
  166. err = WaitForControllerVolumeResize(l.resource.Pvc, f.ClientSet, totalResizeWaitPeriod)
  167. framework.ExpectNoError(err, "While waiting for pvc resize to finish")
  168. ginkgo.By("Checking for conditions on pvc")
  169. npvc, err := WaitForPendingFSResizeCondition(l.resource.Pvc, f.ClientSet)
  170. framework.ExpectNoError(err, "While waiting for pvc to have fs resizing condition")
  171. l.resource.Pvc = npvc
  172. ginkgo.By("Creating a new pod with same volume")
  173. l.pod2, err = e2epod.CreateSecPodWithNodeSelection(f.ClientSet, f.Namespace.Name, []*v1.PersistentVolumeClaim{l.resource.Pvc}, nil, false, "", false, false, e2epv.SELinuxLabel, nil, l.config.ClientNodeSelection, framework.PodStartTimeout)
  174. defer func() {
  175. err = e2epod.DeletePodWithWait(f.ClientSet, l.pod2)
  176. framework.ExpectNoError(err, "while cleaning up pod before exiting resizing test")
  177. }()
  178. framework.ExpectNoError(err, "while recreating pod for resizing")
  179. ginkgo.By("Waiting for file system resize to finish")
  180. l.resource.Pvc, err = WaitForFSResize(l.resource.Pvc, f.ClientSet)
  181. framework.ExpectNoError(err, "while waiting for fs resize to finish")
  182. pvcConditions := l.resource.Pvc.Status.Conditions
  183. framework.ExpectEqual(len(pvcConditions), 0, "pvc should not have conditions")
  184. })
  185. ginkgo.It("should resize volume when PVC is edited while pod is using it", func() {
  186. init()
  187. defer cleanup()
  188. var err error
  189. ginkgo.By("Creating a pod with dynamically provisioned volume")
  190. l.pod, err = e2epod.CreateSecPodWithNodeSelection(f.ClientSet, f.Namespace.Name, []*v1.PersistentVolumeClaim{l.resource.Pvc}, nil, false, "", false, false, e2epv.SELinuxLabel, nil, l.config.ClientNodeSelection, framework.PodStartTimeout)
  191. defer func() {
  192. err = e2epod.DeletePodWithWait(f.ClientSet, l.pod)
  193. framework.ExpectNoError(err, "while cleaning up pod already deleted in resize test")
  194. }()
  195. framework.ExpectNoError(err, "While creating pods for resizing")
  196. // We expand the PVC while no pod is using it to ensure offline expansion
  197. ginkgo.By("Expanding current pvc")
  198. currentPvcSize := l.resource.Pvc.Spec.Resources.Requests[v1.ResourceStorage]
  199. newSize := currentPvcSize.DeepCopy()
  200. newSize.Add(resource.MustParse("1Gi"))
  201. framework.Logf("currentPvcSize %v, newSize %v", currentPvcSize, newSize)
  202. newPVC, err := ExpandPVCSize(l.resource.Pvc, newSize, f.ClientSet)
  203. framework.ExpectNoError(err, "While updating pvc for more size")
  204. l.resource.Pvc = newPVC
  205. gomega.Expect(l.resource.Pvc).NotTo(gomega.BeNil())
  206. pvcSize := l.resource.Pvc.Spec.Resources.Requests[v1.ResourceStorage]
  207. if pvcSize.Cmp(newSize) != 0 {
  208. framework.Failf("error updating pvc size %q", l.resource.Pvc.Name)
  209. }
  210. ginkgo.By("Waiting for cloudprovider resize to finish")
  211. err = WaitForControllerVolumeResize(l.resource.Pvc, f.ClientSet, totalResizeWaitPeriod)
  212. framework.ExpectNoError(err, "While waiting for pvc resize to finish")
  213. ginkgo.By("Waiting for file system resize to finish")
  214. l.resource.Pvc, err = WaitForFSResize(l.resource.Pvc, f.ClientSet)
  215. framework.ExpectNoError(err, "while waiting for fs resize to finish")
  216. pvcConditions := l.resource.Pvc.Status.Conditions
  217. framework.ExpectEqual(len(pvcConditions), 0, "pvc should not have conditions")
  218. })
  219. }
  220. }
  221. // ExpandPVCSize expands PVC size
  222. func ExpandPVCSize(origPVC *v1.PersistentVolumeClaim, size resource.Quantity, c clientset.Interface) (*v1.PersistentVolumeClaim, error) {
  223. pvcName := origPVC.Name
  224. updatedPVC := origPVC.DeepCopy()
  225. // Retry the update on error, until we hit a timeout.
  226. // TODO: Determine whether "retry with timeout" is appropriate here. Maybe we should only retry on version conflict.
  227. var lastUpdateError error
  228. waitErr := wait.PollImmediate(resizePollInterval, 30*time.Second, func() (bool, error) {
  229. var err error
  230. updatedPVC, err = c.CoreV1().PersistentVolumeClaims(origPVC.Namespace).Get(context.TODO(), pvcName, metav1.GetOptions{})
  231. if err != nil {
  232. return false, fmt.Errorf("error fetching pvc %q for resizing: %v", pvcName, err)
  233. }
  234. updatedPVC.Spec.Resources.Requests[v1.ResourceStorage] = size
  235. updatedPVC, err = c.CoreV1().PersistentVolumeClaims(origPVC.Namespace).Update(context.TODO(), updatedPVC, metav1.UpdateOptions{})
  236. if err != nil {
  237. framework.Logf("Error updating pvc %s: %v", pvcName, err)
  238. lastUpdateError = err
  239. return false, nil
  240. }
  241. return true, nil
  242. })
  243. if waitErr == wait.ErrWaitTimeout {
  244. return nil, fmt.Errorf("timed out attempting to update PVC size. last update error: %v", lastUpdateError)
  245. }
  246. if waitErr != nil {
  247. return nil, fmt.Errorf("failed to expand PVC size (check logs for error): %v", waitErr)
  248. }
  249. return updatedPVC, nil
  250. }
  251. // WaitForResizingCondition waits for the pvc condition to be PersistentVolumeClaimResizing
  252. func WaitForResizingCondition(pvc *v1.PersistentVolumeClaim, c clientset.Interface, duration time.Duration) error {
  253. waitErr := wait.PollImmediate(resizePollInterval, duration, func() (bool, error) {
  254. var err error
  255. updatedPVC, err := c.CoreV1().PersistentVolumeClaims(pvc.Namespace).Get(context.TODO(), pvc.Name, metav1.GetOptions{})
  256. if err != nil {
  257. return false, fmt.Errorf("error fetching pvc %q for checking for resize status: %v", pvc.Name, err)
  258. }
  259. pvcConditions := updatedPVC.Status.Conditions
  260. if len(pvcConditions) > 0 {
  261. if pvcConditions[0].Type == v1.PersistentVolumeClaimResizing {
  262. return true, nil
  263. }
  264. }
  265. return false, nil
  266. })
  267. if waitErr != nil {
  268. return fmt.Errorf("error waiting for pvc %q to have resize status: %v", pvc.Name, waitErr)
  269. }
  270. return nil
  271. }
  272. // WaitForControllerVolumeResize waits for the controller resize to be finished
  273. func WaitForControllerVolumeResize(pvc *v1.PersistentVolumeClaim, c clientset.Interface, duration time.Duration) error {
  274. pvName := pvc.Spec.VolumeName
  275. waitErr := wait.PollImmediate(resizePollInterval, duration, func() (bool, error) {
  276. pvcSize := pvc.Spec.Resources.Requests[v1.ResourceStorage]
  277. pv, err := c.CoreV1().PersistentVolumes().Get(context.TODO(), pvName, metav1.GetOptions{})
  278. if err != nil {
  279. return false, fmt.Errorf("error fetching pv %q for resizing %v", pvName, err)
  280. }
  281. pvSize := pv.Spec.Capacity[v1.ResourceStorage]
  282. // If pv size is greater or equal to requested size that means controller resize is finished.
  283. if pvSize.Cmp(pvcSize) >= 0 {
  284. return true, nil
  285. }
  286. return false, nil
  287. })
  288. if waitErr != nil {
  289. return fmt.Errorf("error while waiting for controller resize to finish: %v", waitErr)
  290. }
  291. return nil
  292. }
  293. // WaitForPendingFSResizeCondition waits for pvc to have resize condition
  294. func WaitForPendingFSResizeCondition(pvc *v1.PersistentVolumeClaim, c clientset.Interface) (*v1.PersistentVolumeClaim, error) {
  295. var updatedPVC *v1.PersistentVolumeClaim
  296. waitErr := wait.PollImmediate(resizePollInterval, pvcConditionSyncPeriod, func() (bool, error) {
  297. var err error
  298. updatedPVC, err = c.CoreV1().PersistentVolumeClaims(pvc.Namespace).Get(context.TODO(), pvc.Name, metav1.GetOptions{})
  299. if err != nil {
  300. return false, fmt.Errorf("error fetching pvc %q for checking for resize status : %v", pvc.Name, err)
  301. }
  302. inProgressConditions := updatedPVC.Status.Conditions
  303. // if there are no PVC conditions that means no node expansion is necessary
  304. if len(inProgressConditions) == 0 {
  305. return true, nil
  306. }
  307. conditionType := inProgressConditions[0].Type
  308. if conditionType == v1.PersistentVolumeClaimFileSystemResizePending {
  309. return true, nil
  310. }
  311. return false, nil
  312. })
  313. if waitErr != nil {
  314. return nil, fmt.Errorf("error waiting for pvc %q to have filesystem resize status: %v", pvc.Name, waitErr)
  315. }
  316. return updatedPVC, nil
  317. }
  318. // WaitForFSResize waits for the filesystem in the pv to be resized
  319. func WaitForFSResize(pvc *v1.PersistentVolumeClaim, c clientset.Interface) (*v1.PersistentVolumeClaim, error) {
  320. var updatedPVC *v1.PersistentVolumeClaim
  321. waitErr := wait.PollImmediate(resizePollInterval, totalResizeWaitPeriod, func() (bool, error) {
  322. var err error
  323. updatedPVC, err = c.CoreV1().PersistentVolumeClaims(pvc.Namespace).Get(context.TODO(), pvc.Name, metav1.GetOptions{})
  324. if err != nil {
  325. return false, fmt.Errorf("error fetching pvc %q for checking for resize status : %v", pvc.Name, err)
  326. }
  327. pvcSize := updatedPVC.Spec.Resources.Requests[v1.ResourceStorage]
  328. pvcStatusSize := updatedPVC.Status.Capacity[v1.ResourceStorage]
  329. //If pvc's status field size is greater than or equal to pvc's size then done
  330. if pvcStatusSize.Cmp(pvcSize) >= 0 {
  331. return true, nil
  332. }
  333. return false, nil
  334. })
  335. if waitErr != nil {
  336. return nil, fmt.Errorf("error waiting for pvc %q filesystem resize to finish: %v", pvc.Name, waitErr)
  337. }
  338. return updatedPVC, nil
  339. }