nfs_persistent_volume-disruptive.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. /*
  2. Copyright 2016 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package storage
  14. import (
  15. "context"
  16. "fmt"
  17. "net"
  18. "time"
  19. "github.com/onsi/ginkgo"
  20. "github.com/onsi/gomega"
  21. v1 "k8s.io/api/core/v1"
  22. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  23. "k8s.io/apimachinery/pkg/labels"
  24. utilerrors "k8s.io/apimachinery/pkg/util/errors"
  25. clientset "k8s.io/client-go/kubernetes"
  26. "k8s.io/kubernetes/test/e2e/framework"
  27. e2enode "k8s.io/kubernetes/test/e2e/framework/node"
  28. e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
  29. e2epv "k8s.io/kubernetes/test/e2e/framework/pv"
  30. e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
  31. e2essh "k8s.io/kubernetes/test/e2e/framework/ssh"
  32. "k8s.io/kubernetes/test/e2e/framework/volume"
  33. "k8s.io/kubernetes/test/e2e/storage/utils"
  34. )
  35. type testBody func(c clientset.Interface, f *framework.Framework, clientPod *v1.Pod)
  36. type disruptiveTest struct {
  37. testItStmt string
  38. runTest testBody
  39. }
  40. // checkForControllerManagerHealthy checks that the controller manager does not crash within "duration"
  41. func checkForControllerManagerHealthy(duration time.Duration) error {
  42. var PID string
  43. cmd := "pidof kube-controller-manager"
  44. for start := time.Now(); time.Since(start) < duration; time.Sleep(5 * time.Second) {
  45. result, err := e2essh.SSH(cmd, net.JoinHostPort(framework.GetMasterHost(), sshPort), framework.TestContext.Provider)
  46. if err != nil {
  47. // We don't necessarily know that it crashed, pipe could just be broken
  48. e2essh.LogResult(result)
  49. return fmt.Errorf("master unreachable after %v", time.Since(start))
  50. } else if result.Code != 0 {
  51. e2essh.LogResult(result)
  52. return fmt.Errorf("SSH result code not 0. actually: %v after %v", result.Code, time.Since(start))
  53. } else if result.Stdout != PID {
  54. if PID == "" {
  55. PID = result.Stdout
  56. } else {
  57. //its dead
  58. return fmt.Errorf("controller manager crashed, old PID: %s, new PID: %s", PID, result.Stdout)
  59. }
  60. } else {
  61. framework.Logf("kube-controller-manager still healthy after %v", time.Since(start))
  62. }
  63. }
  64. return nil
  65. }
  66. var _ = utils.SIGDescribe("NFSPersistentVolumes[Disruptive][Flaky]", func() {
  67. f := framework.NewDefaultFramework("disruptive-pv")
  68. var (
  69. c clientset.Interface
  70. ns string
  71. nfsServerPod *v1.Pod
  72. nfsPVconfig e2epv.PersistentVolumeConfig
  73. pvcConfig e2epv.PersistentVolumeClaimConfig
  74. nfsServerIP, clientNodeIP string
  75. clientNode *v1.Node
  76. volLabel labels.Set
  77. selector *metav1.LabelSelector
  78. )
  79. ginkgo.BeforeEach(func() {
  80. // To protect the NFS volume pod from the kubelet restart, we isolate it on its own node.
  81. e2eskipper.SkipUnlessNodeCountIsAtLeast(minNodes)
  82. e2eskipper.SkipIfProviderIs("local")
  83. c = f.ClientSet
  84. ns = f.Namespace.Name
  85. volLabel = labels.Set{e2epv.VolumeSelectorKey: ns}
  86. selector = metav1.SetAsLabelSelector(volLabel)
  87. // Start the NFS server pod.
  88. _, nfsServerPod, nfsServerIP = volume.NewNFSServer(c, ns, []string{"-G", "777", "/exports"})
  89. nfsPVconfig = e2epv.PersistentVolumeConfig{
  90. NamePrefix: "nfs-",
  91. Labels: volLabel,
  92. PVSource: v1.PersistentVolumeSource{
  93. NFS: &v1.NFSVolumeSource{
  94. Server: nfsServerIP,
  95. Path: "/exports",
  96. ReadOnly: false,
  97. },
  98. },
  99. }
  100. emptyStorageClass := ""
  101. pvcConfig = e2epv.PersistentVolumeClaimConfig{
  102. Selector: selector,
  103. StorageClassName: &emptyStorageClass,
  104. }
  105. // Get the first ready node IP that is not hosting the NFS pod.
  106. if clientNodeIP == "" {
  107. framework.Logf("Designating test node")
  108. nodes, err := e2enode.GetReadySchedulableNodes(c)
  109. framework.ExpectNoError(err)
  110. for _, node := range nodes.Items {
  111. if node.Name != nfsServerPod.Spec.NodeName {
  112. clientNode = &node
  113. clientNodeIP, err = e2enode.GetExternalIP(clientNode)
  114. framework.ExpectNoError(err)
  115. break
  116. }
  117. }
  118. gomega.Expect(clientNodeIP).NotTo(gomega.BeEmpty())
  119. }
  120. })
  121. ginkgo.AfterEach(func() {
  122. e2epod.DeletePodWithWait(c, nfsServerPod)
  123. })
  124. ginkgo.Context("when kube-controller-manager restarts", func() {
  125. var (
  126. diskName1, diskName2 string
  127. err error
  128. pvConfig1, pvConfig2 e2epv.PersistentVolumeConfig
  129. pv1, pv2 *v1.PersistentVolume
  130. pvSource1, pvSource2 *v1.PersistentVolumeSource
  131. pvc1, pvc2 *v1.PersistentVolumeClaim
  132. clientPod *v1.Pod
  133. )
  134. ginkgo.BeforeEach(func() {
  135. e2eskipper.SkipUnlessProviderIs("gce")
  136. e2eskipper.SkipUnlessSSHKeyPresent()
  137. ginkgo.By("Initializing first PD with PVPVC binding")
  138. pvSource1, diskName1 = createGCEVolume()
  139. framework.ExpectNoError(err)
  140. pvConfig1 = e2epv.PersistentVolumeConfig{
  141. NamePrefix: "gce-",
  142. Labels: volLabel,
  143. PVSource: *pvSource1,
  144. Prebind: nil,
  145. }
  146. pv1, pvc1, err = e2epv.CreatePVPVC(c, pvConfig1, pvcConfig, ns, false)
  147. framework.ExpectNoError(err)
  148. framework.ExpectNoError(e2epv.WaitOnPVandPVC(c, ns, pv1, pvc1))
  149. ginkgo.By("Initializing second PD with PVPVC binding")
  150. pvSource2, diskName2 = createGCEVolume()
  151. framework.ExpectNoError(err)
  152. pvConfig2 = e2epv.PersistentVolumeConfig{
  153. NamePrefix: "gce-",
  154. Labels: volLabel,
  155. PVSource: *pvSource2,
  156. Prebind: nil,
  157. }
  158. pv2, pvc2, err = e2epv.CreatePVPVC(c, pvConfig2, pvcConfig, ns, false)
  159. framework.ExpectNoError(err)
  160. framework.ExpectNoError(e2epv.WaitOnPVandPVC(c, ns, pv2, pvc2))
  161. ginkgo.By("Attaching both PVC's to a single pod")
  162. clientPod, err = e2epod.CreatePod(c, ns, nil, []*v1.PersistentVolumeClaim{pvc1, pvc2}, true, "")
  163. framework.ExpectNoError(err)
  164. })
  165. ginkgo.AfterEach(func() {
  166. // Delete client/user pod first
  167. framework.ExpectNoError(e2epod.DeletePodWithWait(c, clientPod))
  168. // Delete PV and PVCs
  169. if errs := e2epv.PVPVCCleanup(c, ns, pv1, pvc1); len(errs) > 0 {
  170. framework.Failf("AfterEach: Failed to delete PVC and/or PV. Errors: %v", utilerrors.NewAggregate(errs))
  171. }
  172. pv1, pvc1 = nil, nil
  173. if errs := e2epv.PVPVCCleanup(c, ns, pv2, pvc2); len(errs) > 0 {
  174. framework.Failf("AfterEach: Failed to delete PVC and/or PV. Errors: %v", utilerrors.NewAggregate(errs))
  175. }
  176. pv2, pvc2 = nil, nil
  177. // Delete the actual disks
  178. if diskName1 != "" {
  179. framework.ExpectNoError(e2epv.DeletePDWithRetry(diskName1))
  180. }
  181. if diskName2 != "" {
  182. framework.ExpectNoError(e2epv.DeletePDWithRetry(diskName2))
  183. }
  184. })
  185. ginkgo.It("should delete a bound PVC from a clientPod, restart the kube-control-manager, and ensure the kube-controller-manager does not crash", func() {
  186. e2eskipper.SkipUnlessSSHKeyPresent()
  187. ginkgo.By("Deleting PVC for volume 2")
  188. err = e2epv.DeletePersistentVolumeClaim(c, pvc2.Name, ns)
  189. framework.ExpectNoError(err)
  190. pvc2 = nil
  191. ginkgo.By("Restarting the kube-controller-manager")
  192. err = framework.RestartControllerManager()
  193. framework.ExpectNoError(err)
  194. err = framework.WaitForControllerManagerUp()
  195. framework.ExpectNoError(err)
  196. framework.Logf("kube-controller-manager restarted")
  197. ginkgo.By("Observing the kube-controller-manager healthy for at least 2 minutes")
  198. // Continue checking for 2 minutes to make sure kube-controller-manager is healthy
  199. err = checkForControllerManagerHealthy(2 * time.Minute)
  200. framework.ExpectNoError(err)
  201. })
  202. })
  203. ginkgo.Context("when kubelet restarts", func() {
  204. var (
  205. clientPod *v1.Pod
  206. pv *v1.PersistentVolume
  207. pvc *v1.PersistentVolumeClaim
  208. )
  209. ginkgo.BeforeEach(func() {
  210. framework.Logf("Initializing test spec")
  211. clientPod, pv, pvc = initTestCase(f, c, nfsPVconfig, pvcConfig, ns, clientNode.Name)
  212. })
  213. ginkgo.AfterEach(func() {
  214. framework.Logf("Tearing down test spec")
  215. tearDownTestCase(c, f, ns, clientPod, pvc, pv, true /* force PV delete */)
  216. pv, pvc, clientPod = nil, nil, nil
  217. })
  218. // Test table housing the ginkgo.It() title string and test spec. runTest is type testBody, defined at
  219. // the start of this file. To add tests, define a function mirroring the testBody signature and assign
  220. // to runTest.
  221. disruptiveTestTable := []disruptiveTest{
  222. {
  223. testItStmt: "Should test that a file written to the mount before kubelet restart is readable after restart.",
  224. runTest: utils.TestKubeletRestartsAndRestoresMount,
  225. },
  226. {
  227. testItStmt: "Should test that a volume mounted to a pod that is deleted while the kubelet is down unmounts when the kubelet returns.",
  228. runTest: utils.TestVolumeUnmountsFromDeletedPod,
  229. },
  230. {
  231. testItStmt: "Should test that a volume mounted to a pod that is force deleted while the kubelet is down unmounts when the kubelet returns.",
  232. runTest: utils.TestVolumeUnmountsFromForceDeletedPod,
  233. },
  234. }
  235. // Test loop executes each disruptiveTest iteratively.
  236. for _, test := range disruptiveTestTable {
  237. func(t disruptiveTest) {
  238. ginkgo.It(t.testItStmt, func() {
  239. ginkgo.By("Executing Spec")
  240. t.runTest(c, f, clientPod)
  241. })
  242. }(test)
  243. }
  244. })
  245. })
  246. // createGCEVolume creates PersistentVolumeSource for GCEVolume.
  247. func createGCEVolume() (*v1.PersistentVolumeSource, string) {
  248. diskName, err := e2epv.CreatePDWithRetry()
  249. framework.ExpectNoError(err)
  250. return &v1.PersistentVolumeSource{
  251. GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{
  252. PDName: diskName,
  253. FSType: "ext3",
  254. ReadOnly: false,
  255. },
  256. }, diskName
  257. }
  258. // initTestCase initializes spec resources (pv, pvc, and pod) and returns pointers to be consumed
  259. // by the test.
  260. func initTestCase(f *framework.Framework, c clientset.Interface, pvConfig e2epv.PersistentVolumeConfig, pvcConfig e2epv.PersistentVolumeClaimConfig, ns, nodeName string) (*v1.Pod, *v1.PersistentVolume, *v1.PersistentVolumeClaim) {
  261. pv, pvc, err := e2epv.CreatePVPVC(c, pvConfig, pvcConfig, ns, false)
  262. defer func() {
  263. if err != nil {
  264. e2epv.DeletePersistentVolumeClaim(c, pvc.Name, ns)
  265. e2epv.DeletePersistentVolume(c, pv.Name)
  266. }
  267. }()
  268. framework.ExpectNoError(err)
  269. pod := e2epod.MakePod(ns, nil, []*v1.PersistentVolumeClaim{pvc}, true, "")
  270. pod.Spec.NodeName = nodeName
  271. framework.Logf("Creating NFS client pod.")
  272. pod, err = c.CoreV1().Pods(ns).Create(context.TODO(), pod, metav1.CreateOptions{})
  273. framework.Logf("NFS client Pod %q created on Node %q", pod.Name, nodeName)
  274. framework.ExpectNoError(err)
  275. defer func() {
  276. if err != nil {
  277. e2epod.DeletePodWithWait(c, pod)
  278. }
  279. }()
  280. err = e2epod.WaitForPodRunningInNamespace(c, pod)
  281. framework.ExpectNoError(err, fmt.Sprintf("Pod %q timed out waiting for phase: Running", pod.Name))
  282. // Return created api objects
  283. pod, err = c.CoreV1().Pods(ns).Get(context.TODO(), pod.Name, metav1.GetOptions{})
  284. framework.ExpectNoError(err)
  285. pvc, err = c.CoreV1().PersistentVolumeClaims(ns).Get(context.TODO(), pvc.Name, metav1.GetOptions{})
  286. framework.ExpectNoError(err)
  287. pv, err = c.CoreV1().PersistentVolumes().Get(context.TODO(), pv.Name, metav1.GetOptions{})
  288. framework.ExpectNoError(err)
  289. return pod, pv, pvc
  290. }
  291. // tearDownTestCase destroy resources created by initTestCase.
  292. func tearDownTestCase(c clientset.Interface, f *framework.Framework, ns string, client *v1.Pod, pvc *v1.PersistentVolumeClaim, pv *v1.PersistentVolume, forceDeletePV bool) {
  293. // Ignore deletion errors. Failing on them will interrupt test cleanup.
  294. e2epod.DeletePodWithWait(c, client)
  295. e2epv.DeletePersistentVolumeClaim(c, pvc.Name, ns)
  296. if forceDeletePV && pv != nil {
  297. e2epv.DeletePersistentVolume(c, pv.Name)
  298. return
  299. }
  300. err := framework.WaitForPersistentVolumeDeleted(c, pv.Name, 5*time.Second, 5*time.Minute)
  301. framework.ExpectNoError(err, "Persistent Volume %v not deleted by dynamic provisioner", pv.Name)
  302. }