k8s.io/kubernetes@v1.29.3/test/e2e/storage/utils/pod.go (about) 1 /* 2 Copyright 2020 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 utils 18 19 import ( 20 "context" 21 "fmt" 22 "io" 23 "os" 24 "path" 25 "regexp" 26 "strings" 27 "time" 28 29 "github.com/onsi/ginkgo/v2" 30 "github.com/onsi/gomega" 31 v1 "k8s.io/api/core/v1" 32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 clientset "k8s.io/client-go/kubernetes" 34 "k8s.io/kubernetes/test/e2e/framework" 35 e2enode "k8s.io/kubernetes/test/e2e/framework/node" 36 e2essh "k8s.io/kubernetes/test/e2e/framework/ssh" 37 "k8s.io/kubernetes/test/e2e/storage/podlogs" 38 ) 39 40 // StartPodLogs begins capturing log output and events from current 41 // and future pods running in the namespace of the framework. That 42 // ends when the returned cleanup function is called. 43 // 44 // The output goes to log files (when using --report-dir, as in the 45 // CI) or the output stream (otherwise). 46 func StartPodLogs(ctx context.Context, f *framework.Framework, driverNamespace *v1.Namespace) func() { 47 ctx, cancel := context.WithCancel(ctx) 48 cs := f.ClientSet 49 50 ns := driverNamespace.Name 51 52 var podEventLog io.Writer = ginkgo.GinkgoWriter 53 var podEventLogCloser io.Closer 54 to := podlogs.LogOutput{ 55 StatusWriter: ginkgo.GinkgoWriter, 56 } 57 if framework.TestContext.ReportDir == "" { 58 to.LogWriter = ginkgo.GinkgoWriter 59 } else { 60 test := ginkgo.CurrentSpecReport() 61 // Clean up each individual component text such that 62 // it contains only characters that are valid as file 63 // name. 64 reg := regexp.MustCompile("[^a-zA-Z0-9_-]+") 65 var testName []string 66 for _, text := range test.ContainerHierarchyTexts { 67 testName = append(testName, reg.ReplaceAllString(text, "_")) 68 if len(test.LeafNodeText) > 0 { 69 testName = append(testName, reg.ReplaceAllString(test.LeafNodeText, "_")) 70 } 71 } 72 // We end the prefix with a slash to ensure that all logs 73 // end up in a directory named after the current test. 74 // 75 // Each component name maps to a directory. This 76 // avoids cluttering the root artifact directory and 77 // keeps each directory name smaller (the full test 78 // name at one point exceeded 256 characters, which was 79 // too much for some filesystems). 80 logDir := framework.TestContext.ReportDir + "/" + strings.Join(testName, "/") 81 to.LogPathPrefix = logDir + "/" 82 83 err := os.MkdirAll(logDir, 0755) 84 framework.ExpectNoError(err, "create pod log directory") 85 f, err := os.Create(path.Join(logDir, "pod-event.log")) 86 framework.ExpectNoError(err, "create pod events log file") 87 podEventLog = f 88 podEventLogCloser = f 89 } 90 podlogs.CopyAllLogs(ctx, cs, ns, to) 91 92 // The framework doesn't know about the driver pods because of 93 // the separate namespace. Therefore we always capture the 94 // events ourselves. 95 podlogs.WatchPods(ctx, cs, ns, podEventLog, podEventLogCloser) 96 97 return cancel 98 } 99 100 // KubeletCommand performs `start`, `restart`, or `stop` on the kubelet running on the node of the target pod and waits 101 // for the desired statues.. 102 // - First issues the command via `systemctl` 103 // - If `systemctl` returns stderr "command not found, issues the command via `service` 104 // - If `service` also returns stderr "command not found", the test is aborted. 105 // Allowed kubeletOps are `KStart`, `KStop`, and `KRestart` 106 func KubeletCommand(ctx context.Context, kOp KubeletOpt, c clientset.Interface, pod *v1.Pod) { 107 command := "" 108 systemctlPresent := false 109 kubeletPid := "" 110 111 nodeIP, err := getHostAddress(ctx, c, pod) 112 framework.ExpectNoError(err) 113 nodeIP = nodeIP + ":22" 114 115 framework.Logf("Checking if systemctl command is present") 116 sshResult, err := e2essh.SSH(ctx, "systemctl --version", nodeIP, framework.TestContext.Provider) 117 framework.ExpectNoError(err, fmt.Sprintf("SSH to Node %q errored.", pod.Spec.NodeName)) 118 if !strings.Contains(sshResult.Stderr, "command not found") { 119 command = fmt.Sprintf("systemctl %s kubelet", string(kOp)) 120 systemctlPresent = true 121 } else { 122 command = fmt.Sprintf("service kubelet %s", string(kOp)) 123 } 124 125 sudoPresent := isSudoPresent(ctx, nodeIP, framework.TestContext.Provider) 126 if sudoPresent { 127 command = fmt.Sprintf("sudo %s", command) 128 } 129 130 if kOp == KRestart { 131 kubeletPid = getKubeletMainPid(ctx, nodeIP, sudoPresent, systemctlPresent) 132 } 133 134 framework.Logf("Attempting `%s`", command) 135 sshResult, err = e2essh.SSH(ctx, command, nodeIP, framework.TestContext.Provider) 136 framework.ExpectNoError(err, fmt.Sprintf("SSH to Node %q errored.", pod.Spec.NodeName)) 137 e2essh.LogResult(sshResult) 138 gomega.Expect(sshResult.Code).To(gomega.BeZero(), "Failed to [%s] kubelet:\n%#v", string(kOp), sshResult) 139 140 if kOp == KStop { 141 if ok := e2enode.WaitForNodeToBeNotReady(ctx, c, pod.Spec.NodeName, NodeStateTimeout); !ok { 142 framework.Failf("Node %s failed to enter NotReady state", pod.Spec.NodeName) 143 } 144 } 145 if kOp == KRestart { 146 // Wait for a minute to check if kubelet Pid is getting changed 147 isPidChanged := false 148 for start := time.Now(); time.Since(start) < 1*time.Minute; time.Sleep(2 * time.Second) { 149 if ctx.Err() != nil { 150 framework.Fail("timed out waiting for Kubelet POD change") 151 } 152 kubeletPidAfterRestart := getKubeletMainPid(ctx, nodeIP, sudoPresent, systemctlPresent) 153 if kubeletPid != kubeletPidAfterRestart { 154 isPidChanged = true 155 break 156 } 157 } 158 if !isPidChanged { 159 framework.Fail("Kubelet PID remained unchanged after restarting Kubelet") 160 } 161 162 framework.Logf("Noticed that kubelet PID is changed. Waiting for 30 Seconds for Kubelet to come back") 163 time.Sleep(30 * time.Second) 164 } 165 if kOp == KStart || kOp == KRestart { 166 // For kubelet start and restart operations, Wait until Node becomes Ready 167 if ok := e2enode.WaitForNodeToBeReady(ctx, c, pod.Spec.NodeName, NodeStateTimeout); !ok { 168 framework.Failf("Node %s failed to enter Ready state", pod.Spec.NodeName) 169 } 170 } 171 } 172 173 // getHostAddress gets the node for a pod and returns the first 174 // address. Returns an error if the node the pod is on doesn't have an 175 // address. 176 func getHostAddress(ctx context.Context, client clientset.Interface, p *v1.Pod) (string, error) { 177 node, err := client.CoreV1().Nodes().Get(ctx, p.Spec.NodeName, metav1.GetOptions{}) 178 if err != nil { 179 return "", err 180 } 181 // Try externalAddress first 182 for _, address := range node.Status.Addresses { 183 if address.Type == v1.NodeExternalIP { 184 if address.Address != "" { 185 return address.Address, nil 186 } 187 } 188 } 189 // If no externalAddress found, try internalAddress 190 for _, address := range node.Status.Addresses { 191 if address.Type == v1.NodeInternalIP { 192 if address.Address != "" { 193 return address.Address, nil 194 } 195 } 196 } 197 198 // If not found, return error 199 return "", fmt.Errorf("No address for pod %v on node %v", 200 p.Name, p.Spec.NodeName) 201 }