1
16
17 package remote
18
19 import (
20 "crypto/rand"
21 "encoding/base64"
22 "fmt"
23 "math"
24 "os"
25 "os/exec"
26 "path/filepath"
27 "runtime"
28 "strings"
29 "time"
30
31 "k8s.io/klog/v2"
32
33 "k8s.io/kubernetes/test/e2e_node/builder"
34 "k8s.io/kubernetes/test/utils"
35 )
36
37
38 type ConformanceRemote struct{}
39
40 func init() {
41 RegisterTestSuite("conformance", &ConformanceRemote{})
42 }
43
44
45 func getConformanceDirectory() (string, error) {
46 k8sRoot, err := utils.GetK8sRootDir()
47 if err != nil {
48 return "", err
49 }
50 return filepath.Join(k8sRoot, "test", "e2e_node", "conformance", "build"), nil
51 }
52
53
54 func commandToString(c *exec.Cmd) string {
55 return strings.Join(append([]string{c.Path}, c.Args[1:]...), " ")
56 }
57
58
59 const (
60 conformanceRegistry = "registry.k8s.io"
61 conformanceArch = runtime.GOARCH
62 conformanceTarfile = "node_conformance.tar"
63 conformanceTestBinary = "e2e_node.test"
64 conformanceImageLoadTimeout = time.Duration(30) * time.Second
65 )
66
67
68 var timestamp = getTimestamp()
69
70
71 func getConformanceTestImageName(systemSpecName string) string {
72 if systemSpecName == "" {
73 return fmt.Sprintf("%s/node-test-%s:%s", conformanceRegistry, conformanceArch, timestamp)
74 }
75 return fmt.Sprintf("%s/node-test-%s-%s:%s", conformanceRegistry, systemSpecName, conformanceArch, timestamp)
76 }
77
78
79 func buildConformanceTest(binDir, systemSpecName string) error {
80
81 conformancePath, err := getConformanceDirectory()
82 if err != nil {
83 return fmt.Errorf("failed to get node conformance directory: %w", err)
84 }
85
86 cmd := exec.Command("make", "-C", conformancePath, "BIN_DIR="+binDir,
87 "REGISTRY="+conformanceRegistry,
88 "ARCH="+conformanceArch,
89 "VERSION="+timestamp,
90 "SYSTEM_SPEC_NAME="+systemSpecName)
91 if output, err := cmd.CombinedOutput(); err != nil {
92 return fmt.Errorf("failed to build node conformance docker image: command - %q, error - %v, output - %q",
93 commandToString(cmd), err, output)
94 }
95
96 cmd = exec.Command("docker", "save", "-o", filepath.Join(binDir, conformanceTarfile), getConformanceTestImageName(systemSpecName))
97 if output, err := cmd.CombinedOutput(); err != nil {
98 return fmt.Errorf("failed to save node conformance docker image into tar file: command - %q, error - %v, output - %q",
99 commandToString(cmd), err, output)
100 }
101 return nil
102 }
103
104
105 func (c *ConformanceRemote) SetupTestPackage(tardir, systemSpecName string) error {
106
107 if err := builder.BuildGo(); err != nil {
108 return fmt.Errorf("failed to build the dependencies: %w", err)
109 }
110
111
112 buildOutputDir, err := utils.GetK8sBuildOutputDir(builder.IsDockerizedBuild(), builder.GetTargetBuildArch())
113 if err != nil {
114 return fmt.Errorf("failed to locate kubernetes build output directory %v", err)
115 }
116
117
118 if err := buildConformanceTest(buildOutputDir, systemSpecName); err != nil {
119 return fmt.Errorf("failed to build node conformance test: %w", err)
120 }
121
122
123 requiredFiles := []string{"kubelet", conformanceTestBinary, conformanceTarfile}
124 for _, file := range requiredFiles {
125 source := filepath.Join(buildOutputDir, file)
126 if _, err := os.Stat(source); err != nil {
127 return fmt.Errorf("failed to locate test file %s: %w", file, err)
128 }
129 output, err := exec.Command("cp", source, filepath.Join(tardir, file)).CombinedOutput()
130 if err != nil {
131 return fmt.Errorf("failed to copy %q: error - %v output - %q", file, err, output)
132 }
133 }
134
135 return nil
136 }
137
138
139 func loadConformanceImage(host, workspace string) error {
140 klog.Info("Loading conformance image from tarfile")
141 tarfile := filepath.Join(workspace, conformanceTarfile)
142 if output, err := SSH(host, "timeout", conformanceImageLoadTimeout.String(),
143 "docker", "load", "-i", tarfile); err != nil {
144 return fmt.Errorf("failed to load node conformance image from tar file %q: error - %v output - %q",
145 tarfile, err, output)
146 }
147 return nil
148 }
149
150
151 const kubeletLauncherLog = "kubelet-launcher.log"
152
153
154
155
156
157 var kubeletPodPath = "conformance-pod-manifest-" + timestamp
158
159
160 func getPodPath(workspace string) string {
161 return filepath.Join(workspace, kubeletPodPath)
162 }
163
164
165 func isSystemd(host string) (bool, error) {
166
167 output, err := SSH(host, "test", "-e", "/run/systemd/system", "&&", "echo", "systemd", "||", "true")
168 if err != nil {
169 return false, fmt.Errorf("failed to check systemd: error - %v output - %q", err, output)
170 }
171 return strings.TrimSpace(output) != "", nil
172 }
173
174
175
176
177
178
179 func launchKubelet(host, workspace, results, testArgs, bearerToken string) error {
180 podManifestPath := getPodPath(workspace)
181 if output, err := SSH(host, "mkdir", podManifestPath); err != nil {
182 return fmt.Errorf("failed to create kubelet pod manifest path %q: error - %v output - %q",
183 podManifestPath, err, output)
184 }
185 startKubeletCmd := fmt.Sprintf("./%s --run-kubelet-mode --node-name=%s"+
186 " --bearer-token=%s"+
187 " --report-dir=%s %s --kubelet-flags=--pod-manifest-path=%s > %s 2>&1",
188 conformanceTestBinary, host, bearerToken, results, testArgs, podManifestPath, filepath.Join(results, kubeletLauncherLog))
189 var cmd []string
190 systemd, err := isSystemd(host)
191 if err != nil {
192 return fmt.Errorf("failed to check systemd: %w", err)
193 }
194 if systemd {
195 cmd = []string{
196 "systemd-run", "sh", "-c", getSSHCommand(" && ",
197
198 fmt.Sprintf("cd %s", workspace),
199
200 startKubeletCmd,
201 ),
202 }
203 } else {
204 cmd = []string{
205 "sh", "-c", getSSHCommand(" && ",
206
207 fmt.Sprintf("cd %s", workspace),
208
209 fmt.Sprintf("(nohup %s &)", startKubeletCmd),
210 ),
211 }
212 }
213 klog.V(2).Infof("Launch kubelet with command: %v", cmd)
214 output, err := SSH(host, cmd...)
215 if err != nil {
216 return fmt.Errorf("failed to launch kubelet with command %v: error - %v output - %q",
217 cmd, err, output)
218 }
219 klog.Info("Successfully launch kubelet")
220 return nil
221 }
222
223
224 const kubeletStopGracePeriod = 10 * time.Second
225
226
227 func stopKubelet(host, workspace string) error {
228 klog.Info("Gracefully stop kubelet launcher")
229 if output, err := SSH(host, "pkill", conformanceTestBinary); err != nil {
230 return fmt.Errorf("failed to gracefully stop kubelet launcher: error - %v output - %q",
231 err, output)
232 }
233 klog.Info("Wait for kubelet launcher to stop")
234 stopped := false
235 for start := time.Now(); time.Since(start) < kubeletStopGracePeriod; time.Sleep(time.Second) {
236
237 output, err := SSH(host, "pidof", conformanceTestBinary, "||", "true")
238 if err != nil {
239 return fmt.Errorf("failed to check kubelet stopping: error - %v output -%q",
240 err, output)
241 }
242
243 if strings.TrimSpace(output) == "" {
244 stopped = true
245 break
246 }
247 }
248 if !stopped {
249 klog.Info("Forcibly stop kubelet")
250 if output, err := SSH(host, "pkill", "-SIGKILL", conformanceTestBinary); err != nil {
251 return fmt.Errorf("failed to forcibly stop kubelet: error - %v output - %q",
252 err, output)
253 }
254 }
255 klog.Info("Successfully stop kubelet")
256
257 podManifestPath := getPodPath(workspace)
258 if output, err := SSH(host, "rm", "-f", filepath.Join(workspace, podManifestPath)); err != nil {
259 return fmt.Errorf("failed to cleanup pod manifest directory %q: error - %v, output - %q",
260 podManifestPath, err, output)
261 }
262 return nil
263 }
264
265
266 func (c *ConformanceRemote) RunTest(host, workspace, results, imageDesc, junitFilePrefix, testArgs, _, systemSpecName, extraEnvs, _ string, timeout time.Duration) (string, error) {
267
268 if err := setupCNI(host, workspace); err != nil {
269 return "", err
270 }
271
272
273 if err := configureFirewall(host); err != nil {
274 return "", err
275 }
276
277
278 cleanupNodeProcesses(host)
279
280
281 if err := loadConformanceImage(host, workspace); err != nil {
282 return "", err
283 }
284
285 bearerToken, err := generateSecureToken(16)
286 if err != nil {
287 return "", err
288 }
289
290
291 if err := launchKubelet(host, workspace, results, testArgs, bearerToken); err != nil {
292 return "", err
293 }
294
295 defer func() {
296 if err := stopKubelet(host, workspace); err != nil {
297
298 klog.Errorf("failed to stop kubelet: %v", err)
299 }
300 }()
301
302
303 klog.V(2).Infof("Starting tests on %q", host)
304 podManifestPath := getPodPath(workspace)
305 cmd := fmt.Sprintf("'timeout -k 30s %fs docker run --rm --privileged=true --net=host -v /:/rootfs -v %s:%s -v %s:/var/result -e TEST_ARGS=--report-prefix=%s -e EXTRA_ENVS=%s -e TEST_ARGS=--bearer-token=%s %s'",
306 timeout.Seconds(), podManifestPath, podManifestPath, results, junitFilePrefix, extraEnvs, bearerToken, getConformanceTestImageName(systemSpecName))
307 return SSH(host, "sh", "-c", cmd)
308 }
309
310
311
312
313 func generateSecureToken(tokenLen int) (string, error) {
314
315 tokenSize := math.Ceil(float64(tokenLen) * 6 / 8)
316 rawToken := make([]byte, int(tokenSize))
317 if _, err := rand.Read(rawToken); err != nil {
318 return "", err
319 }
320 encoded := base64.RawURLEncoding.EncodeToString(rawToken)
321 token := encoded[:tokenLen]
322 return token, nil
323 }
324
View as plain text