Files
kubernetes/test/e2e_node/runner/local/run_local.go
Patrick Ohly d97b67d97a e2e node: support running the test binary under a debugger
Single-stepping interactively through a test can be useful to understand what's
happening and to investigate the state at each step.

Similar support was added early to hack/ginkgo-e2e.sh, so the same env variable
is used again.
2024-04-16 11:46:28 +02:00

137 lines
5.1 KiB
Go

/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"flag"
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"k8s.io/kubernetes/test/e2e_node/builder"
"k8s.io/kubernetes/test/e2e_node/system"
"k8s.io/kubernetes/test/utils"
"k8s.io/klog/v2"
)
var buildDependencies = flag.Bool("build-dependencies", true, "If true, build all dependencies.")
var ginkgoFlags = flag.String("ginkgo-flags", "", "Space-separated list of arguments to pass to Ginkgo test runner, with shell-style quoting and escaping.")
var testFlags = flag.String("test-flags", "", "Space-separated list of arguments to pass to node e2e test, with shell-style quoting and escaping.")
var systemSpecName = flag.String("system-spec-name", "", fmt.Sprintf("The name of the system spec used for validating the image in the node conformance test. The specs are at %s. If unspecified, the default built-in spec (system.DefaultSpec) will be used.", system.SystemSpecPath))
var extraEnvs = flag.String("extra-envs", "", "The extra environment variables needed for node e2e tests. Format: a list of key=value pairs, e.g., env1=val1,env2=val2")
var runtimeConfig = flag.String("runtime-config", "", "The runtime configuration for the API server on the node e2e tests. Format: a list of key=value pairs, e.g., env1=val1,env2=val2")
var kubeletConfigFile = flag.String("kubelet-config-file", "", "The KubeletConfiguration file that should be applied to the kubelet")
var debugTool = flag.String("debug-tool", "", "'delve', 'dlv' or 'gdb': run e2e_node.test under that debugger")
func main() {
klog.InitFlags(nil)
flag.Parse()
// Build dependencies - ginkgo, kubelet, e2e_node.test, and mounter.
if *buildDependencies {
if err := builder.BuildGo(); err != nil {
klog.Fatalf("Failed to build the dependencies: %v", err)
}
}
// Run node e2e test
outputDir, err := utils.GetK8sBuildOutputDir(builder.IsDockerizedBuild(), builder.GetTargetBuildArch())
if err != nil {
klog.Fatalf("Failed to get build output directory: %v", err)
}
klog.Infof("Got build output dir: %v", outputDir)
test := filepath.Join(outputDir, "e2e_node.test")
interactive := false
var cmd string
var args []string
switch *debugTool {
case "":
// No debugger, run gingko directly.
cmd = filepath.Join(outputDir, "ginkgo")
args = []string{*ginkgoFlags, test, "--"}
case "delve", "dlv":
dlv, err := exec.LookPath("dlv")
if err != nil {
klog.Fatalf("'dlv' not found: %v", err)
}
interactive = true
cmd = dlv
args = []string{"exec", test, "--", addGinkgoArgPrefix(*ginkgoFlags)}
case "gdb":
gdb, err := exec.LookPath("gdb")
if err != nil {
klog.Fatalf("'gdb' not found: %v", err)
}
interactive = true
cmd = gdb
args = []string{test, "--", addGinkgoArgPrefix(*ginkgoFlags)}
}
args = append(args, *testFlags, fmt.Sprintf("--runtime-config=%s", *runtimeConfig))
if *systemSpecName != "" {
rootDir, err := utils.GetK8sRootDir()
if err != nil {
klog.Fatalf("Failed to get k8s root directory: %v", err)
}
systemSpecFile := filepath.Join(rootDir, system.SystemSpecPath, *systemSpecName+".yaml")
args = append(args, fmt.Sprintf("--system-spec-name=%s --system-spec-file=%s --extra-envs=%s", *systemSpecName, systemSpecFile, *extraEnvs))
}
if *kubeletConfigFile != "" {
args = append(args, fmt.Sprintf("--kubelet-config-file=\"%s\"", *kubeletConfigFile))
}
if err := runCommand(interactive, cmd, args...); err != nil {
klog.Exitf("Test failed: %v", err)
}
return
}
func addGinkgoArgPrefix(ginkgoFlags string) string {
// Warning, hack! This simplistic search/replace assumes that hyphens do not appear
// inside argument values.
//
// The right solution would be to use github.com/anmitsu/go-shlex to split
// the -ginkgo-flags and -test-flags strings into individual arguments, then invoke
// exec.Command with the resulting string slice instead of passing a single string
// to sh. But github.com/anmitsu/go-shlex is not a Kubernetes dependency and not
// worth adding.
ginkgoFlags = regexp.MustCompile(`(^| )--?`).ReplaceAllString(ginkgoFlags, `$1--ginkgo.`)
return ginkgoFlags
}
func runCommand(interactive bool, name string, args ...string) error {
klog.Infof("Running command: %v %v", name, strings.Join(args, " "))
// Using sh is necessary because the args are using POSIX quoting.
// sh has to parse that.
cmd := exec.Command("sudo", "sh", "-c", strings.Join(append([]string{name}, args...), " "))
if interactive {
// stdin must be a console.
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdin
cmd.Stderr = os.Stdin
} else {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
}
return cmd.Run()
}