csi_mock_volume.go 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831
  1. /*
  2. Copyright 2019 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 storage
  14. import (
  15. "context"
  16. "crypto/sha256"
  17. "encoding/json"
  18. "fmt"
  19. "regexp"
  20. "strconv"
  21. "strings"
  22. "time"
  23. v1 "k8s.io/api/core/v1"
  24. storagev1 "k8s.io/api/storage/v1"
  25. apierrors "k8s.io/apimachinery/pkg/api/errors"
  26. "k8s.io/apimachinery/pkg/api/resource"
  27. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  28. utilerrors "k8s.io/apimachinery/pkg/util/errors"
  29. "k8s.io/apimachinery/pkg/util/sets"
  30. "k8s.io/apimachinery/pkg/util/wait"
  31. clientset "k8s.io/client-go/kubernetes"
  32. "k8s.io/kubernetes/test/e2e/framework"
  33. e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
  34. e2epv "k8s.io/kubernetes/test/e2e/framework/pv"
  35. "k8s.io/kubernetes/test/e2e/storage/drivers"
  36. "k8s.io/kubernetes/test/e2e/storage/testsuites"
  37. "k8s.io/kubernetes/test/e2e/storage/utils"
  38. imageutils "k8s.io/kubernetes/test/utils/image"
  39. "github.com/onsi/ginkgo"
  40. "github.com/onsi/gomega"
  41. )
  42. type cleanupFuncs func()
  43. const (
  44. csiNodeLimitUpdateTimeout = 5 * time.Minute
  45. csiPodUnschedulableTimeout = 5 * time.Minute
  46. csiResizeWaitPeriod = 5 * time.Minute
  47. // how long to wait for Resizing Condition on PVC to appear
  48. csiResizingConditionWait = 2 * time.Minute
  49. )
  50. var _ = utils.SIGDescribe("CSI mock volume", func() {
  51. type testParameters struct {
  52. disableAttach bool
  53. attachLimit int
  54. registerDriver bool
  55. podInfo *bool
  56. scName string
  57. enableResizing bool // enable resizing for both CSI mock driver and storageClass.
  58. enableNodeExpansion bool // enable node expansion for CSI mock driver
  59. // just disable resizing on driver it overrides enableResizing flag for CSI mock driver
  60. disableResizingOnDriver bool
  61. }
  62. type mockDriverSetup struct {
  63. cs clientset.Interface
  64. config *testsuites.PerTestConfig
  65. testCleanups []cleanupFuncs
  66. pods []*v1.Pod
  67. pvcs []*v1.PersistentVolumeClaim
  68. sc map[string]*storagev1.StorageClass
  69. driver testsuites.TestDriver
  70. provisioner string
  71. tp testParameters
  72. }
  73. var m mockDriverSetup
  74. f := framework.NewDefaultFramework("csi-mock-volumes")
  75. init := func(tp testParameters) {
  76. m = mockDriverSetup{
  77. cs: f.ClientSet,
  78. sc: make(map[string]*storagev1.StorageClass),
  79. tp: tp,
  80. }
  81. cs := f.ClientSet
  82. var err error
  83. driverOpts := drivers.CSIMockDriverOpts{
  84. RegisterDriver: tp.registerDriver,
  85. PodInfo: tp.podInfo,
  86. AttachLimit: tp.attachLimit,
  87. DisableAttach: tp.disableAttach,
  88. EnableResizing: tp.enableResizing,
  89. EnableNodeExpansion: tp.enableNodeExpansion,
  90. }
  91. // this just disable resizing on driver, keeping resizing on SC enabled.
  92. if tp.disableResizingOnDriver {
  93. driverOpts.EnableResizing = false
  94. }
  95. m.driver = drivers.InitMockCSIDriver(driverOpts)
  96. config, testCleanup := m.driver.PrepareTest(f)
  97. m.testCleanups = append(m.testCleanups, testCleanup)
  98. m.config = config
  99. m.provisioner = config.GetUniqueDriverName()
  100. if tp.registerDriver {
  101. err = waitForCSIDriver(cs, m.config.GetUniqueDriverName())
  102. framework.ExpectNoError(err, "Failed to get CSIDriver : %v", err)
  103. m.testCleanups = append(m.testCleanups, func() {
  104. destroyCSIDriver(cs, m.config.GetUniqueDriverName())
  105. })
  106. }
  107. }
  108. createPod := func(ephemeral bool) (class *storagev1.StorageClass, claim *v1.PersistentVolumeClaim, pod *v1.Pod) {
  109. ginkgo.By("Creating pod")
  110. var sc *storagev1.StorageClass
  111. if dDriver, ok := m.driver.(testsuites.DynamicPVTestDriver); ok {
  112. sc = dDriver.GetDynamicProvisionStorageClass(m.config, "")
  113. }
  114. scTest := testsuites.StorageClassTest{
  115. Name: m.driver.GetDriverInfo().Name,
  116. Provisioner: sc.Provisioner,
  117. Parameters: sc.Parameters,
  118. ClaimSize: "1Gi",
  119. ExpectedSize: "1Gi",
  120. }
  121. if m.tp.scName != "" {
  122. scTest.StorageClassName = m.tp.scName
  123. }
  124. if m.tp.enableResizing {
  125. scTest.AllowVolumeExpansion = true
  126. }
  127. // The mock driver only works when everything runs on a single node.
  128. nodeSelection := m.config.ClientNodeSelection
  129. if ephemeral {
  130. pod = startPausePodInline(f.ClientSet, scTest, nodeSelection, f.Namespace.Name)
  131. if pod != nil {
  132. m.pods = append(m.pods, pod)
  133. }
  134. } else {
  135. class, claim, pod = startPausePod(f.ClientSet, scTest, nodeSelection, f.Namespace.Name)
  136. if class != nil {
  137. m.sc[class.Name] = class
  138. }
  139. if claim != nil {
  140. m.pvcs = append(m.pvcs, claim)
  141. }
  142. if pod != nil {
  143. m.pods = append(m.pods, pod)
  144. }
  145. }
  146. return // result variables set above
  147. }
  148. createPodWithPVC := func(pvc *v1.PersistentVolumeClaim) (*v1.Pod, error) {
  149. nodeSelection := m.config.ClientNodeSelection
  150. pod, err := startPausePodWithClaim(m.cs, pvc, nodeSelection, f.Namespace.Name)
  151. if pod != nil {
  152. m.pods = append(m.pods, pod)
  153. }
  154. return pod, err
  155. }
  156. cleanup := func() {
  157. cs := f.ClientSet
  158. var errs []error
  159. for _, pod := range m.pods {
  160. ginkgo.By(fmt.Sprintf("Deleting pod %s", pod.Name))
  161. errs = append(errs, e2epod.DeletePodWithWait(cs, pod))
  162. }
  163. for _, claim := range m.pvcs {
  164. ginkgo.By(fmt.Sprintf("Deleting claim %s", claim.Name))
  165. claim, err := cs.CoreV1().PersistentVolumeClaims(claim.Namespace).Get(context.TODO(), claim.Name, metav1.GetOptions{})
  166. if err == nil {
  167. cs.CoreV1().PersistentVolumeClaims(claim.Namespace).Delete(context.TODO(), claim.Name, nil)
  168. framework.WaitForPersistentVolumeDeleted(cs, claim.Spec.VolumeName, framework.Poll, 2*time.Minute)
  169. }
  170. }
  171. for _, sc := range m.sc {
  172. ginkgo.By(fmt.Sprintf("Deleting storageclass %s", sc.Name))
  173. cs.StorageV1().StorageClasses().Delete(context.TODO(), sc.Name, nil)
  174. }
  175. ginkgo.By("Cleaning up resources")
  176. for _, cleanupFunc := range m.testCleanups {
  177. cleanupFunc()
  178. }
  179. err := utilerrors.NewAggregate(errs)
  180. framework.ExpectNoError(err, "while cleaning up after test")
  181. }
  182. // The CSIDriverRegistry feature gate is needed for this test in Kubernetes 1.12.
  183. ginkgo.Context("CSI attach test using mock driver", func() {
  184. tests := []struct {
  185. name string
  186. disableAttach bool
  187. deployClusterRegistrar bool
  188. }{
  189. {
  190. name: "should not require VolumeAttach for drivers without attachment",
  191. disableAttach: true,
  192. deployClusterRegistrar: true,
  193. },
  194. {
  195. name: "should require VolumeAttach for drivers with attachment",
  196. deployClusterRegistrar: true,
  197. },
  198. {
  199. name: "should preserve attachment policy when no CSIDriver present",
  200. deployClusterRegistrar: false,
  201. },
  202. }
  203. for _, t := range tests {
  204. test := t
  205. ginkgo.It(t.name, func() {
  206. var err error
  207. init(testParameters{registerDriver: test.deployClusterRegistrar, disableAttach: test.disableAttach})
  208. defer cleanup()
  209. _, claim, pod := createPod(false)
  210. if pod == nil {
  211. return
  212. }
  213. err = e2epod.WaitForPodNameRunningInNamespace(m.cs, pod.Name, pod.Namespace)
  214. framework.ExpectNoError(err, "Failed to start pod: %v", err)
  215. ginkgo.By("Checking if VolumeAttachment was created for the pod")
  216. handle := getVolumeHandle(m.cs, claim)
  217. attachmentHash := sha256.Sum256([]byte(fmt.Sprintf("%s%s%s", handle, m.provisioner, m.config.ClientNodeSelection.Name)))
  218. attachmentName := fmt.Sprintf("csi-%x", attachmentHash)
  219. _, err = m.cs.StorageV1().VolumeAttachments().Get(context.TODO(), attachmentName, metav1.GetOptions{})
  220. if err != nil {
  221. if apierrors.IsNotFound(err) {
  222. if !test.disableAttach {
  223. framework.ExpectNoError(err, "Expected VolumeAttachment but none was found")
  224. }
  225. } else {
  226. framework.ExpectNoError(err, "Failed to find VolumeAttachment")
  227. }
  228. }
  229. if test.disableAttach {
  230. framework.ExpectError(err, "Unexpected VolumeAttachment found")
  231. }
  232. })
  233. }
  234. })
  235. ginkgo.Context("CSI workload information using mock driver", func() {
  236. var (
  237. err error
  238. podInfoTrue = true
  239. podInfoFalse = false
  240. )
  241. tests := []struct {
  242. name string
  243. podInfoOnMount *bool
  244. deployClusterRegistrar bool
  245. expectPodInfo bool
  246. expectEphemeral bool
  247. }{
  248. {
  249. name: "should not be passed when podInfoOnMount=nil",
  250. podInfoOnMount: nil,
  251. deployClusterRegistrar: true,
  252. expectPodInfo: false,
  253. expectEphemeral: false,
  254. },
  255. {
  256. name: "should be passed when podInfoOnMount=true",
  257. podInfoOnMount: &podInfoTrue,
  258. deployClusterRegistrar: true,
  259. expectPodInfo: true,
  260. expectEphemeral: false,
  261. },
  262. {
  263. name: "contain ephemeral=true when using inline volume",
  264. podInfoOnMount: &podInfoTrue,
  265. deployClusterRegistrar: true,
  266. expectPodInfo: true,
  267. expectEphemeral: true,
  268. },
  269. {
  270. name: "should not be passed when podInfoOnMount=false",
  271. podInfoOnMount: &podInfoFalse,
  272. deployClusterRegistrar: true,
  273. expectPodInfo: false,
  274. expectEphemeral: false,
  275. },
  276. {
  277. name: "should not be passed when CSIDriver does not exist",
  278. deployClusterRegistrar: false,
  279. expectPodInfo: false,
  280. expectEphemeral: false,
  281. },
  282. }
  283. for _, t := range tests {
  284. test := t
  285. ginkgo.It(t.name, func() {
  286. init(testParameters{
  287. registerDriver: test.deployClusterRegistrar,
  288. scName: "csi-mock-sc-" + f.UniqueName,
  289. podInfo: test.podInfoOnMount})
  290. defer cleanup()
  291. _, _, pod := createPod(test.expectEphemeral)
  292. if pod == nil {
  293. return
  294. }
  295. err = e2epod.WaitForPodNameRunningInNamespace(m.cs, pod.Name, pod.Namespace)
  296. framework.ExpectNoError(err, "Failed to start pod: %v", err)
  297. // If we expect an ephemeral volume, the feature has to be enabled.
  298. // Otherwise need to check if we expect pod info, because the content
  299. // of that depends on whether the feature is enabled or not.
  300. csiInlineVolumesEnabled := test.expectEphemeral
  301. if test.expectPodInfo {
  302. ginkgo.By("checking for CSIInlineVolumes feature")
  303. csiInlineVolumesEnabled, err = testsuites.CSIInlineVolumesEnabled(m.cs, f.Namespace.Name)
  304. framework.ExpectNoError(err, "failed to test for CSIInlineVolumes")
  305. }
  306. ginkgo.By("Deleting the previously created pod")
  307. err = e2epod.DeletePodWithWait(m.cs, pod)
  308. framework.ExpectNoError(err, "while deleting")
  309. ginkgo.By("Checking CSI driver logs")
  310. // The driver is deployed as a statefulset with stable pod names
  311. driverPodName := "csi-mockplugin-0"
  312. err = checkPodLogs(m.cs, f.Namespace.Name, driverPodName, "mock", pod, test.expectPodInfo, test.expectEphemeral, csiInlineVolumesEnabled)
  313. framework.ExpectNoError(err)
  314. })
  315. }
  316. })
  317. ginkgo.Context("CSI volume limit information using mock driver", func() {
  318. ginkgo.It("should report attach limit when limit is bigger than 0 [Slow]", func() {
  319. // define volume limit to be 2 for this test
  320. var err error
  321. init(testParameters{attachLimit: 2})
  322. defer cleanup()
  323. nodeName := m.config.ClientNodeSelection.Name
  324. driverName := m.config.GetUniqueDriverName()
  325. csiNodeAttachLimit, err := checkCSINodeForLimits(nodeName, driverName, m.cs)
  326. framework.ExpectNoError(err, "while checking limits in CSINode: %v", err)
  327. gomega.Expect(csiNodeAttachLimit).To(gomega.BeNumerically("==", 2))
  328. _, _, pod1 := createPod(false)
  329. gomega.Expect(pod1).NotTo(gomega.BeNil(), "while creating first pod")
  330. err = e2epod.WaitForPodNameRunningInNamespace(m.cs, pod1.Name, pod1.Namespace)
  331. framework.ExpectNoError(err, "Failed to start pod1: %v", err)
  332. _, _, pod2 := createPod(false)
  333. gomega.Expect(pod2).NotTo(gomega.BeNil(), "while creating second pod")
  334. err = e2epod.WaitForPodNameRunningInNamespace(m.cs, pod2.Name, pod2.Namespace)
  335. framework.ExpectNoError(err, "Failed to start pod2: %v", err)
  336. _, _, pod3 := createPod(false)
  337. gomega.Expect(pod3).NotTo(gomega.BeNil(), "while creating third pod")
  338. err = waitForMaxVolumeCondition(pod3, m.cs)
  339. framework.ExpectNoError(err, "while waiting for max volume condition on pod : %+v", pod3)
  340. })
  341. })
  342. ginkgo.Context("CSI Volume expansion", func() {
  343. tests := []struct {
  344. name string
  345. nodeExpansionRequired bool
  346. disableAttach bool
  347. disableResizingOnDriver bool
  348. expectFailure bool
  349. }{
  350. {
  351. name: "should expand volume without restarting pod if nodeExpansion=off",
  352. nodeExpansionRequired: false,
  353. },
  354. {
  355. name: "should expand volume by restarting pod if attach=on, nodeExpansion=on",
  356. nodeExpansionRequired: true,
  357. },
  358. {
  359. name: "should expand volume by restarting pod if attach=off, nodeExpansion=on",
  360. disableAttach: true,
  361. nodeExpansionRequired: true,
  362. },
  363. {
  364. name: "should not expand volume if resizingOnDriver=off, resizingOnSC=on",
  365. disableResizingOnDriver: true,
  366. expectFailure: true,
  367. },
  368. }
  369. for _, t := range tests {
  370. test := t
  371. ginkgo.It(t.name, func() {
  372. var err error
  373. tp := testParameters{
  374. enableResizing: true,
  375. enableNodeExpansion: test.nodeExpansionRequired,
  376. disableResizingOnDriver: test.disableResizingOnDriver,
  377. }
  378. // disabling attach requires drive registration feature
  379. if test.disableAttach {
  380. tp.disableAttach = true
  381. tp.registerDriver = true
  382. }
  383. init(tp)
  384. defer cleanup()
  385. sc, pvc, pod := createPod(false)
  386. gomega.Expect(pod).NotTo(gomega.BeNil(), "while creating pod for resizing")
  387. framework.ExpectEqual(*sc.AllowVolumeExpansion, true, "failed creating sc with allowed expansion")
  388. err = e2epod.WaitForPodNameRunningInNamespace(m.cs, pod.Name, pod.Namespace)
  389. framework.ExpectNoError(err, "Failed to start pod1: %v", err)
  390. ginkgo.By("Expanding current pvc")
  391. newSize := resource.MustParse("6Gi")
  392. newPVC, err := testsuites.ExpandPVCSize(pvc, newSize, m.cs)
  393. framework.ExpectNoError(err, "While updating pvc for more size")
  394. pvc = newPVC
  395. gomega.Expect(pvc).NotTo(gomega.BeNil())
  396. pvcSize := pvc.Spec.Resources.Requests[v1.ResourceStorage]
  397. if pvcSize.Cmp(newSize) != 0 {
  398. framework.Failf("error updating pvc size %q", pvc.Name)
  399. }
  400. if test.expectFailure {
  401. err = testsuites.WaitForResizingCondition(pvc, m.cs, csiResizingConditionWait)
  402. framework.ExpectError(err, "unexpected resizing condition on PVC")
  403. return
  404. }
  405. ginkgo.By("Waiting for persistent volume resize to finish")
  406. err = testsuites.WaitForControllerVolumeResize(pvc, m.cs, csiResizeWaitPeriod)
  407. framework.ExpectNoError(err, "While waiting for CSI PV resize to finish")
  408. checkPVCSize := func() {
  409. ginkgo.By("Waiting for PVC resize to finish")
  410. pvc, err = testsuites.WaitForFSResize(pvc, m.cs)
  411. framework.ExpectNoError(err, "while waiting for PVC resize to finish")
  412. pvcConditions := pvc.Status.Conditions
  413. framework.ExpectEqual(len(pvcConditions), 0, "pvc should not have conditions")
  414. }
  415. // if node expansion is not required PVC should be resized as well
  416. if !test.nodeExpansionRequired {
  417. checkPVCSize()
  418. } else {
  419. ginkgo.By("Checking for conditions on pvc")
  420. npvc, err := testsuites.WaitForPendingFSResizeCondition(pvc, m.cs)
  421. framework.ExpectNoError(err, "While waiting for pvc to have fs resizing condition")
  422. pvc = npvc
  423. inProgressConditions := pvc.Status.Conditions
  424. if len(inProgressConditions) > 0 {
  425. framework.ExpectEqual(inProgressConditions[0].Type, v1.PersistentVolumeClaimFileSystemResizePending, "pvc must have fs resizing condition")
  426. }
  427. ginkgo.By("Deleting the previously created pod")
  428. err = e2epod.DeletePodWithWait(m.cs, pod)
  429. framework.ExpectNoError(err, "while deleting pod for resizing")
  430. ginkgo.By("Creating a new pod with same volume")
  431. pod2, err := createPodWithPVC(pvc)
  432. gomega.Expect(pod2).NotTo(gomega.BeNil(), "while creating pod for csi resizing")
  433. framework.ExpectNoError(err, "while recreating pod for resizing")
  434. checkPVCSize()
  435. }
  436. })
  437. }
  438. })
  439. ginkgo.Context("CSI online volume expansion", func() {
  440. tests := []struct {
  441. name string
  442. disableAttach bool
  443. }{
  444. {
  445. name: "should expand volume without restarting pod if attach=on, nodeExpansion=on",
  446. },
  447. {
  448. name: "should expand volume without restarting pod if attach=off, nodeExpansion=on",
  449. disableAttach: true,
  450. },
  451. }
  452. for _, t := range tests {
  453. test := t
  454. ginkgo.It(test.name, func() {
  455. var err error
  456. params := testParameters{enableResizing: true, enableNodeExpansion: true}
  457. if test.disableAttach {
  458. params.disableAttach = true
  459. params.registerDriver = true
  460. }
  461. init(params)
  462. defer cleanup()
  463. sc, pvc, pod := createPod(false)
  464. gomega.Expect(pod).NotTo(gomega.BeNil(), "while creating pod for resizing")
  465. framework.ExpectEqual(*sc.AllowVolumeExpansion, true, "failed creating sc with allowed expansion")
  466. err = e2epod.WaitForPodNameRunningInNamespace(m.cs, pod.Name, pod.Namespace)
  467. framework.ExpectNoError(err, "Failed to start pod1: %v", err)
  468. ginkgo.By("Expanding current pvc")
  469. newSize := resource.MustParse("6Gi")
  470. newPVC, err := testsuites.ExpandPVCSize(pvc, newSize, m.cs)
  471. framework.ExpectNoError(err, "While updating pvc for more size")
  472. pvc = newPVC
  473. gomega.Expect(pvc).NotTo(gomega.BeNil())
  474. pvcSize := pvc.Spec.Resources.Requests[v1.ResourceStorage]
  475. if pvcSize.Cmp(newSize) != 0 {
  476. framework.Failf("error updating pvc size %q", pvc.Name)
  477. }
  478. ginkgo.By("Waiting for persistent volume resize to finish")
  479. err = testsuites.WaitForControllerVolumeResize(pvc, m.cs, csiResizeWaitPeriod)
  480. framework.ExpectNoError(err, "While waiting for PV resize to finish")
  481. ginkgo.By("Waiting for PVC resize to finish")
  482. pvc, err = testsuites.WaitForFSResize(pvc, m.cs)
  483. framework.ExpectNoError(err, "while waiting for PVC to finish")
  484. pvcConditions := pvc.Status.Conditions
  485. framework.ExpectEqual(len(pvcConditions), 0, "pvc should not have conditions")
  486. })
  487. }
  488. })
  489. })
  490. func waitForMaxVolumeCondition(pod *v1.Pod, cs clientset.Interface) error {
  491. reg, err := regexp.Compile(`max.+volume.+count`)
  492. if err != nil {
  493. return err
  494. }
  495. waitErr := wait.PollImmediate(10*time.Second, csiPodUnschedulableTimeout, func() (bool, error) {
  496. pod, err = cs.CoreV1().Pods(pod.Namespace).Get(context.TODO(), pod.Name, metav1.GetOptions{})
  497. if err != nil {
  498. return false, err
  499. }
  500. conditions := pod.Status.Conditions
  501. for _, condition := range conditions {
  502. matched := reg.MatchString(condition.Message)
  503. if condition.Reason == v1.PodReasonUnschedulable && matched {
  504. return true, nil
  505. }
  506. }
  507. return false, nil
  508. })
  509. if waitErr != nil {
  510. return fmt.Errorf("error waiting for pod %s/%s to have max volume condition: %v", pod.Namespace, pod.Name, waitErr)
  511. }
  512. return nil
  513. }
  514. func checkCSINodeForLimits(nodeName string, driverName string, cs clientset.Interface) (int32, error) {
  515. var attachLimit int32
  516. waitErr := wait.PollImmediate(10*time.Second, csiNodeLimitUpdateTimeout, func() (bool, error) {
  517. csiNode, err := cs.StorageV1().CSINodes().Get(context.TODO(), nodeName, metav1.GetOptions{})
  518. if err != nil && !apierrors.IsNotFound(err) {
  519. return false, err
  520. }
  521. attachLimit = getVolumeLimitFromCSINode(csiNode, driverName)
  522. if attachLimit > 0 {
  523. return true, nil
  524. }
  525. return false, nil
  526. })
  527. if waitErr != nil {
  528. return 0, fmt.Errorf("error waiting for non-zero volume limit of driver %s on node %s: %v", driverName, nodeName, waitErr)
  529. }
  530. return attachLimit, nil
  531. }
  532. func startPausePod(cs clientset.Interface, t testsuites.StorageClassTest, node e2epod.NodeSelection, ns string) (*storagev1.StorageClass, *v1.PersistentVolumeClaim, *v1.Pod) {
  533. class := newStorageClass(t, ns, "")
  534. var err error
  535. _, err = cs.StorageV1().StorageClasses().Get(context.TODO(), class.Name, metav1.GetOptions{})
  536. if err != nil {
  537. class, err = cs.StorageV1().StorageClasses().Create(context.TODO(), class, metav1.CreateOptions{})
  538. framework.ExpectNoError(err, "Failed to create class : %v", err)
  539. }
  540. claim := e2epv.MakePersistentVolumeClaim(e2epv.PersistentVolumeClaimConfig{
  541. ClaimSize: t.ClaimSize,
  542. StorageClassName: &(class.Name),
  543. VolumeMode: &t.VolumeMode,
  544. }, ns)
  545. claim, err = cs.CoreV1().PersistentVolumeClaims(ns).Create(context.TODO(), claim, metav1.CreateOptions{})
  546. framework.ExpectNoError(err, "Failed to create claim: %v", err)
  547. pvcClaims := []*v1.PersistentVolumeClaim{claim}
  548. _, err = e2epv.WaitForPVClaimBoundPhase(cs, pvcClaims, framework.ClaimProvisionTimeout)
  549. framework.ExpectNoError(err, "Failed waiting for PVC to be bound %v", err)
  550. pod, err := startPausePodWithClaim(cs, claim, node, ns)
  551. framework.ExpectNoError(err, "Failed to create pod: %v", err)
  552. return class, claim, pod
  553. }
  554. func startPausePodInline(cs clientset.Interface, t testsuites.StorageClassTest, node e2epod.NodeSelection, ns string) *v1.Pod {
  555. pod, err := startPausePodWithInlineVolume(cs,
  556. &v1.CSIVolumeSource{
  557. Driver: t.Provisioner,
  558. },
  559. node, ns)
  560. framework.ExpectNoError(err, "Failed to create pod: %v", err)
  561. return pod
  562. }
  563. func startPausePodWithClaim(cs clientset.Interface, pvc *v1.PersistentVolumeClaim, node e2epod.NodeSelection, ns string) (*v1.Pod, error) {
  564. return startPausePodWithVolumeSource(cs,
  565. v1.VolumeSource{
  566. PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
  567. ClaimName: pvc.Name,
  568. ReadOnly: false,
  569. },
  570. },
  571. node, ns)
  572. }
  573. func startPausePodWithInlineVolume(cs clientset.Interface, inlineVolume *v1.CSIVolumeSource, node e2epod.NodeSelection, ns string) (*v1.Pod, error) {
  574. return startPausePodWithVolumeSource(cs,
  575. v1.VolumeSource{
  576. CSI: inlineVolume,
  577. },
  578. node, ns)
  579. }
  580. func startPausePodWithVolumeSource(cs clientset.Interface, volumeSource v1.VolumeSource, node e2epod.NodeSelection, ns string) (*v1.Pod, error) {
  581. pod := &v1.Pod{
  582. ObjectMeta: metav1.ObjectMeta{
  583. GenerateName: "pvc-volume-tester-",
  584. },
  585. Spec: v1.PodSpec{
  586. Containers: []v1.Container{
  587. {
  588. Name: "volume-tester",
  589. Image: imageutils.GetE2EImage(imageutils.Pause),
  590. VolumeMounts: []v1.VolumeMount{
  591. {
  592. Name: "my-volume",
  593. MountPath: "/mnt/test",
  594. },
  595. },
  596. },
  597. },
  598. RestartPolicy: v1.RestartPolicyNever,
  599. Volumes: []v1.Volume{
  600. {
  601. Name: "my-volume",
  602. VolumeSource: volumeSource,
  603. },
  604. },
  605. },
  606. }
  607. e2epod.SetNodeSelection(pod, node)
  608. return cs.CoreV1().Pods(ns).Create(context.TODO(), pod, metav1.CreateOptions{})
  609. }
  610. // checkPodLogs tests that NodePublish was called with expected volume_context and (for ephemeral inline volumes)
  611. // has the matching NodeUnpublish
  612. func checkPodLogs(cs clientset.Interface, namespace, driverPodName, driverContainerName string, pod *v1.Pod, expectPodInfo, ephemeralVolume, csiInlineVolumesEnabled bool) error {
  613. expectedAttributes := map[string]string{
  614. "csi.storage.k8s.io/pod.name": pod.Name,
  615. "csi.storage.k8s.io/pod.namespace": namespace,
  616. "csi.storage.k8s.io/pod.uid": string(pod.UID),
  617. "csi.storage.k8s.io/serviceAccount.name": "default",
  618. }
  619. if csiInlineVolumesEnabled {
  620. // This is only passed in 1.15 when the CSIInlineVolume feature gate is set.
  621. expectedAttributes["csi.storage.k8s.io/ephemeral"] = strconv.FormatBool(ephemeralVolume)
  622. }
  623. // Load logs of driver pod
  624. log, err := e2epod.GetPodLogs(cs, namespace, driverPodName, driverContainerName)
  625. if err != nil {
  626. return fmt.Errorf("could not load CSI driver logs: %s", err)
  627. }
  628. framework.Logf("CSI driver logs:\n%s", log)
  629. // Find NodePublish in the logs
  630. foundAttributes := sets.NewString()
  631. logLines := strings.Split(log, "\n")
  632. numNodePublishVolume := 0
  633. numNodeUnpublishVolume := 0
  634. for _, line := range logLines {
  635. if !strings.HasPrefix(line, "gRPCCall:") {
  636. continue
  637. }
  638. line = strings.TrimPrefix(line, "gRPCCall:")
  639. // Dummy structure that parses just volume_attributes out of logged CSI call
  640. type MockCSICall struct {
  641. Method string
  642. Request struct {
  643. VolumeContext map[string]string `json:"volume_context"`
  644. }
  645. }
  646. var call MockCSICall
  647. err := json.Unmarshal([]byte(line), &call)
  648. if err != nil {
  649. framework.Logf("Could not parse CSI driver log line %q: %s", line, err)
  650. continue
  651. }
  652. switch call.Method {
  653. case "/csi.v1.Node/NodePublishVolume":
  654. numNodePublishVolume++
  655. if numNodePublishVolume == 1 {
  656. // Check that NodePublish had expected attributes for first volume
  657. for k, v := range expectedAttributes {
  658. vv, found := call.Request.VolumeContext[k]
  659. if found && v == vv {
  660. foundAttributes.Insert(k)
  661. framework.Logf("Found volume attribute %s: %s", k, v)
  662. }
  663. }
  664. }
  665. case "/csi.v1.Node/NodeUnpublishVolume":
  666. framework.Logf("Found NodeUnpublishVolume: %+v", call)
  667. numNodeUnpublishVolume++
  668. }
  669. }
  670. if numNodePublishVolume == 0 {
  671. return fmt.Errorf("NodePublish was never called")
  672. }
  673. if numNodeUnpublishVolume == 0 {
  674. return fmt.Errorf("NodeUnpublish was never called")
  675. }
  676. if expectPodInfo {
  677. if foundAttributes.Len() != len(expectedAttributes) {
  678. return fmt.Errorf("number of found volume attributes does not match, expected %d, got %d", len(expectedAttributes), foundAttributes.Len())
  679. }
  680. return nil
  681. }
  682. if foundAttributes.Len() != 0 {
  683. return fmt.Errorf("some unexpected volume attributes were found: %+v", foundAttributes.List())
  684. }
  685. return nil
  686. }
  687. func waitForCSIDriver(cs clientset.Interface, driverName string) error {
  688. timeout := 4 * time.Minute
  689. framework.Logf("waiting up to %v for CSIDriver %q", timeout, driverName)
  690. for start := time.Now(); time.Since(start) < timeout; time.Sleep(framework.Poll) {
  691. _, err := cs.StorageV1beta1().CSIDrivers().Get(context.TODO(), driverName, metav1.GetOptions{})
  692. if !apierrors.IsNotFound(err) {
  693. return err
  694. }
  695. }
  696. return fmt.Errorf("gave up after waiting %v for CSIDriver %q", timeout, driverName)
  697. }
  698. func destroyCSIDriver(cs clientset.Interface, driverName string) {
  699. driverGet, err := cs.StorageV1beta1().CSIDrivers().Get(context.TODO(), driverName, metav1.GetOptions{})
  700. if err == nil {
  701. framework.Logf("deleting %s.%s: %s", driverGet.TypeMeta.APIVersion, driverGet.TypeMeta.Kind, driverGet.ObjectMeta.Name)
  702. // Uncomment the following line to get full dump of CSIDriver object
  703. // framework.Logf("%s", framework.PrettyPrint(driverGet))
  704. cs.StorageV1beta1().CSIDrivers().Delete(context.TODO(), driverName, nil)
  705. }
  706. }
  707. func getVolumeHandle(cs clientset.Interface, claim *v1.PersistentVolumeClaim) string {
  708. // re-get the claim to the latest state with bound volume
  709. claim, err := cs.CoreV1().PersistentVolumeClaims(claim.Namespace).Get(context.TODO(), claim.Name, metav1.GetOptions{})
  710. if err != nil {
  711. framework.ExpectNoError(err, "Cannot get PVC")
  712. return ""
  713. }
  714. pvName := claim.Spec.VolumeName
  715. pv, err := cs.CoreV1().PersistentVolumes().Get(context.TODO(), pvName, metav1.GetOptions{})
  716. if err != nil {
  717. framework.ExpectNoError(err, "Cannot get PV")
  718. return ""
  719. }
  720. if pv.Spec.CSI == nil {
  721. gomega.Expect(pv.Spec.CSI).NotTo(gomega.BeNil())
  722. return ""
  723. }
  724. return pv.Spec.CSI.VolumeHandle
  725. }
  726. func getVolumeLimitFromCSINode(csiNode *storagev1.CSINode, driverName string) int32 {
  727. for _, d := range csiNode.Spec.Drivers {
  728. if d.Name != driverName {
  729. continue
  730. }
  731. if d.Allocatable != nil && d.Allocatable.Count != nil {
  732. return *d.Allocatable.Count
  733. }
  734. }
  735. return 0
  736. }