subpath.go 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922
  1. /*
  2. Copyright 2018 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. "fmt"
  16. "path/filepath"
  17. "regexp"
  18. "strings"
  19. v1 "k8s.io/api/core/v1"
  20. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  21. "k8s.io/apimachinery/pkg/util/rand"
  22. "k8s.io/apimachinery/pkg/util/wait"
  23. "k8s.io/kubernetes/test/e2e/framework"
  24. e2elog "k8s.io/kubernetes/test/e2e/framework/log"
  25. "k8s.io/kubernetes/test/e2e/framework/volume"
  26. "k8s.io/kubernetes/test/e2e/storage/testpatterns"
  27. "k8s.io/kubernetes/test/e2e/storage/utils"
  28. imageutils "k8s.io/kubernetes/test/utils/image"
  29. "time"
  30. "github.com/onsi/ginkgo"
  31. )
  32. var (
  33. volumePath = "/test-volume"
  34. volumeName = "test-volume"
  35. probeVolumePath = "/probe-volume"
  36. probeFilePath = probeVolumePath + "/probe-file"
  37. fileName = "test-file"
  38. retryDuration = 20
  39. mountImage = imageutils.GetE2EImage(imageutils.Mounttest)
  40. )
  41. type subPathTestSuite struct {
  42. tsInfo TestSuiteInfo
  43. }
  44. var _ TestSuite = &subPathTestSuite{}
  45. // InitSubPathTestSuite returns subPathTestSuite that implements TestSuite interface
  46. func InitSubPathTestSuite() TestSuite {
  47. return &subPathTestSuite{
  48. tsInfo: TestSuiteInfo{
  49. name: "subPath",
  50. testPatterns: []testpatterns.TestPattern{
  51. testpatterns.DefaultFsInlineVolume,
  52. testpatterns.DefaultFsPreprovisionedPV,
  53. testpatterns.DefaultFsDynamicPV,
  54. testpatterns.NtfsDynamicPV,
  55. },
  56. },
  57. }
  58. }
  59. func (s *subPathTestSuite) getTestSuiteInfo() TestSuiteInfo {
  60. return s.tsInfo
  61. }
  62. func (s *subPathTestSuite) defineTests(driver TestDriver, pattern testpatterns.TestPattern) {
  63. type local struct {
  64. config *PerTestConfig
  65. testCleanup func()
  66. resource *genericVolumeTestResource
  67. roVolSource *v1.VolumeSource
  68. pod *v1.Pod
  69. formatPod *v1.Pod
  70. subPathDir string
  71. filePathInSubpath string
  72. filePathInVolume string
  73. intreeOps opCounts
  74. migratedOps opCounts
  75. }
  76. var l local
  77. // No preconditions to test. Normally they would be in a BeforeEach here.
  78. // This intentionally comes after checking the preconditions because it
  79. // registers its own BeforeEach which creates the namespace. Beware that it
  80. // also registers an AfterEach which renders f unusable. Any code using
  81. // f must run inside an It or Context callback.
  82. f := framework.NewDefaultFramework("provisioning")
  83. init := func() {
  84. l = local{}
  85. // Now do the more expensive test initialization.
  86. l.config, l.testCleanup = driver.PrepareTest(f)
  87. l.intreeOps, l.migratedOps = getMigrationVolumeOpCounts(f.ClientSet, driver.GetDriverInfo().InTreePluginName)
  88. l.resource = createGenericVolumeTestResource(driver, l.config, pattern)
  89. // Setup subPath test dependent resource
  90. volType := pattern.VolType
  91. switch volType {
  92. case testpatterns.InlineVolume:
  93. if iDriver, ok := driver.(InlineVolumeTestDriver); ok {
  94. l.roVolSource = iDriver.GetVolumeSource(true, pattern.FsType, l.resource.volume)
  95. }
  96. case testpatterns.PreprovisionedPV:
  97. l.roVolSource = &v1.VolumeSource{
  98. PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
  99. ClaimName: l.resource.pvc.Name,
  100. ReadOnly: true,
  101. },
  102. }
  103. case testpatterns.DynamicPV:
  104. l.roVolSource = &v1.VolumeSource{
  105. PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
  106. ClaimName: l.resource.pvc.Name,
  107. ReadOnly: true,
  108. },
  109. }
  110. default:
  111. framework.Failf("SubPath test doesn't support: %s", volType)
  112. }
  113. subPath := f.Namespace.Name
  114. l.pod = SubpathTestPod(f, subPath, l.resource.volType, l.resource.volSource, true)
  115. l.pod.Spec.NodeName = l.config.ClientNodeName
  116. l.pod.Spec.NodeSelector = l.config.ClientNodeSelector
  117. l.formatPod = volumeFormatPod(f, l.resource.volSource)
  118. l.formatPod.Spec.NodeName = l.config.ClientNodeName
  119. l.formatPod.Spec.NodeSelector = l.config.ClientNodeSelector
  120. l.subPathDir = filepath.Join(volumePath, subPath)
  121. l.filePathInSubpath = filepath.Join(volumePath, fileName)
  122. l.filePathInVolume = filepath.Join(l.subPathDir, fileName)
  123. }
  124. cleanup := func() {
  125. if l.pod != nil {
  126. ginkgo.By("Deleting pod")
  127. err := framework.DeletePodWithWait(f, f.ClientSet, l.pod)
  128. framework.ExpectNoError(err, "while deleting pod")
  129. l.pod = nil
  130. }
  131. if l.resource != nil {
  132. l.resource.cleanupResource()
  133. l.resource = nil
  134. }
  135. if l.testCleanup != nil {
  136. l.testCleanup()
  137. l.testCleanup = nil
  138. }
  139. validateMigrationVolumeOpCounts(f.ClientSet, driver.GetDriverInfo().InTreePluginName, l.intreeOps, l.migratedOps)
  140. }
  141. ginkgo.It("should support non-existent path", func() {
  142. init()
  143. defer cleanup()
  144. // Write the file in the subPath from init container 1
  145. setWriteCommand(l.filePathInSubpath, &l.pod.Spec.InitContainers[1])
  146. // Read it from outside the subPath from container 1
  147. testReadFile(f, l.filePathInVolume, l.pod, 1)
  148. })
  149. ginkgo.It("should support existing directory", func() {
  150. init()
  151. defer cleanup()
  152. // Create the directory
  153. setInitCommand(l.pod, fmt.Sprintf("mkdir -p %s", l.subPathDir))
  154. // Write the file in the subPath from init container 1
  155. setWriteCommand(l.filePathInSubpath, &l.pod.Spec.InitContainers[1])
  156. // Read it from outside the subPath from container 1
  157. testReadFile(f, l.filePathInVolume, l.pod, 1)
  158. })
  159. ginkgo.It("should support existing single file [LinuxOnly]", func() {
  160. init()
  161. defer cleanup()
  162. // Create the file in the init container
  163. setInitCommand(l.pod, fmt.Sprintf("mkdir -p %s; echo \"mount-tester new file\" > %s", l.subPathDir, l.filePathInVolume))
  164. // Read it from inside the subPath from container 0
  165. testReadFile(f, l.filePathInSubpath, l.pod, 0)
  166. })
  167. ginkgo.It("should support file as subpath [LinuxOnly]", func() {
  168. init()
  169. defer cleanup()
  170. // Create the file in the init container
  171. setInitCommand(l.pod, fmt.Sprintf("echo %s > %s", f.Namespace.Name, l.subPathDir))
  172. TestBasicSubpath(f, f.Namespace.Name, l.pod)
  173. })
  174. ginkgo.It("should fail if subpath directory is outside the volume [Slow]", func() {
  175. init()
  176. defer cleanup()
  177. // Create the subpath outside the volume
  178. var command string
  179. if framework.NodeOSDistroIs("windows") {
  180. command = fmt.Sprintf("New-Item -ItemType SymbolicLink -Path %s -value \\Windows", l.subPathDir)
  181. } else {
  182. command = fmt.Sprintf("ln -s /bin %s", l.subPathDir)
  183. }
  184. setInitCommand(l.pod, command)
  185. // Pod should fail
  186. testPodFailSubpath(f, l.pod, false)
  187. })
  188. ginkgo.It("should fail if subpath file is outside the volume [Slow][LinuxOnly]", func() {
  189. init()
  190. defer cleanup()
  191. // Create the subpath outside the volume
  192. setInitCommand(l.pod, fmt.Sprintf("ln -s /bin/sh %s", l.subPathDir))
  193. // Pod should fail
  194. testPodFailSubpath(f, l.pod, false)
  195. })
  196. ginkgo.It("should fail if non-existent subpath is outside the volume [Slow][LinuxOnly]", func() {
  197. init()
  198. defer cleanup()
  199. // Create the subpath outside the volume
  200. setInitCommand(l.pod, fmt.Sprintf("ln -s /bin/notanexistingpath %s", l.subPathDir))
  201. // Pod should fail
  202. testPodFailSubpath(f, l.pod, false)
  203. })
  204. ginkgo.It("should fail if subpath with backstepping is outside the volume [Slow]", func() {
  205. init()
  206. defer cleanup()
  207. // Create the subpath outside the volume
  208. var command string
  209. if framework.NodeOSDistroIs("windows") {
  210. command = fmt.Sprintf("New-Item -ItemType SymbolicLink -Path %s -value ..\\", l.subPathDir)
  211. } else {
  212. command = fmt.Sprintf("ln -s ../ %s", l.subPathDir)
  213. }
  214. setInitCommand(l.pod, command)
  215. // Pod should fail
  216. testPodFailSubpath(f, l.pod, false)
  217. })
  218. ginkgo.It("should support creating multiple subpath from same volumes [Slow]", func() {
  219. init()
  220. defer cleanup()
  221. subpathDir1 := filepath.Join(volumePath, "subpath1")
  222. subpathDir2 := filepath.Join(volumePath, "subpath2")
  223. filepath1 := filepath.Join("/test-subpath1", fileName)
  224. filepath2 := filepath.Join("/test-subpath2", fileName)
  225. setInitCommand(l.pod, fmt.Sprintf("mkdir -p %s; mkdir -p %s", subpathDir1, subpathDir2))
  226. addSubpathVolumeContainer(&l.pod.Spec.Containers[0], v1.VolumeMount{
  227. Name: volumeName,
  228. MountPath: "/test-subpath1",
  229. SubPath: "subpath1",
  230. })
  231. addSubpathVolumeContainer(&l.pod.Spec.Containers[0], v1.VolumeMount{
  232. Name: volumeName,
  233. MountPath: "/test-subpath2",
  234. SubPath: "subpath2",
  235. })
  236. // Write the files from container 0 and instantly read them back
  237. addMultipleWrites(&l.pod.Spec.Containers[0], filepath1, filepath2)
  238. testMultipleReads(f, l.pod, 0, filepath1, filepath2)
  239. })
  240. ginkgo.It("should support restarting containers using directory as subpath [Slow]", func() {
  241. init()
  242. defer cleanup()
  243. // Create the directory
  244. var command string
  245. if framework.NodeOSDistroIs("windows") {
  246. command = fmt.Sprintf("mkdir -p %v; New-Item -itemtype File -path %v", l.subPathDir, probeFilePath)
  247. } else {
  248. command = fmt.Sprintf("mkdir -p %v; touch %v", l.subPathDir, probeFilePath)
  249. }
  250. setInitCommand(l.pod, command)
  251. testPodContainerRestart(f, l.pod)
  252. })
  253. ginkgo.It("should support restarting containers using file as subpath [Slow][LinuxOnly]", func() {
  254. init()
  255. defer cleanup()
  256. // Create the file
  257. setInitCommand(l.pod, fmt.Sprintf("touch %v; touch %v", l.subPathDir, probeFilePath))
  258. testPodContainerRestart(f, l.pod)
  259. })
  260. ginkgo.It("should unmount if pod is gracefully deleted while kubelet is down [Disruptive][Slow][LinuxOnly]", func() {
  261. init()
  262. defer cleanup()
  263. testSubpathReconstruction(f, l.pod, false)
  264. })
  265. ginkgo.It("should unmount if pod is force deleted while kubelet is down [Disruptive][Slow][LinuxOnly]", func() {
  266. init()
  267. defer cleanup()
  268. if strings.HasPrefix(l.resource.volType, "hostPath") || strings.HasPrefix(l.resource.volType, "csi-hostpath") {
  269. // TODO: This skip should be removed once #61446 is fixed
  270. framework.Skipf("%s volume type does not support reconstruction, skipping", l.resource.volType)
  271. }
  272. testSubpathReconstruction(f, l.pod, true)
  273. })
  274. ginkgo.It("should support readOnly directory specified in the volumeMount", func() {
  275. init()
  276. defer cleanup()
  277. // Create the directory
  278. setInitCommand(l.pod, fmt.Sprintf("mkdir -p %s", l.subPathDir))
  279. // Write the file in the volume from init container 2
  280. setWriteCommand(l.filePathInVolume, &l.pod.Spec.InitContainers[2])
  281. // Read it from inside the subPath from container 0
  282. l.pod.Spec.Containers[0].VolumeMounts[0].ReadOnly = true
  283. testReadFile(f, l.filePathInSubpath, l.pod, 0)
  284. })
  285. ginkgo.It("should support readOnly file specified in the volumeMount [LinuxOnly]", func() {
  286. init()
  287. defer cleanup()
  288. // Create the file
  289. setInitCommand(l.pod, fmt.Sprintf("touch %s", l.subPathDir))
  290. // Write the file in the volume from init container 2
  291. setWriteCommand(l.subPathDir, &l.pod.Spec.InitContainers[2])
  292. // Read it from inside the subPath from container 0
  293. l.pod.Spec.Containers[0].VolumeMounts[0].ReadOnly = true
  294. testReadFile(f, volumePath, l.pod, 0)
  295. })
  296. ginkgo.It("should support existing directories when readOnly specified in the volumeSource", func() {
  297. init()
  298. defer cleanup()
  299. if l.roVolSource == nil {
  300. framework.Skipf("Volume type %v doesn't support readOnly source", l.resource.volType)
  301. }
  302. origpod := l.pod.DeepCopy()
  303. // Create the directory
  304. setInitCommand(l.pod, fmt.Sprintf("mkdir -p %s", l.subPathDir))
  305. // Write the file in the subPath from init container 1
  306. setWriteCommand(l.filePathInSubpath, &l.pod.Spec.InitContainers[1])
  307. // Read it from inside the subPath from container 0
  308. testReadFile(f, l.filePathInSubpath, l.pod, 0)
  309. // Reset the pod
  310. l.pod = origpod
  311. // Set volume source to read only
  312. l.pod.Spec.Volumes[0].VolumeSource = *l.roVolSource
  313. // Read it from inside the subPath from container 0
  314. testReadFile(f, l.filePathInSubpath, l.pod, 0)
  315. })
  316. ginkgo.It("should verify container cannot write to subpath readonly volumes [Slow]", func() {
  317. init()
  318. defer cleanup()
  319. if l.roVolSource == nil {
  320. framework.Skipf("Volume type %v doesn't support readOnly source", l.resource.volType)
  321. }
  322. // Format the volume while it's writable
  323. formatVolume(f, l.formatPod)
  324. // Set volume source to read only
  325. l.pod.Spec.Volumes[0].VolumeSource = *l.roVolSource
  326. // Write the file in the volume from container 0
  327. setWriteCommand(l.subPathDir, &l.pod.Spec.Containers[0])
  328. // Pod should fail
  329. testPodFailSubpath(f, l.pod, true)
  330. })
  331. ginkgo.It("should be able to unmount after the subpath directory is deleted", func() {
  332. init()
  333. defer cleanup()
  334. // Change volume container to busybox so we can exec later
  335. l.pod.Spec.Containers[1].Image = volume.GetTestImage(imageutils.GetE2EImage(imageutils.BusyBox))
  336. l.pod.Spec.Containers[1].Command = volume.GenerateScriptCmd("sleep 100000")
  337. ginkgo.By(fmt.Sprintf("Creating pod %s", l.pod.Name))
  338. removeUnusedContainers(l.pod)
  339. pod, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Create(l.pod)
  340. framework.ExpectNoError(err, "while creating pod")
  341. defer func() {
  342. ginkgo.By(fmt.Sprintf("Deleting pod %s", pod.Name))
  343. framework.DeletePodWithWait(f, f.ClientSet, pod)
  344. }()
  345. // Wait for pod to be running
  346. err = framework.WaitForPodRunningInNamespace(f.ClientSet, l.pod)
  347. framework.ExpectNoError(err, "while waiting for pod to be running")
  348. // Exec into container that mounted the volume, delete subpath directory
  349. rmCmd := fmt.Sprintf("rm -r %s", l.subPathDir)
  350. _, err = podContainerExec(l.pod, 1, rmCmd)
  351. framework.ExpectNoError(err, "while removing subpath directory")
  352. // Delete pod (from defer) and wait for it to be successfully deleted
  353. })
  354. // TODO: add a test case for the same disk with two partitions
  355. }
  356. // TestBasicSubpath runs basic subpath test
  357. func TestBasicSubpath(f *framework.Framework, contents string, pod *v1.Pod) {
  358. TestBasicSubpathFile(f, contents, pod, volumePath)
  359. }
  360. // TestBasicSubpathFile runs basic subpath file test
  361. func TestBasicSubpathFile(f *framework.Framework, contents string, pod *v1.Pod, filepath string) {
  362. setReadCommand(filepath, &pod.Spec.Containers[0])
  363. ginkgo.By(fmt.Sprintf("Creating pod %s", pod.Name))
  364. removeUnusedContainers(pod)
  365. f.TestContainerOutput("atomic-volume-subpath", pod, 0, []string{contents})
  366. ginkgo.By(fmt.Sprintf("Deleting pod %s", pod.Name))
  367. err := framework.DeletePodWithWait(f, f.ClientSet, pod)
  368. framework.ExpectNoError(err, "while deleting pod")
  369. }
  370. func generateSuffixForPodName(s string) string {
  371. // Pod name must:
  372. // 1. consist of lower case alphanumeric characters or '-',
  373. // 2. start and end with an alphanumeric character.
  374. // (e.g. 'my-name', or '123-abc', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?')
  375. // Therefore, suffix is generated by following steps:
  376. // 1. all strings other than [A-Za-z0-9] is replaced with "-",
  377. // 2. add lower case alphanumeric characters at the end ('-[a-z0-9]{4}' is added),
  378. // 3. convert the entire strings to lower case.
  379. re := regexp.MustCompile("[^A-Za-z0-9]")
  380. return strings.ToLower(fmt.Sprintf("%s-%s", re.ReplaceAllString(s, "-"), rand.String(4)))
  381. }
  382. // SubpathTestPod returns a pod spec for subpath tests
  383. func SubpathTestPod(f *framework.Framework, subpath, volumeType string, source *v1.VolumeSource, privilegedSecurityContext bool) *v1.Pod {
  384. var (
  385. suffix = generateSuffixForPodName(volumeType)
  386. gracePeriod = int64(1)
  387. probeVolumeName = "liveness-probe-volume"
  388. seLinuxOptions = &v1.SELinuxOptions{Level: "s0:c0,c1"}
  389. )
  390. return &v1.Pod{
  391. ObjectMeta: metav1.ObjectMeta{
  392. Name: fmt.Sprintf("pod-subpath-test-%s", suffix),
  393. Namespace: f.Namespace.Name,
  394. },
  395. Spec: v1.PodSpec{
  396. InitContainers: []v1.Container{
  397. {
  398. Name: fmt.Sprintf("init-volume-%s", suffix),
  399. Image: volume.GetTestImage(imageutils.GetE2EImage(imageutils.BusyBox)),
  400. VolumeMounts: []v1.VolumeMount{
  401. {
  402. Name: volumeName,
  403. MountPath: volumePath,
  404. },
  405. {
  406. Name: probeVolumeName,
  407. MountPath: probeVolumePath,
  408. },
  409. },
  410. SecurityContext: volume.GenerateSecurityContext(privilegedSecurityContext),
  411. },
  412. {
  413. Name: fmt.Sprintf("test-init-subpath-%s", suffix),
  414. Image: mountImage,
  415. VolumeMounts: []v1.VolumeMount{
  416. {
  417. Name: volumeName,
  418. MountPath: volumePath,
  419. SubPath: subpath,
  420. },
  421. {
  422. Name: probeVolumeName,
  423. MountPath: probeVolumePath,
  424. },
  425. },
  426. SecurityContext: volume.GenerateSecurityContext(privilegedSecurityContext),
  427. },
  428. {
  429. Name: fmt.Sprintf("test-init-volume-%s", suffix),
  430. Image: mountImage,
  431. VolumeMounts: []v1.VolumeMount{
  432. {
  433. Name: volumeName,
  434. MountPath: volumePath,
  435. },
  436. {
  437. Name: probeVolumeName,
  438. MountPath: probeVolumePath,
  439. },
  440. },
  441. SecurityContext: volume.GenerateSecurityContext(privilegedSecurityContext),
  442. },
  443. },
  444. Containers: []v1.Container{
  445. {
  446. Name: fmt.Sprintf("test-container-subpath-%s", suffix),
  447. Image: mountImage,
  448. VolumeMounts: []v1.VolumeMount{
  449. {
  450. Name: volumeName,
  451. MountPath: volumePath,
  452. SubPath: subpath,
  453. },
  454. {
  455. Name: probeVolumeName,
  456. MountPath: probeVolumePath,
  457. },
  458. },
  459. SecurityContext: volume.GenerateSecurityContext(privilegedSecurityContext),
  460. },
  461. {
  462. Name: fmt.Sprintf("test-container-volume-%s", suffix),
  463. Image: mountImage,
  464. VolumeMounts: []v1.VolumeMount{
  465. {
  466. Name: volumeName,
  467. MountPath: volumePath,
  468. },
  469. {
  470. Name: probeVolumeName,
  471. MountPath: probeVolumePath,
  472. },
  473. },
  474. SecurityContext: volume.GenerateSecurityContext(privilegedSecurityContext),
  475. },
  476. },
  477. RestartPolicy: v1.RestartPolicyNever,
  478. TerminationGracePeriodSeconds: &gracePeriod,
  479. Volumes: []v1.Volume{
  480. {
  481. Name: volumeName,
  482. VolumeSource: *source,
  483. },
  484. {
  485. Name: probeVolumeName,
  486. VolumeSource: v1.VolumeSource{
  487. EmptyDir: &v1.EmptyDirVolumeSource{},
  488. },
  489. },
  490. },
  491. SecurityContext: volume.GeneratePodSecurityContext(nil, seLinuxOptions),
  492. },
  493. }
  494. }
  495. func containerIsUnused(container *v1.Container) bool {
  496. // mountImage with nil Args does nothing. Leave everything else
  497. return container.Image == mountImage && container.Args == nil
  498. }
  499. // removeUnusedContainers removes containers from a SubpathTestPod that aren't
  500. // needed for a test. e.g. to test for subpath mount failure, only one
  501. // container needs to run and get its status checked.
  502. func removeUnusedContainers(pod *v1.Pod) {
  503. initContainers := []v1.Container{}
  504. containers := []v1.Container{}
  505. if pod.Spec.InitContainers[0].Command != nil {
  506. initContainers = append(initContainers, pod.Spec.InitContainers[0])
  507. }
  508. for _, ic := range pod.Spec.InitContainers[1:] {
  509. if !containerIsUnused(&ic) {
  510. initContainers = append(initContainers, ic)
  511. }
  512. }
  513. containers = append(containers, pod.Spec.Containers[0])
  514. if !containerIsUnused(&pod.Spec.Containers[1]) {
  515. containers = append(containers, pod.Spec.Containers[1])
  516. }
  517. pod.Spec.InitContainers = initContainers
  518. pod.Spec.Containers = containers
  519. }
  520. // volumeFormatPod returns a Pod that does nothing but will cause the plugin to format a filesystem
  521. // on first use
  522. func volumeFormatPod(f *framework.Framework, volumeSource *v1.VolumeSource) *v1.Pod {
  523. return &v1.Pod{
  524. ObjectMeta: metav1.ObjectMeta{
  525. Name: fmt.Sprintf("volume-prep-%s", f.Namespace.Name),
  526. },
  527. Spec: v1.PodSpec{
  528. Containers: []v1.Container{
  529. {
  530. Name: fmt.Sprintf("init-volume-%s", f.Namespace.Name),
  531. Image: volume.GetTestImage(imageutils.GetE2EImage(imageutils.BusyBox)),
  532. Command: volume.GenerateScriptCmd("echo nothing"),
  533. VolumeMounts: []v1.VolumeMount{
  534. {
  535. Name: volumeName,
  536. MountPath: "/vol",
  537. },
  538. },
  539. },
  540. },
  541. RestartPolicy: v1.RestartPolicyNever,
  542. Volumes: []v1.Volume{
  543. {
  544. Name: volumeName,
  545. VolumeSource: *volumeSource,
  546. },
  547. },
  548. },
  549. }
  550. }
  551. func setInitCommand(pod *v1.Pod, command string) {
  552. pod.Spec.InitContainers[0].Command = volume.GenerateScriptCmd(command)
  553. }
  554. func setWriteCommand(file string, container *v1.Container) {
  555. container.Args = []string{
  556. fmt.Sprintf("--new_file_0644=%v", file),
  557. fmt.Sprintf("--file_mode=%v", file),
  558. }
  559. }
  560. func addSubpathVolumeContainer(container *v1.Container, volumeMount v1.VolumeMount) {
  561. existingMounts := container.VolumeMounts
  562. container.VolumeMounts = append(existingMounts, volumeMount)
  563. }
  564. func addMultipleWrites(container *v1.Container, file1 string, file2 string) {
  565. container.Args = []string{
  566. fmt.Sprintf("--new_file_0644=%v", file1),
  567. fmt.Sprintf("--new_file_0666=%v", file2),
  568. }
  569. }
  570. func testMultipleReads(f *framework.Framework, pod *v1.Pod, containerIndex int, file1 string, file2 string) {
  571. ginkgo.By(fmt.Sprintf("Creating pod %s", pod.Name))
  572. removeUnusedContainers(pod)
  573. f.TestContainerOutput("multi_subpath", pod, containerIndex, []string{
  574. "content of file \"" + file1 + "\": mount-tester new file",
  575. "content of file \"" + file2 + "\": mount-tester new file",
  576. })
  577. }
  578. func setReadCommand(file string, container *v1.Container) {
  579. container.Args = []string{
  580. fmt.Sprintf("--file_content_in_loop=%v", file),
  581. fmt.Sprintf("--retry_time=%d", retryDuration),
  582. }
  583. }
  584. func testReadFile(f *framework.Framework, file string, pod *v1.Pod, containerIndex int) {
  585. setReadCommand(file, &pod.Spec.Containers[containerIndex])
  586. ginkgo.By(fmt.Sprintf("Creating pod %s", pod.Name))
  587. removeUnusedContainers(pod)
  588. f.TestContainerOutput("subpath", pod, containerIndex, []string{
  589. "content of file \"" + file + "\": mount-tester new file",
  590. })
  591. ginkgo.By(fmt.Sprintf("Deleting pod %s", pod.Name))
  592. err := framework.DeletePodWithWait(f, f.ClientSet, pod)
  593. framework.ExpectNoError(err, "while deleting pod")
  594. }
  595. func testPodFailSubpath(f *framework.Framework, pod *v1.Pod, allowContainerTerminationError bool) {
  596. testPodFailSubpathError(f, pod, "subPath", allowContainerTerminationError)
  597. }
  598. func testPodFailSubpathError(f *framework.Framework, pod *v1.Pod, errorMsg string, allowContainerTerminationError bool) {
  599. ginkgo.By(fmt.Sprintf("Creating pod %s", pod.Name))
  600. removeUnusedContainers(pod)
  601. pod, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Create(pod)
  602. framework.ExpectNoError(err, "while creating pod")
  603. defer func() {
  604. framework.DeletePodWithWait(f, f.ClientSet, pod)
  605. }()
  606. ginkgo.By("Checking for subpath error in container status")
  607. err = waitForPodSubpathError(f, pod, allowContainerTerminationError)
  608. framework.ExpectNoError(err, "while waiting for subpath failure")
  609. }
  610. func findSubpathContainerName(pod *v1.Pod) string {
  611. for _, container := range pod.Spec.Containers {
  612. for _, mount := range container.VolumeMounts {
  613. if mount.SubPath != "" {
  614. return container.Name
  615. }
  616. }
  617. }
  618. return ""
  619. }
  620. func waitForPodSubpathError(f *framework.Framework, pod *v1.Pod, allowContainerTerminationError bool) error {
  621. subpathContainerName := findSubpathContainerName(pod)
  622. if subpathContainerName == "" {
  623. return fmt.Errorf("failed to find container that uses subpath")
  624. }
  625. return wait.PollImmediate(framework.Poll, framework.PodStartTimeout, func() (bool, error) {
  626. pod, err := f.ClientSet.CoreV1().Pods(pod.Namespace).Get(pod.Name, metav1.GetOptions{})
  627. if err != nil {
  628. return false, err
  629. }
  630. for _, status := range pod.Status.ContainerStatuses {
  631. // 0 is the container that uses subpath
  632. if status.Name == subpathContainerName {
  633. switch {
  634. case status.State.Terminated != nil:
  635. if status.State.Terminated.ExitCode != 0 && allowContainerTerminationError {
  636. return true, nil
  637. }
  638. return false, fmt.Errorf("subpath container unexpectedly terminated")
  639. case status.State.Waiting != nil:
  640. if status.State.Waiting.Reason == "CreateContainerConfigError" &&
  641. strings.Contains(status.State.Waiting.Message, "subPath") {
  642. return true, nil
  643. }
  644. return false, nil
  645. default:
  646. return false, nil
  647. }
  648. }
  649. }
  650. return false, nil
  651. })
  652. }
  653. // Tests that the existing subpath mount is detected when a container restarts
  654. func testPodContainerRestart(f *framework.Framework, pod *v1.Pod) {
  655. pod.Spec.RestartPolicy = v1.RestartPolicyOnFailure
  656. pod.Spec.Containers[0].Image = volume.GetTestImage(imageutils.GetE2EImage(imageutils.BusyBox))
  657. pod.Spec.Containers[0].Command = volume.GenerateScriptCmd("sleep 100000")
  658. pod.Spec.Containers[1].Image = volume.GetTestImage(imageutils.GetE2EImage(imageutils.BusyBox))
  659. pod.Spec.Containers[1].Command = volume.GenerateScriptCmd("sleep 100000")
  660. // Add liveness probe to subpath container
  661. pod.Spec.Containers[0].LivenessProbe = &v1.Probe{
  662. Handler: v1.Handler{
  663. Exec: &v1.ExecAction{
  664. Command: []string{"cat", probeFilePath},
  665. },
  666. },
  667. InitialDelaySeconds: 1,
  668. FailureThreshold: 1,
  669. PeriodSeconds: 2,
  670. }
  671. // Start pod
  672. ginkgo.By(fmt.Sprintf("Creating pod %s", pod.Name))
  673. removeUnusedContainers(pod)
  674. pod, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Create(pod)
  675. framework.ExpectNoError(err, "while creating pod")
  676. defer func() {
  677. framework.DeletePodWithWait(f, f.ClientSet, pod)
  678. }()
  679. err = framework.WaitForPodRunningInNamespace(f.ClientSet, pod)
  680. framework.ExpectNoError(err, "while waiting for pod to be running")
  681. ginkgo.By("Failing liveness probe")
  682. out, err := podContainerExec(pod, 1, fmt.Sprintf("rm %v", probeFilePath))
  683. e2elog.Logf("Pod exec output: %v", out)
  684. framework.ExpectNoError(err, "while failing liveness probe")
  685. // Check that container has restarted
  686. ginkgo.By("Waiting for container to restart")
  687. restarts := int32(0)
  688. err = wait.PollImmediate(10*time.Second, 2*time.Minute, func() (bool, error) {
  689. pod, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Get(pod.Name, metav1.GetOptions{})
  690. if err != nil {
  691. return false, err
  692. }
  693. for _, status := range pod.Status.ContainerStatuses {
  694. if status.Name == pod.Spec.Containers[0].Name {
  695. e2elog.Logf("Container %v, restarts: %v", status.Name, status.RestartCount)
  696. restarts = status.RestartCount
  697. if restarts > 0 {
  698. e2elog.Logf("Container has restart count: %v", restarts)
  699. return true, nil
  700. }
  701. }
  702. }
  703. return false, nil
  704. })
  705. framework.ExpectNoError(err, "while waiting for container to restart")
  706. // Fix liveness probe
  707. ginkgo.By("Rewriting the file")
  708. var writeCmd string
  709. if framework.NodeOSDistroIs("windows") {
  710. writeCmd = fmt.Sprintf("echo test-after | Out-File -FilePath %v", probeFilePath)
  711. } else {
  712. writeCmd = fmt.Sprintf("echo test-after > %v", probeFilePath)
  713. }
  714. out, err = podContainerExec(pod, 1, writeCmd)
  715. e2elog.Logf("Pod exec output: %v", out)
  716. framework.ExpectNoError(err, "while rewriting the probe file")
  717. // Wait for container restarts to stabilize
  718. ginkgo.By("Waiting for container to stop restarting")
  719. stableCount := int(0)
  720. stableThreshold := int(time.Minute / framework.Poll)
  721. err = wait.PollImmediate(framework.Poll, 2*time.Minute, func() (bool, error) {
  722. pod, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Get(pod.Name, metav1.GetOptions{})
  723. if err != nil {
  724. return false, err
  725. }
  726. for _, status := range pod.Status.ContainerStatuses {
  727. if status.Name == pod.Spec.Containers[0].Name {
  728. if status.RestartCount == restarts {
  729. stableCount++
  730. if stableCount > stableThreshold {
  731. e2elog.Logf("Container restart has stabilized")
  732. return true, nil
  733. }
  734. } else {
  735. restarts = status.RestartCount
  736. stableCount = 0
  737. e2elog.Logf("Container has restart count: %v", restarts)
  738. }
  739. break
  740. }
  741. }
  742. return false, nil
  743. })
  744. framework.ExpectNoError(err, "while waiting for container to stabilize")
  745. }
  746. func testSubpathReconstruction(f *framework.Framework, pod *v1.Pod, forceDelete bool) {
  747. // This is mostly copied from TestVolumeUnmountsFromDeletedPodWithForceOption()
  748. // Change to busybox
  749. pod.Spec.Containers[0].Image = volume.GetTestImage(imageutils.GetE2EImage(imageutils.BusyBox))
  750. pod.Spec.Containers[0].Command = volume.GenerateScriptCmd("sleep 100000")
  751. pod.Spec.Containers[1].Image = volume.GetTestImage(imageutils.GetE2EImage(imageutils.BusyBox))
  752. pod.Spec.Containers[1].Command = volume.GenerateScriptCmd("sleep 100000")
  753. // If grace period is too short, then there is not enough time for the volume
  754. // manager to cleanup the volumes
  755. gracePeriod := int64(30)
  756. pod.Spec.TerminationGracePeriodSeconds = &gracePeriod
  757. ginkgo.By(fmt.Sprintf("Creating pod %s", pod.Name))
  758. removeUnusedContainers(pod)
  759. pod, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Create(pod)
  760. framework.ExpectNoError(err, "while creating pod")
  761. err = framework.WaitForPodRunningInNamespace(f.ClientSet, pod)
  762. framework.ExpectNoError(err, "while waiting for pod to be running")
  763. pod, err = f.ClientSet.CoreV1().Pods(f.Namespace.Name).Get(pod.Name, metav1.GetOptions{})
  764. framework.ExpectNoError(err, "while getting pod")
  765. utils.TestVolumeUnmountsFromDeletedPodWithForceOption(f.ClientSet, f, pod, forceDelete, true)
  766. }
  767. func formatVolume(f *framework.Framework, pod *v1.Pod) {
  768. ginkgo.By(fmt.Sprintf("Creating pod to format volume %s", pod.Name))
  769. pod, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Create(pod)
  770. framework.ExpectNoError(err, "while creating volume init pod")
  771. err = framework.WaitForPodSuccessInNamespace(f.ClientSet, pod.Name, pod.Namespace)
  772. framework.ExpectNoError(err, "while waiting for volume init pod to succeed")
  773. err = framework.DeletePodWithWait(f, f.ClientSet, pod)
  774. framework.ExpectNoError(err, "while deleting volume init pod")
  775. }
  776. func podContainerExec(pod *v1.Pod, containerIndex int, command string) (string, error) {
  777. var shell string
  778. var option string
  779. if framework.NodeOSDistroIs("windows") {
  780. shell = "powershell"
  781. option = "/c"
  782. } else {
  783. shell = "/bin/sh"
  784. option = "-c"
  785. }
  786. return framework.RunKubectl("exec", fmt.Sprintf("--namespace=%s", pod.Namespace), pod.Name, "--container", pod.Spec.Containers[containerIndex].Name, "--", shell, option, command)
  787. }