pd.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652
  1. /*
  2. Copyright 2015 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. "fmt"
  16. mathrand "math/rand"
  17. "strings"
  18. "time"
  19. "google.golang.org/api/googleapi"
  20. "github.com/aws/aws-sdk-go/aws"
  21. "github.com/aws/aws-sdk-go/aws/session"
  22. "github.com/aws/aws-sdk-go/service/ec2"
  23. "github.com/onsi/ginkgo"
  24. "github.com/onsi/gomega"
  25. v1 "k8s.io/api/core/v1"
  26. policy "k8s.io/api/policy/v1beta1"
  27. "k8s.io/apimachinery/pkg/api/resource"
  28. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  29. "k8s.io/apimachinery/pkg/types"
  30. "k8s.io/apimachinery/pkg/util/uuid"
  31. "k8s.io/apimachinery/pkg/util/wait"
  32. clientset "k8s.io/client-go/kubernetes"
  33. v1core "k8s.io/client-go/kubernetes/typed/core/v1"
  34. "k8s.io/kubernetes/test/e2e/framework"
  35. e2elog "k8s.io/kubernetes/test/e2e/framework/log"
  36. "k8s.io/kubernetes/test/e2e/framework/providers/gce"
  37. "k8s.io/kubernetes/test/e2e/storage/utils"
  38. imageutils "k8s.io/kubernetes/test/utils/image"
  39. )
  40. const (
  41. gcePDDetachTimeout = 10 * time.Minute
  42. gcePDDetachPollTime = 10 * time.Second
  43. nodeStatusTimeout = 10 * time.Minute
  44. nodeStatusPollTime = 1 * time.Second
  45. podEvictTimeout = 2 * time.Minute
  46. maxReadRetry = 3
  47. minNodes = 2
  48. )
  49. var _ = utils.SIGDescribe("Pod Disks", func() {
  50. var (
  51. ns string
  52. cs clientset.Interface
  53. podClient v1core.PodInterface
  54. nodeClient v1core.NodeInterface
  55. host0Name types.NodeName
  56. host1Name types.NodeName
  57. nodes *v1.NodeList
  58. )
  59. f := framework.NewDefaultFramework("pod-disks")
  60. ginkgo.BeforeEach(func() {
  61. framework.SkipUnlessNodeCountIsAtLeast(minNodes)
  62. cs = f.ClientSet
  63. ns = f.Namespace.Name
  64. framework.SkipIfMultizone(cs)
  65. podClient = cs.CoreV1().Pods(ns)
  66. nodeClient = cs.CoreV1().Nodes()
  67. nodes = framework.GetReadySchedulableNodesOrDie(cs)
  68. gomega.Expect(len(nodes.Items)).To(gomega.BeNumerically(">=", minNodes), fmt.Sprintf("Requires at least %d nodes", minNodes))
  69. host0Name = types.NodeName(nodes.Items[0].ObjectMeta.Name)
  70. host1Name = types.NodeName(nodes.Items[1].ObjectMeta.Name)
  71. mathrand.Seed(time.Now().UnixNano())
  72. })
  73. ginkgo.Context("schedule pods each with a PD, delete pod and verify detach [Slow]", func() {
  74. const (
  75. podDefaultGrace = "default (30s)"
  76. podImmediateGrace = "immediate (0s)"
  77. )
  78. var readOnlyMap = map[bool]string{
  79. true: "read-only",
  80. false: "RW",
  81. }
  82. type testT struct {
  83. descr string // It description
  84. readOnly bool // true means pd is read-only
  85. deleteOpt *metav1.DeleteOptions // pod delete option
  86. }
  87. tests := []testT{
  88. {
  89. descr: podImmediateGrace,
  90. readOnly: false,
  91. deleteOpt: metav1.NewDeleteOptions(0),
  92. },
  93. {
  94. descr: podDefaultGrace,
  95. readOnly: false,
  96. deleteOpt: &metav1.DeleteOptions{},
  97. },
  98. {
  99. descr: podImmediateGrace,
  100. readOnly: true,
  101. deleteOpt: metav1.NewDeleteOptions(0),
  102. },
  103. {
  104. descr: podDefaultGrace,
  105. readOnly: true,
  106. deleteOpt: &metav1.DeleteOptions{},
  107. },
  108. }
  109. for _, t := range tests {
  110. podDelOpt := t.deleteOpt
  111. readOnly := t.readOnly
  112. readOnlyTxt := readOnlyMap[readOnly]
  113. ginkgo.It(fmt.Sprintf("for %s PD with pod delete grace period of %q", readOnlyTxt, t.descr), func() {
  114. framework.SkipUnlessProviderIs("gce", "gke", "aws")
  115. if readOnly {
  116. framework.SkipIfProviderIs("aws")
  117. }
  118. ginkgo.By("creating PD")
  119. diskName, err := framework.CreatePDWithRetry()
  120. framework.ExpectNoError(err, "Error creating PD")
  121. var fmtPod *v1.Pod
  122. if readOnly {
  123. // if all test pods are RO then need a RW pod to format pd
  124. ginkgo.By("creating RW fmt Pod to ensure PD is formatted")
  125. fmtPod = testPDPod([]string{diskName}, host0Name, false, 1)
  126. _, err = podClient.Create(fmtPod)
  127. framework.ExpectNoError(err, "Failed to create fmtPod")
  128. framework.ExpectNoError(f.WaitForPodRunningSlow(fmtPod.Name))
  129. ginkgo.By("deleting the fmtPod")
  130. framework.ExpectNoError(podClient.Delete(fmtPod.Name, metav1.NewDeleteOptions(0)), "Failed to delete fmtPod")
  131. e2elog.Logf("deleted fmtPod %q", fmtPod.Name)
  132. ginkgo.By("waiting for PD to detach")
  133. framework.ExpectNoError(waitForPDDetach(diskName, host0Name))
  134. }
  135. // prepare to create two test pods on separate nodes
  136. host0Pod := testPDPod([]string{diskName}, host0Name, readOnly, 1)
  137. host1Pod := testPDPod([]string{diskName}, host1Name, readOnly, 1)
  138. defer func() {
  139. // Teardown should do nothing unless test failed
  140. ginkgo.By("defer: cleaning up PD-RW test environment")
  141. e2elog.Logf("defer cleanup errors can usually be ignored")
  142. if fmtPod != nil {
  143. podClient.Delete(fmtPod.Name, podDelOpt)
  144. }
  145. podClient.Delete(host0Pod.Name, podDelOpt)
  146. podClient.Delete(host1Pod.Name, podDelOpt)
  147. detachAndDeletePDs(diskName, []types.NodeName{host0Name, host1Name})
  148. }()
  149. ginkgo.By("creating host0Pod on node0")
  150. _, err = podClient.Create(host0Pod)
  151. framework.ExpectNoError(err, fmt.Sprintf("Failed to create host0Pod: %v", err))
  152. framework.ExpectNoError(f.WaitForPodRunningSlow(host0Pod.Name))
  153. e2elog.Logf("host0Pod: %q, node0: %q", host0Pod.Name, host0Name)
  154. var containerName, testFile, testFileContents string
  155. if !readOnly {
  156. ginkgo.By("writing content to host0Pod on node0")
  157. containerName = "mycontainer"
  158. testFile = "/testpd1/tracker"
  159. testFileContents = fmt.Sprintf("%v", mathrand.Int())
  160. framework.ExpectNoError(f.WriteFileViaContainer(host0Pod.Name, containerName, testFile, testFileContents))
  161. e2elog.Logf("wrote %q to file %q in pod %q on node %q", testFileContents, testFile, host0Pod.Name, host0Name)
  162. ginkgo.By("verifying PD is present in node0's VolumeInUse list")
  163. framework.ExpectNoError(waitForPDInVolumesInUse(nodeClient, diskName, host0Name, nodeStatusTimeout, true /* shouldExist */))
  164. ginkgo.By("deleting host0Pod") // delete this pod before creating next pod
  165. framework.ExpectNoError(podClient.Delete(host0Pod.Name, podDelOpt), "Failed to delete host0Pod")
  166. e2elog.Logf("deleted host0Pod %q", host0Pod.Name)
  167. }
  168. ginkgo.By("creating host1Pod on node1")
  169. _, err = podClient.Create(host1Pod)
  170. framework.ExpectNoError(err, "Failed to create host1Pod")
  171. framework.ExpectNoError(f.WaitForPodRunningSlow(host1Pod.Name))
  172. e2elog.Logf("host1Pod: %q, node1: %q", host1Pod.Name, host1Name)
  173. if readOnly {
  174. ginkgo.By("deleting host0Pod")
  175. framework.ExpectNoError(podClient.Delete(host0Pod.Name, podDelOpt), "Failed to delete host0Pod")
  176. e2elog.Logf("deleted host0Pod %q", host0Pod.Name)
  177. } else {
  178. ginkgo.By("verifying PD contents in host1Pod")
  179. verifyPDContentsViaContainer(f, host1Pod.Name, containerName, map[string]string{testFile: testFileContents})
  180. e2elog.Logf("verified PD contents in pod %q", host1Pod.Name)
  181. ginkgo.By("verifying PD is removed from node0")
  182. framework.ExpectNoError(waitForPDInVolumesInUse(nodeClient, diskName, host0Name, nodeStatusTimeout, false /* shouldExist */))
  183. e2elog.Logf("PD %q removed from node %q's VolumeInUse list", diskName, host1Pod.Name)
  184. }
  185. ginkgo.By("deleting host1Pod")
  186. framework.ExpectNoError(podClient.Delete(host1Pod.Name, podDelOpt), "Failed to delete host1Pod")
  187. e2elog.Logf("deleted host1Pod %q", host1Pod.Name)
  188. ginkgo.By("Test completed successfully, waiting for PD to detach from both nodes")
  189. waitForPDDetach(diskName, host0Name)
  190. waitForPDDetach(diskName, host1Name)
  191. })
  192. }
  193. })
  194. ginkgo.Context("schedule a pod w/ RW PD(s) mounted to 1 or more containers, write to PD, verify content, delete pod, and repeat in rapid succession [Slow]", func() {
  195. type testT struct {
  196. numContainers int
  197. numPDs int
  198. repeatCnt int
  199. }
  200. tests := []testT{
  201. {
  202. numContainers: 4,
  203. numPDs: 1,
  204. repeatCnt: 3,
  205. },
  206. {
  207. numContainers: 1,
  208. numPDs: 2,
  209. repeatCnt: 3,
  210. },
  211. }
  212. for _, t := range tests {
  213. numPDs := t.numPDs
  214. numContainers := t.numContainers
  215. ginkgo.It(fmt.Sprintf("using %d containers and %d PDs", numContainers, numPDs), func() {
  216. framework.SkipUnlessProviderIs("gce", "gke", "aws")
  217. var host0Pod *v1.Pod
  218. var err error
  219. fileAndContentToVerify := make(map[string]string)
  220. diskNames := make([]string, 0, numPDs)
  221. ginkgo.By(fmt.Sprintf("creating %d PD(s)", numPDs))
  222. for i := 0; i < numPDs; i++ {
  223. name, err := framework.CreatePDWithRetry()
  224. framework.ExpectNoError(err, fmt.Sprintf("Error creating PD %d", i))
  225. diskNames = append(diskNames, name)
  226. }
  227. defer func() {
  228. // Teardown should do nothing unless test failed.
  229. ginkgo.By("defer: cleaning up PD-RW test environment")
  230. e2elog.Logf("defer cleanup errors can usually be ignored")
  231. if host0Pod != nil {
  232. podClient.Delete(host0Pod.Name, metav1.NewDeleteOptions(0))
  233. }
  234. for _, diskName := range diskNames {
  235. detachAndDeletePDs(diskName, []types.NodeName{host0Name})
  236. }
  237. }()
  238. for i := 0; i < t.repeatCnt; i++ { // "rapid" repeat loop
  239. e2elog.Logf("PD Read/Writer Iteration #%v", i)
  240. ginkgo.By(fmt.Sprintf("creating host0Pod with %d containers on node0", numContainers))
  241. host0Pod = testPDPod(diskNames, host0Name, false /* readOnly */, numContainers)
  242. _, err = podClient.Create(host0Pod)
  243. framework.ExpectNoError(err, fmt.Sprintf("Failed to create host0Pod: %v", err))
  244. framework.ExpectNoError(f.WaitForPodRunningSlow(host0Pod.Name))
  245. ginkgo.By(fmt.Sprintf("writing %d file(s) via a container", numPDs))
  246. containerName := "mycontainer"
  247. if numContainers > 1 {
  248. containerName = fmt.Sprintf("mycontainer%v", mathrand.Intn(numContainers)+1)
  249. }
  250. for x := 1; x <= numPDs; x++ {
  251. testFile := fmt.Sprintf("/testpd%d/tracker%d", x, i)
  252. testFileContents := fmt.Sprintf("%v", mathrand.Int())
  253. fileAndContentToVerify[testFile] = testFileContents
  254. framework.ExpectNoError(f.WriteFileViaContainer(host0Pod.Name, containerName, testFile, testFileContents))
  255. e2elog.Logf("wrote %q to file %q in pod %q (container %q) on node %q", testFileContents, testFile, host0Pod.Name, containerName, host0Name)
  256. }
  257. ginkgo.By("verifying PD contents via a container")
  258. if numContainers > 1 {
  259. containerName = fmt.Sprintf("mycontainer%v", mathrand.Intn(numContainers)+1)
  260. }
  261. verifyPDContentsViaContainer(f, host0Pod.Name, containerName, fileAndContentToVerify)
  262. ginkgo.By("deleting host0Pod")
  263. framework.ExpectNoError(podClient.Delete(host0Pod.Name, metav1.NewDeleteOptions(0)), "Failed to delete host0Pod")
  264. }
  265. ginkgo.By(fmt.Sprintf("Test completed successfully, waiting for %d PD(s) to detach from node0", numPDs))
  266. for _, diskName := range diskNames {
  267. waitForPDDetach(diskName, host0Name)
  268. }
  269. })
  270. }
  271. })
  272. ginkgo.Context("detach in a disrupted environment [Slow] [Disruptive]", func() {
  273. const (
  274. deleteNode = 1 // delete physical node
  275. deleteNodeObj = 2 // delete node's api object only
  276. evictPod = 3 // evict host0Pod on node0
  277. )
  278. type testT struct {
  279. descr string // It description
  280. disruptOp int // disruptive operation performed on target node
  281. }
  282. tests := []testT{
  283. {
  284. descr: "node is deleted",
  285. disruptOp: deleteNode,
  286. },
  287. {
  288. descr: "node's API object is deleted",
  289. disruptOp: deleteNodeObj,
  290. },
  291. {
  292. descr: "pod is evicted",
  293. disruptOp: evictPod,
  294. },
  295. }
  296. for _, t := range tests {
  297. disruptOp := t.disruptOp
  298. ginkgo.It(fmt.Sprintf("when %s", t.descr), func() {
  299. framework.SkipUnlessProviderIs("gce")
  300. origNodeCnt := len(nodes.Items) // healhy nodes running kubelet
  301. ginkgo.By("creating a pd")
  302. diskName, err := framework.CreatePDWithRetry()
  303. framework.ExpectNoError(err, "Error creating a pd")
  304. targetNode := &nodes.Items[0] // for node delete ops
  305. host0Pod := testPDPod([]string{diskName}, host0Name, false, 1)
  306. containerName := "mycontainer"
  307. defer func() {
  308. ginkgo.By("defer: cleaning up PD-RW test env")
  309. e2elog.Logf("defer cleanup errors can usually be ignored")
  310. ginkgo.By("defer: delete host0Pod")
  311. podClient.Delete(host0Pod.Name, metav1.NewDeleteOptions(0))
  312. ginkgo.By("defer: detach and delete PDs")
  313. detachAndDeletePDs(diskName, []types.NodeName{host0Name})
  314. if disruptOp == deleteNode || disruptOp == deleteNodeObj {
  315. if disruptOp == deleteNodeObj {
  316. targetNode.ObjectMeta.SetResourceVersion("0")
  317. // need to set the resource version or else the Create() fails
  318. ginkgo.By("defer: re-create host0 node object")
  319. _, err := nodeClient.Create(targetNode)
  320. framework.ExpectNoError(err, fmt.Sprintf("defer: Unable to re-create the deleted node object %q", targetNode.Name))
  321. }
  322. ginkgo.By("defer: verify the number of ready nodes")
  323. numNodes := countReadyNodes(cs, host0Name)
  324. // if this defer is reached due to an Expect then nested
  325. // Expects are lost, so use Failf here
  326. if numNodes != origNodeCnt {
  327. framework.Failf("defer: Requires current node count (%d) to return to original node count (%d)", numNodes, origNodeCnt)
  328. }
  329. }
  330. }()
  331. ginkgo.By("creating host0Pod on node0")
  332. _, err = podClient.Create(host0Pod)
  333. framework.ExpectNoError(err, fmt.Sprintf("Failed to create host0Pod: %v", err))
  334. ginkgo.By("waiting for host0Pod to be running")
  335. framework.ExpectNoError(f.WaitForPodRunningSlow(host0Pod.Name))
  336. ginkgo.By("writing content to host0Pod")
  337. testFile := "/testpd1/tracker"
  338. testFileContents := fmt.Sprintf("%v", mathrand.Int())
  339. framework.ExpectNoError(f.WriteFileViaContainer(host0Pod.Name, containerName, testFile, testFileContents))
  340. e2elog.Logf("wrote %q to file %q in pod %q on node %q", testFileContents, testFile, host0Pod.Name, host0Name)
  341. ginkgo.By("verifying PD is present in node0's VolumeInUse list")
  342. framework.ExpectNoError(waitForPDInVolumesInUse(nodeClient, diskName, host0Name, nodeStatusTimeout, true /* should exist*/))
  343. if disruptOp == deleteNode {
  344. ginkgo.By("getting gce instances")
  345. gceCloud, err := gce.GetGCECloud()
  346. framework.ExpectNoError(err, fmt.Sprintf("Unable to create gcloud client err=%v", err))
  347. output, err := gceCloud.ListInstanceNames(framework.TestContext.CloudConfig.ProjectID, framework.TestContext.CloudConfig.Zone)
  348. framework.ExpectNoError(err, fmt.Sprintf("Unable to get list of node instances err=%v output=%s", err, output))
  349. gomega.Expect(true, strings.Contains(string(output), string(host0Name)))
  350. ginkgo.By("deleting host0")
  351. err = gceCloud.DeleteInstance(framework.TestContext.CloudConfig.ProjectID, framework.TestContext.CloudConfig.Zone, string(host0Name))
  352. framework.ExpectNoError(err, fmt.Sprintf("Failed to delete host0Pod: err=%v", err))
  353. ginkgo.By("expecting host0 node to be re-created")
  354. numNodes := countReadyNodes(cs, host0Name)
  355. gomega.Expect(numNodes).To(gomega.Equal(origNodeCnt), fmt.Sprintf("Requires current node count (%d) to return to original node count (%d)", numNodes, origNodeCnt))
  356. output, err = gceCloud.ListInstanceNames(framework.TestContext.CloudConfig.ProjectID, framework.TestContext.CloudConfig.Zone)
  357. framework.ExpectNoError(err, fmt.Sprintf("Unable to get list of node instances err=%v output=%s", err, output))
  358. gomega.Expect(false, strings.Contains(string(output), string(host0Name)))
  359. } else if disruptOp == deleteNodeObj {
  360. ginkgo.By("deleting host0's node api object")
  361. framework.ExpectNoError(nodeClient.Delete(string(host0Name), metav1.NewDeleteOptions(0)), "Unable to delete host0's node object")
  362. ginkgo.By("deleting host0Pod")
  363. framework.ExpectNoError(podClient.Delete(host0Pod.Name, metav1.NewDeleteOptions(0)), "Unable to delete host0Pod")
  364. } else if disruptOp == evictPod {
  365. evictTarget := &policy.Eviction{
  366. ObjectMeta: metav1.ObjectMeta{
  367. Name: host0Pod.Name,
  368. Namespace: ns,
  369. },
  370. }
  371. ginkgo.By("evicting host0Pod")
  372. err = wait.PollImmediate(framework.Poll, podEvictTimeout, func() (bool, error) {
  373. err = cs.CoreV1().Pods(ns).Evict(evictTarget)
  374. if err != nil {
  375. return false, nil
  376. }
  377. return true, nil
  378. })
  379. framework.ExpectNoError(err, fmt.Sprintf("failed to evict host0Pod after %v", podEvictTimeout))
  380. }
  381. ginkgo.By("waiting for pd to detach from host0")
  382. waitForPDDetach(diskName, host0Name)
  383. })
  384. }
  385. })
  386. ginkgo.It("should be able to delete a non-existent PD without error", func() {
  387. framework.SkipUnlessProviderIs("gce")
  388. ginkgo.By("delete a PD")
  389. framework.ExpectNoError(framework.DeletePDWithRetry("non-exist"))
  390. })
  391. })
  392. func countReadyNodes(c clientset.Interface, hostName types.NodeName) int {
  393. framework.WaitForNodeToBeReady(c, string(hostName), nodeStatusTimeout)
  394. framework.WaitForAllNodesSchedulable(c, nodeStatusTimeout)
  395. nodes := framework.GetReadySchedulableNodesOrDie(c)
  396. return len(nodes.Items)
  397. }
  398. func verifyPDContentsViaContainer(f *framework.Framework, podName, containerName string, fileAndContentToVerify map[string]string) {
  399. for filePath, expectedContents := range fileAndContentToVerify {
  400. var value string
  401. // Add a retry to avoid temporal failure in reading the content
  402. for i := 0; i < maxReadRetry; i++ {
  403. v, err := f.ReadFileViaContainer(podName, containerName, filePath)
  404. value = v
  405. if err != nil {
  406. e2elog.Logf("Error reading file: %v", err)
  407. }
  408. framework.ExpectNoError(err)
  409. e2elog.Logf("Read file %q with content: %v (iteration %d)", filePath, v, i)
  410. if strings.TrimSpace(v) != strings.TrimSpace(expectedContents) {
  411. e2elog.Logf("Warning: read content <%q> does not match execpted content <%q>.", v, expectedContents)
  412. size, err := f.CheckFileSizeViaContainer(podName, containerName, filePath)
  413. if err != nil {
  414. e2elog.Logf("Error checking file size: %v", err)
  415. }
  416. e2elog.Logf("Check file %q size: %q", filePath, size)
  417. } else {
  418. break
  419. }
  420. }
  421. gomega.Expect(strings.TrimSpace(value)).To(gomega.Equal(strings.TrimSpace(expectedContents)))
  422. }
  423. }
  424. func detachPD(nodeName types.NodeName, pdName string) error {
  425. if framework.TestContext.Provider == "gce" || framework.TestContext.Provider == "gke" {
  426. gceCloud, err := gce.GetGCECloud()
  427. if err != nil {
  428. return err
  429. }
  430. err = gceCloud.DetachDisk(pdName, nodeName)
  431. if err != nil {
  432. if gerr, ok := err.(*googleapi.Error); ok && strings.Contains(gerr.Message, "Invalid value for field 'disk'") {
  433. // PD already detached, ignore error.
  434. return nil
  435. }
  436. e2elog.Logf("Error detaching PD %q: %v", pdName, err)
  437. }
  438. return err
  439. } else if framework.TestContext.Provider == "aws" {
  440. client := ec2.New(session.New())
  441. tokens := strings.Split(pdName, "/")
  442. awsVolumeID := tokens[len(tokens)-1]
  443. request := ec2.DetachVolumeInput{
  444. VolumeId: aws.String(awsVolumeID),
  445. }
  446. _, err := client.DetachVolume(&request)
  447. if err != nil {
  448. return fmt.Errorf("error detaching EBS volume: %v", err)
  449. }
  450. return nil
  451. } else {
  452. return fmt.Errorf("Provider does not support volume detaching")
  453. }
  454. }
  455. // Returns pod spec suitable for api Create call. Handles gce, gke and aws providers only and
  456. // escapes if a different provider is supplied.
  457. // The first container name is hard-coded to "mycontainer". Subsequent containers are named:
  458. // "mycontainer<number> where <number> is 1..numContainers. Note if there is only one container it's
  459. // name has no number.
  460. // Container's volumeMounts are hard-coded to "/testpd<number>" where <number> is 1..len(diskNames).
  461. func testPDPod(diskNames []string, targetNode types.NodeName, readOnly bool, numContainers int) *v1.Pod {
  462. // escape if not a supported provider
  463. if !(framework.TestContext.Provider == "gce" || framework.TestContext.Provider == "gke" ||
  464. framework.TestContext.Provider == "aws") {
  465. framework.Failf(fmt.Sprintf("func `testPDPod` only supports gce, gke, and aws providers, not %v", framework.TestContext.Provider))
  466. }
  467. containers := make([]v1.Container, numContainers)
  468. for i := range containers {
  469. containers[i].Name = "mycontainer"
  470. if numContainers > 1 {
  471. containers[i].Name = fmt.Sprintf("mycontainer%v", i+1)
  472. }
  473. containers[i].Image = imageutils.GetE2EImage(imageutils.BusyBox)
  474. containers[i].Command = []string{"sleep", "6000"}
  475. containers[i].VolumeMounts = make([]v1.VolumeMount, len(diskNames))
  476. for k := range diskNames {
  477. containers[i].VolumeMounts[k].Name = fmt.Sprintf("testpd%v", k+1)
  478. containers[i].VolumeMounts[k].MountPath = fmt.Sprintf("/testpd%v", k+1)
  479. }
  480. containers[i].Resources.Limits = v1.ResourceList{}
  481. containers[i].Resources.Limits[v1.ResourceCPU] = *resource.NewQuantity(int64(0), resource.DecimalSI)
  482. }
  483. pod := &v1.Pod{
  484. TypeMeta: metav1.TypeMeta{
  485. Kind: "Pod",
  486. APIVersion: "v1",
  487. },
  488. ObjectMeta: metav1.ObjectMeta{
  489. Name: "pd-test-" + string(uuid.NewUUID()),
  490. },
  491. Spec: v1.PodSpec{
  492. Containers: containers,
  493. NodeName: string(targetNode),
  494. },
  495. }
  496. pod.Spec.Volumes = make([]v1.Volume, len(diskNames))
  497. for k, diskName := range diskNames {
  498. pod.Spec.Volumes[k].Name = fmt.Sprintf("testpd%v", k+1)
  499. if framework.TestContext.Provider == "aws" {
  500. pod.Spec.Volumes[k].VolumeSource = v1.VolumeSource{
  501. AWSElasticBlockStore: &v1.AWSElasticBlockStoreVolumeSource{
  502. VolumeID: diskName,
  503. FSType: "ext4",
  504. ReadOnly: readOnly,
  505. },
  506. }
  507. } else { // "gce" or "gke"
  508. pod.Spec.Volumes[k].VolumeSource = v1.VolumeSource{
  509. GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{
  510. PDName: diskName,
  511. FSType: "ext4",
  512. ReadOnly: readOnly,
  513. },
  514. }
  515. }
  516. }
  517. return pod
  518. }
  519. // Waits for specified PD to detach from specified hostName
  520. func waitForPDDetach(diskName string, nodeName types.NodeName) error {
  521. if framework.TestContext.Provider == "gce" || framework.TestContext.Provider == "gke" {
  522. e2elog.Logf("Waiting for GCE PD %q to detach from node %q.", diskName, nodeName)
  523. gceCloud, err := gce.GetGCECloud()
  524. if err != nil {
  525. return err
  526. }
  527. for start := time.Now(); time.Since(start) < gcePDDetachTimeout; time.Sleep(gcePDDetachPollTime) {
  528. diskAttached, err := gceCloud.DiskIsAttached(diskName, nodeName)
  529. if err != nil {
  530. e2elog.Logf("Error waiting for PD %q to detach from node %q. 'DiskIsAttached(...)' failed with %v", diskName, nodeName, err)
  531. return err
  532. }
  533. if !diskAttached {
  534. // Specified disk does not appear to be attached to specified node
  535. e2elog.Logf("GCE PD %q appears to have successfully detached from %q.", diskName, nodeName)
  536. return nil
  537. }
  538. e2elog.Logf("Waiting for GCE PD %q to detach from %q.", diskName, nodeName)
  539. }
  540. return fmt.Errorf("Gave up waiting for GCE PD %q to detach from %q after %v", diskName, nodeName, gcePDDetachTimeout)
  541. }
  542. return nil
  543. }
  544. func detachAndDeletePDs(diskName string, hosts []types.NodeName) {
  545. for _, host := range hosts {
  546. e2elog.Logf("Detaching GCE PD %q from node %q.", diskName, host)
  547. detachPD(host, diskName)
  548. ginkgo.By(fmt.Sprintf("Waiting for PD %q to detach from %q", diskName, host))
  549. waitForPDDetach(diskName, host)
  550. }
  551. ginkgo.By(fmt.Sprintf("Deleting PD %q", diskName))
  552. framework.ExpectNoError(framework.DeletePDWithRetry(diskName))
  553. }
  554. func waitForPDInVolumesInUse(
  555. nodeClient v1core.NodeInterface,
  556. diskName string,
  557. nodeName types.NodeName,
  558. timeout time.Duration,
  559. shouldExist bool) error {
  560. logStr := "to contain"
  561. if !shouldExist {
  562. logStr = "to NOT contain"
  563. }
  564. e2elog.Logf("Waiting for node %s's VolumesInUse Status %s PD %q", nodeName, logStr, diskName)
  565. for start := time.Now(); time.Since(start) < timeout; time.Sleep(nodeStatusPollTime) {
  566. nodeObj, err := nodeClient.Get(string(nodeName), metav1.GetOptions{})
  567. if err != nil || nodeObj == nil {
  568. e2elog.Logf("Failed to fetch node object %q from API server. err=%v", nodeName, err)
  569. continue
  570. }
  571. exists := false
  572. for _, volumeInUse := range nodeObj.Status.VolumesInUse {
  573. volumeInUseStr := string(volumeInUse)
  574. if strings.Contains(volumeInUseStr, diskName) {
  575. if shouldExist {
  576. e2elog.Logf("Found PD %q in node %q's VolumesInUse Status: %q", diskName, nodeName, volumeInUseStr)
  577. return nil
  578. }
  579. exists = true
  580. }
  581. }
  582. if !shouldExist && !exists {
  583. e2elog.Logf("Verified PD %q does not exist in node %q's VolumesInUse Status.", diskName, nodeName)
  584. return nil
  585. }
  586. }
  587. return fmt.Errorf("Timed out waiting for node %s VolumesInUse Status %s diskName %q", nodeName, logStr, diskName)
  588. }