k8s.io/kubernetes@v1.29.3/test/e2e/storage/static_pods.go (about) 1 /* 2 Copyright 2023 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package storage 18 19 import ( 20 "context" 21 "fmt" 22 "os" 23 "os/exec" 24 "strings" 25 "time" 26 27 "github.com/onsi/ginkgo/v2" 28 "github.com/onsi/gomega" 29 v1 "k8s.io/api/core/v1" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/kubernetes/test/e2e/feature" 32 "k8s.io/kubernetes/test/e2e/framework" 33 e2epod "k8s.io/kubernetes/test/e2e/framework/pod" 34 e2evolume "k8s.io/kubernetes/test/e2e/framework/volume" 35 "k8s.io/kubernetes/test/e2e/storage/drivers" 36 storageframework "k8s.io/kubernetes/test/e2e/storage/framework" 37 "k8s.io/kubernetes/test/e2e/storage/utils" 38 admissionapi "k8s.io/pod-security-admission/api" 39 ) 40 41 // Tests for static Pods + CSI volume reconstruction. 42 // 43 // NOTE: these tests *require* the cluster under test to be Kubernetes In Docker (kind)! 44 // Kind runs its API server as a static Pod, and we leverage it here 45 // to test kubelet starting without the API server. 46 var _ = utils.SIGDescribe("StaticPods", feature.Kind, func() { 47 f := framework.NewDefaultFramework("static-pods-csi") 48 f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged 49 50 // global vars for the ginkgo.BeforeEach() / It()'s below 51 var ( 52 driver storageframework.DynamicPVTestDriver 53 testConfig *storageframework.PerTestConfig 54 ) 55 56 ginkgo.BeforeEach(func(ctx context.Context) { 57 // Install a CSI driver. Using csi-driver-hospath here. 58 driver = drivers.InitHostPathCSIDriver().(storageframework.DynamicPVTestDriver) 59 testConfig = driver.PrepareTest(ctx, f) 60 }) 61 62 // Test https://github.com/kubernetes/kubernetes/issues/117745 63 // I.e. kubelet starts and it must start the API server as a static pod, 64 // while there is a CSI volume mounted by the previous kubelet. 65 f.It("should run after kubelet stopped with CSI volume mounted", f.WithDisruptive(), f.WithSerial(), func(ctx context.Context) { 66 var timeout int64 = 5 67 68 ginkgo.By("Provision a new CSI volume") 69 res := storageframework.CreateVolumeResource(ctx, driver, testConfig, storageframework.DefaultFsDynamicPV, e2evolume.SizeRange{Min: "1", Max: "1Gi"}) 70 ginkgo.DeferCleanup(func(ctx context.Context) { 71 res.CleanupResource(ctx) 72 }) 73 74 ginkgo.By("Run a pod with the volume") 75 podConfig := &e2epod.Config{ 76 NS: f.Namespace.Name, 77 PVCs: []*v1.PersistentVolumeClaim{res.Pvc}, 78 NodeSelection: testConfig.ClientNodeSelection, 79 } 80 pod, err := e2epod.CreateSecPod(ctx, f.ClientSet, podConfig, f.Timeouts.PodStart) 81 framework.ExpectNoError(err, "Creating a pod with a CSI volume") 82 ginkgo.DeferCleanup(f.ClientSet.CoreV1().Pods(f.Namespace.Name).Delete, pod.Name, metav1.DeleteOptions{}) 83 84 ginkgo.By("Stop the control plane by removing the static pod manifests") 85 err = stopControlPlane(ctx) 86 framework.ExpectNoError(err, "Stopping the control plane") 87 88 ginkgo.By("Wait for the API server to disappear") 89 gomega.Eventually(ctx, func(ctx context.Context) error { 90 _, err := f.ClientSet.StorageV1().StorageClasses().List(ctx, metav1.ListOptions{TimeoutSeconds: &timeout}) 91 return err 92 }).WithTimeout(time.Minute).Should(gomega.Not(gomega.Succeed())) 93 94 ginkgo.By("Stop kubelet") 95 err = stopKindKubelet(ctx) 96 framework.ExpectNoError(err, "Stopping kubelet") 97 98 ginkgo.By("Re-enable control plane") 99 err = enableControlPlane(ctx) 100 framework.ExpectNoError(err, "Re-enabling control plane by restoring the static pod manifests") 101 102 ginkgo.By("Start kubelet") 103 err = startKindKubelet(ctx) 104 framework.ExpectNoError(err, "Starting kubelet") 105 106 ginkgo.By("Wait for a static pod with the API server to start") 107 const apiServerStartTimeout = 10 * time.Minute // It takes up to 2 minutes on my machine 108 gomega.Eventually(ctx, func(ctx context.Context) error { 109 _, err := f.ClientSet.StorageV1().StorageClasses().List(ctx, metav1.ListOptions{TimeoutSeconds: &timeout}) 110 return err 111 }).WithTimeout(apiServerStartTimeout).Should(gomega.Succeed()) 112 }) 113 }) 114 115 func stopKindKubelet(ctx context.Context) error { 116 return kubeletExec("systemctl", "stop", "kubelet") 117 } 118 119 func startKindKubelet(ctx context.Context) error { 120 return kubeletExec("systemctl", "start", "kubelet") 121 } 122 123 func stopControlPlane(ctx context.Context) error { 124 // Move all static pod manifests to a backup dir (incl. KCM, scheduler, API server and etcd) 125 return kubeletExec("sh", "-c", "mkdir -p /etc/kubernetes/manifests-backup && mv /etc/kubernetes/manifests/* /etc/kubernetes/manifests-backup/") 126 } 127 128 func enableControlPlane(ctx context.Context) error { 129 // Restore the static pod manifests from the backup dir. 130 // Using `sh` to get `*` expansion. 131 return kubeletExec("sh", "-c", "mv /etc/kubernetes/manifests-backup/* /etc/kubernetes/manifests/") 132 } 133 134 // Run a command in container with kubelet (and the whole control plane as containers) 135 func kubeletExec(command ...string) error { 136 containerName := getKindContainerName() 137 args := []string{"exec", containerName} 138 args = append(args, command...) 139 cmd := exec.Command("docker", args...) 140 141 out, err := cmd.CombinedOutput() 142 if err != nil { 143 return fmt.Errorf("command %q failed: %v\noutput:%s", prettyCmd(cmd), err, string(out)) 144 } 145 146 framework.Logf("command %q succeeded:\n%s", prettyCmd(cmd), string(out)) 147 return nil 148 } 149 150 func getKindContainerName() string { 151 clusterName := os.Getenv("KIND_CLUSTER_NAME") 152 if clusterName == "" { 153 clusterName = "kind" 154 } 155 return clusterName + "-control-plane" 156 } 157 158 func prettyCmd(cmd *exec.Cmd) string { 159 return fmt.Sprintf("%s %s", cmd.Path, strings.Join(cmd.Args, " ")) 160 }