Skip to content

Commit 20756a8

Browse files
authored
Merge pull request kubernetes-sigs#4499 from stmcginnis/no-docker-testing
🌱 Use docker API instead of CLI in test framework
2 parents 9154e6e + bb194af commit 20756a8

File tree

10 files changed

+841
-124
lines changed

10 files changed

+841
-124
lines changed

test/framework/bootstrap/kind_util.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ import (
2525
"github.com/pkg/errors"
2626

2727
"sigs.k8s.io/cluster-api/test/framework/clusterctl"
28-
"sigs.k8s.io/cluster-api/test/framework/exec"
2928
"sigs.k8s.io/cluster-api/test/framework/internal/log"
29+
"sigs.k8s.io/cluster-api/test/infrastructure/container"
3030
kind "sigs.k8s.io/kind/pkg/cluster"
3131
kindnodes "sigs.k8s.io/kind/pkg/cluster/nodes"
3232
kindnodesutils "sigs.k8s.io/kind/pkg/cluster/nodeutils"
@@ -149,11 +149,13 @@ func loadImage(ctx context.Context, cluster, image string) error {
149149
// copied from kind https://github.com/kubernetes-sigs/kind/blob/v0.7.0/pkg/cmd/kind/load/docker-image/docker-image.go#L168
150150
// save saves image to dest, as in `docker save`.
151151
func save(ctx context.Context, image, dest string) error {
152-
sout, serr, err := exec.NewCommand(
153-
exec.WithCommand("docker"),
154-
exec.WithArgs("save", "-o", dest, image),
155-
).Run(ctx)
156-
return errors.Wrapf(err, "stdout: %q, stderr: %q", string(sout), string(serr))
152+
containerRuntime, err := container.NewDockerClient()
153+
if err != nil {
154+
return errors.Wrap(err, "failed to get Docker runtime client")
155+
}
156+
157+
err = containerRuntime.SaveContainerImage(ctx, image, dest)
158+
return errors.Wrapf(err, "error saving image %q to %q", image, dest)
157159
}
158160

159161
// copied from kind https://github.com/kubernetes-sigs/kind/blob/v0.7.0/pkg/cmd/kind/load/docker-image/docker-image.go#L158

test/framework/cluster_proxy.go

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import (
2424
"os"
2525
"path"
2626
goruntime "runtime"
27-
"strings"
2827

2928
. "github.com/onsi/gomega"
3029
corev1 "k8s.io/api/core/v1"
@@ -37,6 +36,7 @@ import (
3736
expv1 "sigs.k8s.io/cluster-api/exp/api/v1alpha4"
3837
"sigs.k8s.io/cluster-api/test/framework/exec"
3938
"sigs.k8s.io/cluster-api/test/framework/internal/log"
39+
"sigs.k8s.io/cluster-api/test/infrastructure/container"
4040
"sigs.k8s.io/controller-runtime/pkg/client"
4141
)
4242

@@ -314,7 +314,10 @@ func (p *clusterProxy) isDockerCluster(ctx context.Context, namespace string, na
314314
}
315315

316316
func (p *clusterProxy) fixConfig(ctx context.Context, name string, config *api.Config) {
317-
port, err := findLoadBalancerPort(ctx, name)
317+
containerRuntime, err := container.NewDockerClient()
318+
Expect(err).ToNot(HaveOccurred(), "Failed to get Docker runtime client")
319+
320+
port, err := containerRuntime.GetHostPort(ctx, name, "6443/tcp")
318321
Expect(err).ToNot(HaveOccurred(), "Failed to get load balancer port")
319322

320323
controlPlaneURL := &url.URL{
@@ -325,21 +328,6 @@ func (p *clusterProxy) fixConfig(ctx context.Context, name string, config *api.C
325328
config.Clusters[currentCluster].Server = controlPlaneURL.String()
326329
}
327330

328-
func findLoadBalancerPort(ctx context.Context, name string) (string, error) {
329-
loadBalancerName := name + "-lb"
330-
portFormat := `{{index (index (index .NetworkSettings.Ports "6443/tcp") 0) "HostPort"}}`
331-
getPathCmd := exec.NewCommand(
332-
exec.WithCommand("docker"),
333-
exec.WithArgs("inspect", loadBalancerName, "--format", portFormat),
334-
)
335-
stdout, _, err := getPathCmd.Run(ctx)
336-
if err != nil {
337-
return "", err
338-
}
339-
340-
return strings.TrimSpace(string(stdout)), nil
341-
}
342-
343331
// Dispose clusterProxy internal resources (the operation does not affects the Kubernetes cluster).
344332
func (p *clusterProxy) Dispose(ctx context.Context) {
345333
Expect(ctx).NotTo(BeNil(), "ctx is required for Dispose")

test/framework/docker_logcollector.go

Lines changed: 12 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ import (
2727
kerrors "k8s.io/apimachinery/pkg/util/errors"
2828
clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha4"
2929
expv1 "sigs.k8s.io/cluster-api/exp/api/v1alpha4"
30+
"sigs.k8s.io/cluster-api/test/infrastructure/container"
3031
"sigs.k8s.io/controller-runtime/pkg/client"
3132
"sigs.k8s.io/kind/pkg/errors"
32-
"sigs.k8s.io/kind/pkg/exec"
3333
)
3434

3535
// DockerLogCollector collect logs from a CAPD workload cluster.
@@ -47,30 +47,35 @@ func machineContainerName(cluster, machine string) string {
4747

4848
func (k DockerLogCollector) CollectMachineLog(ctx context.Context, managementClusterClient client.Client, m *clusterv1.Machine, outputPath string) error {
4949
containerName := machineContainerName(m.Spec.ClusterName, m.Name)
50-
return k.collectLogsFromNode(outputPath, containerName)
50+
return k.collectLogsFromNode(ctx, outputPath, containerName)
5151
}
5252

5353
func (k DockerLogCollector) CollectMachinePoolLog(ctx context.Context, managementClusterClient client.Client, m *expv1.MachinePool, outputPath string) error {
5454
var errs []error
5555
for _, instance := range m.Status.NodeRefs {
5656
containerName := machineContainerName(m.Spec.ClusterName, instance.Name)
57-
if err := k.collectLogsFromNode(filepath.Join(outputPath, instance.Name), containerName); err != nil {
57+
if err := k.collectLogsFromNode(ctx, filepath.Join(outputPath, instance.Name), containerName); err != nil {
5858
// collecting logs is best effort so we proceed to the next instance even if we encounter an error.
5959
errs = append(errs, err)
6060
}
6161
}
6262
return kerrors.NewAggregate(errs)
6363
}
6464

65-
func (k DockerLogCollector) collectLogsFromNode(outputPath string, containerName string) error {
65+
func (k DockerLogCollector) collectLogsFromNode(ctx context.Context, outputPath string, containerName string) error {
66+
containerRuntime, err := container.NewDockerClient()
67+
if err != nil {
68+
return errors.Wrap(err, "Failed to collect logs from node")
69+
}
70+
6671
execToPathFn := func(outputFileName, command string, args ...string) func() error {
6772
return func() error {
6873
f, err := fileOnHost(filepath.Join(outputPath, outputFileName))
6974
if err != nil {
7075
return err
7176
}
7277
defer f.Close()
73-
return execOnContainer(containerName, f, command, args...)
78+
return containerRuntime.ExecToFile(ctx, containerName, f, command, args...)
7479
}
7580
}
7681
copyDirFn := func(containerDir, dirName string) func() error {
@@ -85,7 +90,8 @@ func (k DockerLogCollector) collectLogsFromNode(outputPath string, containerName
8590

8691
defer os.Remove(tempfileName)
8792

88-
err = execOnContainer(
93+
err = containerRuntime.ExecToFile(
94+
ctx,
8995
containerName,
9096
f,
9197
"tar", "--hard-dereference", "--dereference", "--directory", containerDir, "--create", "--file", "-", ".",
@@ -140,34 +146,3 @@ func fileOnHost(path string) (*os.File, error) {
140146
}
141147
return os.Create(path)
142148
}
143-
144-
// execOnContainer is an helper that runs a command on a CAPD node/container.
145-
func execOnContainer(containerName string, fileOnHost *os.File, command string, args ...string) error {
146-
dockerArgs := []string{
147-
"exec",
148-
// run with privileges so we can remount etc..
149-
// this might not make sense in the most general sense, but it is
150-
// important to many kind commands
151-
"--privileged",
152-
}
153-
// specify the container and command, after this everything will be
154-
// args the the command in the container rather than to docker
155-
dockerArgs = append(
156-
dockerArgs,
157-
containerName, // ... against the container
158-
command, // with the command specified
159-
)
160-
dockerArgs = append(
161-
dockerArgs,
162-
// finally, with the caller args
163-
args...,
164-
)
165-
166-
cmd := exec.Command("docker", dockerArgs...)
167-
cmd.SetEnv("PATH", os.Getenv("PATH"))
168-
169-
cmd.SetStderr(fileOnHost)
170-
cmd.SetStdout(fileOnHost)
171-
172-
return errors.WithStack(cmd.Run())
173-
}

test/framework/ginkgoextensions/output.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ import (
2323
"github.com/onsi/ginkgo"
2424
)
2525

26+
// TestOutput can be used for writing testing output.
27+
var TestOutput = ginkgo.GinkgoWriter
28+
29+
// Byf provides formatted output to the GinkoWriter.
2630
func Byf(format string, a ...interface{}) {
2731
ginkgo.By(fmt.Sprintf(format, a...))
2832
}

test/framework/kubetest/run.go

Lines changed: 47 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,20 @@ import (
2121
"context"
2222
"fmt"
2323
"os"
24-
"os/exec"
2524
"os/user"
2625
"path"
2726
"runtime"
2827
"strconv"
2928
"strings"
3029

30+
"github.com/onsi/ginkgo"
3131
"github.com/pkg/errors"
3232
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3333
"k8s.io/client-go/discovery"
3434
"k8s.io/client-go/tools/clientcmd"
3535
"sigs.k8s.io/cluster-api/test/framework"
36+
"sigs.k8s.io/cluster-api/test/framework/ginkgoextensions"
37+
"sigs.k8s.io/cluster-api/test/infrastructure/container"
3638
"sigs.k8s.io/yaml"
3739
)
3840

@@ -123,14 +125,6 @@ func Run(ctx context.Context, input RunInput) error {
123125
return err
124126
}
125127

126-
var testRepoListVolumeArgs []string
127-
if input.KubeTestRepoListPath != "" {
128-
testRepoListVolumeArgs, err = buildKubeTestRepoListArgs(kubetestConfigDir, input.KubeTestRepoListPath)
129-
if err != nil {
130-
return err
131-
}
132-
}
133-
134128
e2eVars := map[string]string{
135129
"kubeconfig": "/tmp/kubeconfig",
136130
"provider": "skeleton",
@@ -145,27 +139,55 @@ func Run(ctx context.Context, input RunInput) error {
145139
if input.ConformanceImage == "" {
146140
input.ConformanceImage = versionToConformanceImage(input.KubernetesVersion)
147141
}
148-
kubeConfigVolumeMount := volumeArg(tmpKubeConfigPath, "/tmp/kubeconfig")
149-
outputVolumeMount := volumeArg(reportDir, "/output")
142+
volumeMounts := map[string]string{
143+
tmpKubeConfigPath: "/tmp/kubeconfig",
144+
reportDir: "/output",
145+
}
150146
user, err := user.Current()
151147
if err != nil {
152148
return errors.Wrap(err, "unable to determine current user")
153149
}
154-
userArg := user.Uid + ":" + user.Gid
155-
entrypointArg := "--entrypoint=/usr/local/bin/ginkgo"
156-
networkArg := "--network=kind"
157-
e2eCmd := exec.Command("docker", "run", "--user", userArg, entrypointArg, kubeConfigVolumeMount, outputVolumeMount, "-t", networkArg)
158-
if len(testRepoListVolumeArgs) > 0 {
159-
e2eCmd.Args = append(e2eCmd.Args, testRepoListVolumeArgs...)
150+
env := map[string]string{}
151+
152+
if input.KubeTestRepoListPath != "" {
153+
tmpKubeTestRepoListPath := path.Join(kubetestConfigDir, "repo_list.yaml")
154+
if err := copyFile(input.KubeTestRepoListPath, tmpKubeTestRepoListPath); err != nil {
155+
return err
156+
}
157+
dest := "/tmp/repo_list.yaml"
158+
env["KUBE_TEST_REPO_LIST"] = dest
159+
volumeMounts[tmpKubeTestRepoListPath] = dest
160+
}
161+
162+
// Formulate our command arguments
163+
args := []string{}
164+
args = append(args, ginkgoArgs...)
165+
args = append(args, "/usr/local/bin/e2e.test")
166+
args = append(args, "--")
167+
args = append(args, e2eArgs...)
168+
args = append(args, config.toFlags()...)
169+
170+
// Get our current working directory. Just for information, so we don't need
171+
// to worry about errors at this point.
172+
cwd, _ := os.Getwd()
173+
ginkgoextensions.Byf("Running e2e test: dir=%s, command=%q", cwd, args)
174+
175+
containerRuntime, err := container.NewDockerClient()
176+
if err != nil {
177+
return errors.Wrap(err, "Unable to run conformance tests")
160178
}
161-
e2eCmd.Args = append(e2eCmd.Args, input.ConformanceImage)
162-
e2eCmd.Args = append(e2eCmd.Args, ginkgoArgs...)
163-
e2eCmd.Args = append(e2eCmd.Args, "/usr/local/bin/e2e.test")
164-
e2eCmd.Args = append(e2eCmd.Args, "--")
165-
e2eCmd.Args = append(e2eCmd.Args, e2eArgs...)
166-
e2eCmd.Args = append(e2eCmd.Args, config.toFlags()...)
167-
e2eCmd = framework.CompleteCommand(e2eCmd, "Running e2e test", false)
168-
if err := e2eCmd.Run(); err != nil {
179+
180+
err = containerRuntime.RunContainer(ctx, &container.RunContainerInput{
181+
Image: input.ConformanceImage,
182+
Network: "kind",
183+
User: user.Uid,
184+
Group: user.Gid,
185+
Volumes: volumeMounts,
186+
EnvironmentVars: env,
187+
CommandArgs: args,
188+
Entrypoint: []string{"/usr/local/bin/ginkgo"},
189+
}, ginkgo.GinkgoWriter)
190+
if err != nil {
169191
return errors.Wrap(err, "Unable to run conformance tests")
170192
}
171193
return framework.GatherJUnitReports(reportDir, input.ArtifactsDirectory)
@@ -235,27 +257,6 @@ func countClusterNodes(ctx context.Context, proxy framework.ClusterProxy) (int,
235257
return len(nodeList.Items), nil
236258
}
237259

238-
func isSELinuxEnforcing() bool {
239-
dat, err := os.ReadFile("/sys/fs/selinux/enforce")
240-
if err != nil {
241-
return false
242-
}
243-
return string(dat) == "1"
244-
}
245-
246-
func volumeArg(src, dest string) string {
247-
volumeArg := "-v" + src + ":" + dest
248-
if isSELinuxEnforcing() {
249-
return volumeArg + ":z"
250-
}
251-
return volumeArg
252-
}
253-
254-
func envArg(key, value string) string {
255-
envArg := "-e" + key + "=" + value
256-
return envArg
257-
}
258-
259260
func versionToConformanceImage(kubernetesVersion string) string {
260261
k8sVersion := strings.ReplaceAll(kubernetesVersion, "+", "_")
261262
if isUsingCIArtifactsVersion(kubernetesVersion) {
@@ -274,16 +275,3 @@ func buildArgs(kv map[string]string, flagMarker string) []string {
274275
}
275276
return args
276277
}
277-
278-
func buildKubeTestRepoListArgs(kubetestConfigDir, kubeTestRepoListPath string) ([]string, error) {
279-
args := make([]string, 2)
280-
281-
tmpKubeTestRepoListPath := path.Join(kubetestConfigDir, "repo_list.yaml")
282-
if err := copyFile(kubeTestRepoListPath, tmpKubeTestRepoListPath); err != nil {
283-
return nil, err
284-
}
285-
dest := "/tmp/repo_list.yaml"
286-
args[0] = envArg("KUBE_TEST_REPO_LIST", dest)
287-
args[1] = volumeArg(tmpKubeTestRepoListPath, dest)
288-
return args, nil
289-
}

test/framework/suite_helpers.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import (
2424
"path/filepath"
2525
"strings"
2626

27-
"github.com/onsi/ginkgo"
2827
"github.com/onsi/ginkgo/config"
2928
"github.com/onsi/ginkgo/reporters"
3029
. "sigs.k8s.io/cluster-api/test/framework/ginkgoextensions"
@@ -87,8 +86,8 @@ func CreateJUnitReporterForProw(artifactsDirectory string) *reporters.JUnitRepor
8786
// CompleteCommand prints a command before running it. Acts as a helper function.
8887
// privateArgs when true will not print arguments.
8988
func CompleteCommand(cmd *exec.Cmd, desc string, privateArgs bool) *exec.Cmd {
90-
cmd.Stderr = ginkgo.GinkgoWriter
91-
cmd.Stdout = ginkgo.GinkgoWriter
89+
cmd.Stderr = TestOutput
90+
cmd.Stdout = TestOutput
9291
if privateArgs {
9392
Byf("%s: dir=%s, command=%s", desc, cmd.Dir, cmd)
9493
} else {

test/go.mod

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,13 @@ go 1.16
55
replace sigs.k8s.io/cluster-api => ../
66

77
require (
8+
github.com/Microsoft/go-winio v0.5.0 // indirect
89
github.com/blang/semver v3.5.1+incompatible
10+
github.com/containerd/containerd v1.5.2 // indirect
11+
github.com/docker/docker v20.10.7+incompatible
12+
github.com/docker/go-connections v0.4.0
913
github.com/go-logr/logr v0.4.0
14+
github.com/morikuni/aec v1.0.0 // indirect
1015
github.com/onsi/ginkgo v1.16.4
1116
github.com/onsi/gomega v1.13.0
1217
github.com/pkg/errors v0.9.1

0 commit comments

Comments
 (0)