Merge pull request #103874 from liggitt/move-conformance

Move conformance image
This commit is contained in:
Kubernetes Prow Robot
2021-08-05 08:17:34 -07:00
committed by GitHub
20 changed files with 6 additions and 6 deletions

View File

@@ -0,0 +1,43 @@
# Copyright 2018 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.
ARG BASEIMAGE
ARG RUNNERIMAGE
FROM ${BASEIMAGE} as debbase
FROM ${RUNNERIMAGE}
# This is a dependency for `kubectl diff` tests
COPY --from=debbase /usr/bin/diff /usr/local/bin/
COPY cluster /kubernetes/cluster
COPY ginkgo /usr/local/bin/
COPY e2e.test /usr/local/bin/
COPY kubectl /usr/local/bin/
COPY gorunner /usr/local/bin/kubeconformance
# Legacy executables -- deprecated
COPY gorunner /run_e2e.sh
COPY gorunner /gorunner
ENV E2E_FOCUS="\[Conformance\]"
ENV E2E_SKIP=""
ENV E2E_PROVIDER="local"
ENV E2E_PARALLEL="1"
ENV E2E_VERBOSITY="4"
ENV RESULTS_DIR="/tmp/results"
ENV KUBECONFIG=""
ENTRYPOINT [ "kubeconformance" ]

View File

@@ -0,0 +1,85 @@
# 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.
# Build the conformance image.
#
# Usage:
# [ARCH=amd64] [REGISTRY="k8s.gcr.io"] make (build|push) VERSION={some_released_version_of_kubernetes}
REGISTRY?=k8s.gcr.io
ARCH?=amd64
OUT_DIR?=_output
LOCAL_OUTPUT_PATH=$(shell pwd)/../../../$(OUT_DIR)/local/bin/linux/$(ARCH)
DOCKERIZED_OUTPUT_PATH=$(shell pwd)/../../../$(OUT_DIR)/dockerized/bin/linux/$(ARCH)
GINKGO_BIN?=$(shell test -f $(LOCAL_OUTPUT_PATH)/ginkgo && echo $(LOCAL_OUTPUT_PATH)/ginkgo || echo $(DOCKERIZED_OUTPUT_PATH)/ginkgo)
KUBECTL_BIN?=$(shell test -f $(LOCAL_OUTPUT_PATH)/kubectl && echo $(LOCAL_OUTPUT_PATH)/kubectl || echo $(DOCKERIZED_OUTPUT_PATH)/kubectl)
E2E_TEST_BIN?=$(shell test -f $(LOCAL_OUTPUT_PATH)/e2e.test && echo $(LOCAL_OUTPUT_PATH)/e2e.test || echo $(DOCKERIZED_OUTPUT_PATH)/e2e.test)
E2E_GO_RUNNER_BIN?=$(shell test -f $(LOCAL_OUTPUT_PATH)/go-runner && echo $(LOCAL_OUTPUT_PATH)/go-runner || echo $(DOCKERIZED_OUTPUT_PATH)/go-runner)
CLUSTER_DIR?=$(shell pwd)/../../../cluster/
# This is defined in root Makefile, but some build contexts do not refer to them
KUBE_BASE_IMAGE_REGISTRY?=k8s.gcr.io
ifeq ($(ARCH),amd64)
BASEIMAGE?=${KUBE_BASE_IMAGE_REGISTRY}/build-image/debian-base:v2.1.3
else
BASEIMAGE?=${KUBE_BASE_IMAGE_REGISTRY}/build-image/debian-base-${ARCH}:v2.1.3
endif
RUNNERIMAGE?=gcr.io/distroless/base:latest
TEMP_DIR:=$(shell mktemp -d -t conformance-XXXXXX)
all: build
build:
ifndef VERSION
$(error VERSION is undefined)
endif
cp -r ./* ${TEMP_DIR}
cp ${GINKGO_BIN} ${TEMP_DIR}
cp ${KUBECTL_BIN} ${TEMP_DIR}
cp ${E2E_TEST_BIN} ${TEMP_DIR}
cp ${E2E_GO_RUNNER_BIN} ${TEMP_DIR}/gorunner
cp -r ${CLUSTER_DIR} ${TEMP_DIR}/cluster
chmod a+rx ${TEMP_DIR}/ginkgo
chmod a+rx ${TEMP_DIR}/kubectl
chmod a+rx ${TEMP_DIR}/e2e.test
chmod a+rx ${TEMP_DIR}/gorunner
DOCKER_CLI_EXPERIMENTAL=enabled docker buildx build \
--platform linux/${ARCH} \
--load \
--pull \
-t ${REGISTRY}/conformance-${ARCH}:${VERSION} \
--build-arg BASEIMAGE=$(BASEIMAGE) \
--build-arg RUNNERIMAGE=$(RUNNERIMAGE) \
${TEMP_DIR}
rm -rf "${TEMP_DIR}"
push: build
docker push ${REGISTRY}/conformance-${ARCH}:${VERSION}
ifeq ($(ARCH),amd64)
docker rmi ${REGISTRY}/conformance:${VERSION} 2>/dev/null || true
docker tag ${REGISTRY}/conformance-${ARCH}:${VERSION} ${REGISTRY}/conformance:${VERSION}
docker push ${REGISTRY}/conformance:${VERSION}
endif
.PHONY: build push all

View File

@@ -0,0 +1,23 @@
# See the OWNERS docs at https://go.k8s.io/owners
reviewers:
- timothysc
- dims
- spiffxp
- neolit123
- bentheelder
- brahmaroutu
- stevesloka
- johnSchnake
approvers:
- timothysc
- dims
- spiffxp
- neolit123
- bentheelder
emeritus_approvers:
- ixdy
labels:
- sig/release
- sig/testing
- area/conformance

View File

@@ -0,0 +1,40 @@
### conformance
`conformance` is a standalone container to launch Kubernetes end-to-end tests, for the purposes of conformance testing.
`conformance` is built for multiple architectures and _the image is pushed automatically on every release._
#### How to release by hand
```console
# First, build the binaries by running make from the root directory
$ make WHAT="test/e2e/e2e.test vendor/github.com/onsi/ginkgo/ginkgo cmd/kubectl test/conformance/image/go-runner"
# Build for linux/amd64 (default)
# export REGISTRY=$HOST/$ORG to switch from k8s.gcr.io
$ make push VERSION={target_version} ARCH=amd64
# ---> k8s.gcr.io/conformance-amd64:VERSION
# ---> k8s.gcr.io/conformance:VERSION (image with backwards-compatible naming)
$ make push VERSION={target_version} ARCH=arm
# ---> k8s.gcr.io/conformance-arm:VERSION
$ make push VERSION={target_version} ARCH=arm64
# ---> k8s.gcr.io/conformance-arm64:VERSION
$ make push VERSION={target_version} ARCH=ppc64le
# ---> k8s.gcr.io/conformance-ppc64le:VERSION
$ make push VERSION={target_version} ARCH=s390x
# ---> k8s.gcr.io/conformance-s390x:VERSION
```
If you don't want to push the images, run `make` or `make build` instead
#### How to run tests
```
kubectl create -f conformance-e2e.yaml
```

View File

@@ -0,0 +1,37 @@
#!/usr/bin/env bash
# Copyright 2018 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.
set -o errexit
set -o nounset
set -o pipefail
kubectl create -f conformance-e2e.yaml
while true; do
STATUS=$(kubectl -n conformance get pods e2e-conformance-test -o jsonpath="{.status.phase}")
timestamp=$(date +"[%H:%M:%S]")
echo "$timestamp Pod status is: ${STATUS}"
if [[ "$STATUS" == "Succeeded" ]]; then
echo "$timestamp Done."
break
elif [[ "$STATUS" == "Failed" ]]; then
echo "$timestamp Failed."
kubectl -n conformance describe pods e2e-conformance-test || true
kubectl -n conformance logs e2e-conformance-test || true
exit 1
else
sleep 5
fi
done
echo "Please use 'kubectl logs -n conformance e2e-conformance-test' to view the results"

View File

@@ -0,0 +1,79 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: conformance
---
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
component: conformance
name: conformance-serviceaccount
namespace: conformance
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
labels:
component: conformance
name: conformance-serviceaccount-role
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: conformance-serviceaccount
subjects:
- kind: ServiceAccount
name: conformance-serviceaccount
namespace: conformance
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
component: conformance
name: conformance-serviceaccount
rules:
- apiGroups:
- '*'
resources:
- '*'
verbs:
- '*'
- nonResourceURLs:
- '/metrics'
- '/logs'
- '/logs/*'
verbs:
- 'get'
---
apiVersion: v1
kind: Pod
metadata:
name: e2e-conformance-test
namespace: conformance
spec:
containers:
- name: conformance-container
image: k8s.gcr.io/conformance-amd64:v1.14
imagePullPolicy: IfNotPresent
env:
- name: E2E_FOCUS
value: "\\[Conformance\\]"
- name: E2E_SKIP
value: ""
- name: E2E_PROVIDER
value: "skeleton"
- name: E2E_PARALLEL
value: "false"
- name: E2E_VERBOSITY
value: "4"
volumeMounts:
- name: output-volume
mountPath: /tmp/results
volumes:
- name: output-volume
hostPath:
path: /tmp/results
restartPolicy: Never
serviceAccountName: conformance-serviceaccount

View File

@@ -0,0 +1,27 @@
# Copyright 2019 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.
HOST_GOOS ?= $(shell go env GOOS)
HOST_GOARCH ?= $(shell go env GOARCH)
GO_BUILD ?= go build
.PHONY: all build clean
all: build
build:
$(GO_BUILD)
clean:
rm done e2e.log e2e.tar.gz go-runner

View File

@@ -0,0 +1,96 @@
/*
Copyright 2019 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 (
"fmt"
"io"
"os/exec"
"strings"
)
// getCmd uses the given environment to form the ginkgo command to run tests. It will
// set the stdout/stderr to the given writer.
func getCmd(env Getenver, w io.Writer) *exec.Cmd {
ginkgoArgs := []string{}
// The logic of the parallel env var impacting the skip value necessitates it
// being placed before the rest of the flag resolution.
skip := env.Getenv(skipEnvKey)
switch env.Getenv(parallelEnvKey) {
case "y", "Y", "true":
ginkgoArgs = append(ginkgoArgs, "--p")
if len(skip) == 0 {
skip = serialTestsRegexp
}
}
ginkgoArgs = append(ginkgoArgs, []string{
"--focus=" + env.Getenv(focusEnvKey),
"--skip=" + skip,
"--noColor=true",
}...)
extraArgs := []string{
"--disable-log-dump",
"--repo-root=/kubernetes",
"--provider=" + env.Getenv(providerEnvKey),
"--report-dir=" + env.Getenv(resultsDirEnvKey),
"--kubeconfig=" + env.Getenv(kubeconfigEnvKey),
}
// Extra args handling
sep := " "
if len(env.Getenv(extraArgsSeparaterEnvKey)) > 0 {
sep = env.Getenv(extraArgsSeparaterEnvKey)
}
if len(env.Getenv(extraGinkgoArgsEnvKey)) > 0 {
ginkgoArgs = append(ginkgoArgs, strings.Split(env.Getenv(extraGinkgoArgsEnvKey), sep)...)
}
if len(env.Getenv(extraArgsEnvKey)) > 0 {
fmt.Printf("sep is %q args are %q", sep, env.Getenv(extraArgsEnvKey))
fmt.Println("split", strings.Split(env.Getenv(extraArgsEnvKey), sep))
extraArgs = append(extraArgs, strings.Split(env.Getenv(extraArgsEnvKey), sep)...)
}
if len(env.Getenv(dryRunEnvKey)) > 0 {
ginkgoArgs = append(ginkgoArgs, "--dryRun=true")
}
args := []string{}
args = append(args, ginkgoArgs...)
args = append(args, env.Getenv(testBinEnvKey))
args = append(args, "--")
args = append(args, extraArgs...)
cmd := exec.Command(env.Getenv(ginkgoEnvKey), args...)
cmd.Stdout = w
cmd.Stderr = w
return cmd
}
// cmdInfo generates a useful look at what the command is for printing/debug.
func cmdInfo(cmd *exec.Cmd) string {
return fmt.Sprintf(
`Command env: %v
Run from directory: %v
Executable path: %v
Args (comma-delimited): %v`, cmd.Env, cmd.Dir, cmd.Path, strings.Join(cmd.Args, ","),
)
}

View File

@@ -0,0 +1,129 @@
/*
Copyright 2019 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 (
"os"
"reflect"
"testing"
)
func TestGetCmd(t *testing.T) {
testCases := []struct {
desc string
env Getenver
expectArgs []string
}{
{
desc: "Default",
env: &explicitEnv{
vals: map[string]string{
ginkgoEnvKey: "ginkgobin",
testBinEnvKey: "testbin",
},
},
expectArgs: []string{
"ginkgobin",
"--focus=", "--skip=",
"--noColor=true", "testbin", "--",
"--disable-log-dump", "--repo-root=/kubernetes",
"--provider=", "--report-dir=", "--kubeconfig=",
},
}, {
desc: "Filling in defaults",
env: &explicitEnv{
vals: map[string]string{
ginkgoEnvKey: "ginkgobin",
testBinEnvKey: "testbin",
focusEnvKey: "focus",
skipEnvKey: "skip",
providerEnvKey: "provider",
resultsDirEnvKey: "results",
kubeconfigEnvKey: "kubeconfig",
},
},
expectArgs: []string{
"ginkgobin",
"--focus=focus", "--skip=skip",
"--noColor=true", "testbin", "--",
"--disable-log-dump", "--repo-root=/kubernetes",
"--provider=provider", "--report-dir=results", "--kubeconfig=kubeconfig",
},
}, {
desc: "Parallel gets set and skips serial",
env: &explicitEnv{
vals: map[string]string{
ginkgoEnvKey: "ginkgobin",
testBinEnvKey: "testbin",
parallelEnvKey: "true",
},
},
expectArgs: []string{
"ginkgobin", "--p",
"--focus=", "--skip=\\[Serial\\]",
"--noColor=true", "testbin", "--",
"--disable-log-dump", "--repo-root=/kubernetes",
"--provider=", "--report-dir=", "--kubeconfig=",
},
}, {
desc: "Arbitrary options before and after double dash split by space",
env: &explicitEnv{
vals: map[string]string{
ginkgoEnvKey: "ginkgobin",
testBinEnvKey: "testbin",
extraArgsEnvKey: "--extra=1 --extra=2",
extraGinkgoArgsEnvKey: "--ginkgo1 --ginkgo2",
},
},
expectArgs: []string{
"ginkgobin", "--focus=", "--skip=",
"--noColor=true", "--ginkgo1", "--ginkgo2",
"testbin", "--",
"--disable-log-dump", "--repo-root=/kubernetes",
"--provider=", "--report-dir=", "--kubeconfig=",
"--extra=1", "--extra=2",
},
}, {
desc: "Arbitrary options can be split by other tokens",
env: &explicitEnv{
vals: map[string]string{
ginkgoEnvKey: "ginkgobin",
testBinEnvKey: "testbin",
extraArgsEnvKey: "--extra=value with spaces:--extra=value with % anything!$$",
extraGinkgoArgsEnvKey: `--ginkgo='with "quotes" and ':--ginkgo2=true$(foo)`,
extraArgsSeparaterEnvKey: ":",
},
},
expectArgs: []string{
"ginkgobin", "--focus=", "--skip=",
"--noColor=true", `--ginkgo='with "quotes" and '`, "--ginkgo2=true$(foo)",
"testbin", "--",
"--disable-log-dump", "--repo-root=/kubernetes",
"--provider=", "--report-dir=", "--kubeconfig=",
"--extra=value with spaces", "--extra=value with % anything!$$",
},
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
c := getCmd(tc.env, os.Stdout)
if !reflect.DeepEqual(c.Args, tc.expectArgs) {
t.Errorf("Expected args %q but got %q", tc.expectArgs, c.Args)
}
})
}
}

View File

@@ -0,0 +1,67 @@
/*
Copyright 2019 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
const (
// resultsTarballName is the name of the tarball we create with all the results.
resultsTarballName = "e2e.tar.gz"
// doneFileName is the name of the file that signals to the Sonobuoy worker we are
// done. The file should contain the path to the results file.
doneFileName = "done"
// resultsDirEnvKey is the env var which stores which directory to put the donefile
// and results into. It is a shared, mounted volume between the plugin and Sonobuoy.
resultsDirEnvKey = "RESULTS_DIR"
// logFileName is the name of the file which stdout is tee'd to.
logFileName = "e2e.log"
// Misc env vars which were explicitly supported prior to the go runner.
dryRunEnvKey = "E2E_DRYRUN"
parallelEnvKey = "E2E_PARALLEL"
focusEnvKey = "E2E_FOCUS"
skipEnvKey = "E2E_SKIP"
providerEnvKey = "E2E_PROVIDER"
kubeconfigEnvKey = "KUBECONFIG"
ginkgoEnvKey = "GINKGO_BIN"
testBinEnvKey = "TEST_BIN"
// extraGinkgoArgsEnvKey, if set, will is a list of other arguments to pass to ginkgo.
// These are passed before the test binary and include things like `--afterSuiteHook`.
extraGinkgoArgsEnvKey = "E2E_EXTRA_GINKGO_ARGS"
// extraArgsEnvKey, if set, will is a list of other arguments to pass to the tests.
// These are passed after the `--` and include things like `--provider`.
extraArgsEnvKey = "E2E_EXTRA_ARGS"
// extraArgsSeparaterEnvKey specifies how to split the extra args values. If unset,
// it will default to splitting by spaces.
extraArgsSeparaterEnvKey = "E2E_EXTRA_ARGS_SEP"
defaultSkip = ""
defaultFocus = "\\[Conformance\\]"
defaultProvider = "local"
defaultParallel = "1"
defaultResultsDir = "/tmp/results"
defaultGinkgoBinary = "/usr/local/bin/ginkgo"
defaultTestBinary = "/usr/local/bin/e2e.test"
// serialTestsRegexp is the default skip value if running in parallel. Will not
// override an explicit E2E_SKIP value.
serialTestsRegexp = "\\[Serial\\]"
)

View File

@@ -0,0 +1,66 @@
/*
Copyright 2019 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 (
"os"
)
// Getenver is the interface we use to mock out the env for easier testing. OS env
// vars can't be as easily tested since internally it uses sync.Once.
type Getenver interface {
Getenv(string) string
}
// osEnv uses the actual os.Getenv methods to lookup values.
type osEnv struct{}
// Getenv gets the value of the requested environment variable.
func (*osEnv) Getenv(s string) string {
return os.Getenv(s)
}
// explicitEnv uses a map instead of os.Getenv methods to lookup values.
type explicitEnv struct {
vals map[string]string
}
// Getenv returns the value of the requested environment variable (in this
// implementation, really just a map lookup).
func (e *explicitEnv) Getenv(s string) string {
return e.vals[s]
}
// defaultOSEnv uses a Getenver to lookup values but if it does
// not have that value, it falls back to its internal set of defaults.
type defaultEnver struct {
firstChoice Getenver
defaults map[string]string
}
// Getenv returns the value of the environment variable or its default if unset.
func (e *defaultEnver) Getenv(s string) string {
v := e.firstChoice.Getenv(s)
if len(v) == 0 {
return e.defaults[s]
}
return v
}
func envWithDefaults(defaults map[string]string) Getenver {
return &defaultEnver{firstChoice: &osEnv{}, defaults: defaults}
}

View File

@@ -0,0 +1,77 @@
/*
Copyright 2019 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 (
"os"
"testing"
)
func TestEnv(t *testing.T) {
testCases := []struct {
desc string
preHook func()
env Getenver
expect map[string]string
}{
{
desc: "OS env",
env: &osEnv{},
preHook: func() {
os.Setenv("key1", "1")
},
expect: map[string]string{"key1": "1"},
}, {
desc: "OS env falls defaults to empty",
env: &osEnv{},
preHook: func() {
os.Unsetenv("key1")
},
expect: map[string]string{"key1": ""},
}, {
desc: "First choice of env respected",
env: &defaultEnver{
firstChoice: &explicitEnv{
vals: map[string]string{
"key1": "1",
},
},
defaults: map[string]string{
"key1": "default1",
"key2": "default2",
},
},
expect: map[string]string{
"key1": "1",
"key2": "default2",
},
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
for k, expectVal := range tc.expect {
if tc.preHook != nil {
tc.preHook()
}
val := tc.env.Getenv(k)
if val != expectVal {
t.Errorf("Expected %q but got %q", expectVal, val)
}
}
})
}
}

View File

@@ -0,0 +1,125 @@
/*
Copyright 2019 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 (
"io"
"io/ioutil"
"log"
"os"
"os/signal"
"path/filepath"
"strings"
"github.com/pkg/errors"
)
func main() {
if strings.Contains(os.Args[0], "run_e2e.sh") || strings.Contains(os.Args[0], "gorunner") {
log.Print("warn: calling test with e2e.test is deprecated and will be removed in 1.25, please rely on container manifest to invoke executable")
}
env := envWithDefaults(map[string]string{
resultsDirEnvKey: defaultResultsDir,
skipEnvKey: defaultSkip,
focusEnvKey: defaultFocus,
providerEnvKey: defaultProvider,
parallelEnvKey: defaultParallel,
ginkgoEnvKey: defaultGinkgoBinary,
testBinEnvKey: defaultTestBinary,
})
if err := configureAndRunWithEnv(env); err != nil {
log.Fatal(err)
}
}
// configureAndRunWithEnv uses the given environment to configure and then start the test run.
// It will handle TERM signals gracefully and kill the test process and will
// save the logs/results to the location specified via the RESULTS_DIR environment
// variable.
func configureAndRunWithEnv(env Getenver) error {
// Ensure we save results regardless of other errors. This helps any
// consumer who may be polling for the results.
resultsDir := env.Getenv(resultsDirEnvKey)
defer saveResults(resultsDir)
// Print the output to stdout and a logfile which will be returned
// as part of the results tarball.
logFilePath := filepath.Join(resultsDir, logFileName)
logFile, err := os.Create(logFilePath)
if err != nil {
return errors.Wrapf(err, "failed to create log file %v", logFilePath)
}
mw := io.MultiWriter(os.Stdout, logFile)
cmd := getCmd(env, mw)
log.Printf("Running command:\n%v\n", cmdInfo(cmd))
err = cmd.Start()
if err != nil {
return errors.Wrap(err, "starting command")
}
// Handle signals and shutdown process gracefully.
go setupSigHandler(cmd.Process.Pid)
return errors.Wrap(cmd.Wait(), "running command")
}
// setupSigHandler will kill the process identified by the given PID if it
// gets a TERM signal.
func setupSigHandler(pid int) {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
// Block until a signal is received.
log.Println("Now listening for interrupts")
s := <-c
log.Printf("Got signal: %v. Shutting down test process (PID: %v)\n", s, pid)
p, err := os.FindProcess(pid)
if err != nil {
log.Printf("Could not find process %v to shut down.\n", pid)
return
}
if err := p.Signal(s); err != nil {
log.Printf("Failed to signal test process to terminate: %v\n", err)
return
}
log.Printf("Signalled process %v to terminate successfully.\n", pid)
}
// saveResults will tar the results directory and write the resulting tarball path
// into the donefile.
func saveResults(resultsDir string) error {
log.Printf("Saving results at %v\n", resultsDir)
err := tarDir(resultsDir, filepath.Join(resultsDir, resultsTarballName))
if err != nil {
return errors.Wrapf(err, "tar directory %v", resultsDir)
}
doneFile := filepath.Join(resultsDir, doneFileName)
resultsTarball := filepath.Join(resultsDir, resultsTarballName)
resultsTarball, err = filepath.Abs(resultsTarball)
if err != nil {
return errors.Wrapf(err, "failed to find absolute path for %v", resultsTarball)
}
return errors.Wrap(
ioutil.WriteFile(doneFile, []byte(resultsTarball), os.FileMode(0777)),
"writing donefile",
)
}

View File

@@ -0,0 +1,83 @@
/*
Copyright 2019 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 (
"archive/tar"
"compress/gzip"
"io"
"os"
"path/filepath"
"strings"
"github.com/pkg/errors"
)
// tarDir takes a source and variable writers and walks 'source' writing each file
// found to the tar writer.
func tarDir(dir, outpath string) error {
// ensure the src actually exists before trying to tar it
if _, err := os.Stat(dir); err != nil {
return errors.Wrapf(err, "tar unable to stat directory %v", dir)
}
outfile, err := os.Create(outpath)
if err != nil {
return errors.Wrapf(err, "creating tarball %v", outpath)
}
defer outfile.Close()
gzw := gzip.NewWriter(outfile)
defer gzw.Close()
tw := tar.NewWriter(gzw)
defer tw.Close()
return filepath.Walk(dir, func(file string, fi os.FileInfo, err error) error {
// Return on any error.
if err != nil {
return err
}
// Only write regular files and don't include the archive itself.
if !fi.Mode().IsRegular() || filepath.Join(dir, fi.Name()) == outpath {
return nil
}
// Create a new dir/file header.
header, err := tar.FileInfoHeader(fi, fi.Name())
if err != nil {
return errors.Wrapf(err, "creating file info header %v", fi.Name())
}
// Update the name to correctly reflect the desired destination when untaring.
header.Name = strings.TrimPrefix(strings.Replace(file, dir, "", -1), string(filepath.Separator))
if err := tw.WriteHeader(header); err != nil {
return errors.Wrapf(err, "writing header for tarball %v", header.Name)
}
// Open files, copy into tarfile, and close.
f, err := os.Open(file)
if err != nil {
return errors.Wrapf(err, "opening file %v for writing into tarball", file)
}
defer f.Close()
_, err = io.Copy(tw, f)
return errors.Wrapf(err, "creating file %v contents into tarball", file)
})
}

View File

@@ -0,0 +1,150 @@
/*
Copyright 2019 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 (
"archive/tar"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"github.com/pkg/errors"
)
func TestTar(t *testing.T) {
tmp, err := ioutil.TempDir("", "testtar")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmp)
if err := os.Mkdir(filepath.Join(tmp, "subdir"), os.FileMode(0755)); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(filepath.Join(tmp, "file1"), []byte(`file1 data`), os.FileMode(0644)); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(filepath.Join(tmp, "file2"), []byte(`file2 data`), os.FileMode(0644)); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(filepath.Join(tmp, "subdir", "file4"), []byte(`file4 data`), os.FileMode(0644)); err != nil {
t.Fatal(err)
}
testCases := []struct {
desc string
dir string
outpath string
expectErr string
expect map[string]string
}{
{
desc: "Contents preserved and no self-reference",
dir: tmp,
outpath: filepath.Join(tmp, "out.tar.gz"),
expect: map[string]string{
"file1": "file1 data",
"file2": "file2 data",
"subdir/file4": "file4 data",
},
}, {
desc: "Errors if directory does not exist",
dir: filepath.Join(tmp, "does-not-exist"),
outpath: filepath.Join(tmp, "out.tar.gz"),
expectErr: "tar unable to stat directory",
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
err := tarDir(tc.dir, tc.outpath)
if err == nil {
defer os.Remove(tc.outpath)
}
switch {
case err != nil && len(tc.expectErr) == 0:
t.Fatalf("Expected nil error but got %q", err)
case err != nil && len(tc.expectErr) > 0:
if !strings.Contains(fmt.Sprint(err), tc.expectErr) {
t.Errorf("Expected error \n\t%q\nbut got\n\t%q", tc.expectErr, err)
}
return
case err == nil && len(tc.expectErr) > 0:
t.Fatalf("Expected error %q but got nil", tc.expectErr)
default:
// No error
}
data, err := readAllTar(tc.outpath)
if err != nil {
t.Fatalf("Failed to read tarball: %v", err)
}
if !reflect.DeepEqual(data, tc.expect) {
t.Errorf("Expected data %v but got %v", tc.expect, data)
}
})
}
}
// readAllTar walks all of the files in the archive. It returns a map
// of filenames and their contents and any error encountered.
func readAllTar(tarPath string) (map[string]string, error) {
tarPath, err := filepath.Abs(tarPath)
if err != nil {
return nil, err
}
fileReader, err := os.Open(tarPath)
if err != nil {
return nil, err
}
defer fileReader.Close()
gzStream, err := gzip.NewReader(fileReader)
if err != nil {
return nil, errors.Wrap(err, "couldn't uncompress reader")
}
defer gzStream.Close()
// Open and iterate through the files in the archive.
tr := tar.NewReader(gzStream)
fileData := map[string]string{}
for {
hdr, err := tr.Next()
if err == io.EOF {
break // End of archive
}
if err != nil {
return nil, err
}
b, err := ioutil.ReadAll(tr)
if err != nil {
return nil, err
}
fileData[filepath.ToSlash(hdr.Name)] = string(b)
}
return fileData, nil
}

View File

@@ -0,0 +1,73 @@
#!/usr/bin/env bash
# Copyright 2018 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.
set -o errexit
set -o nounset
set -o pipefail
# Shutdown the tests gracefully then save the results
shutdown () {
E2E_SUITE_PID=$(pgrep e2e.test)
echo "sending TERM to ${E2E_SUITE_PID}"
kill -s TERM "${E2E_SUITE_PID}"
# Kind of a hack to wait for this pid to finish.
# Since it's not a child of this shell we cannot use wait.
tail --pid "${E2E_SUITE_PID}" -f /dev/null
saveResults
}
saveResults() {
cd "${RESULTS_DIR}" || exit
tar -czf e2e.tar.gz ./*
# mark the done file as a termination notice.
echo -n "${RESULTS_DIR}/e2e.tar.gz" > "${RESULTS_DIR}/done"
}
# Optional Golang runner alternative to the bash script.
# Entry provided via env var to simplify invocation.
if [[ -n ${E2E_USE_GO_RUNNER:-} ]]; then
set -x
/gorunner && ret=0 || ret=$?
exit ${ret}
fi
# We get the TERM from kubernetes and handle it gracefully
trap shutdown TERM
ginkgo_args=()
if [[ -n ${E2E_DRYRUN:-} ]]; then
ginkgo_args+=("--dryRun=true")
fi
case ${E2E_PARALLEL} in
'y'|'Y'|'true')
# The flag '--p' will automatically detect the optimal number of ginkgo nodes.
ginkgo_args+=("--p")
# Skip serial tests if parallel mode is enabled.
E2E_SKIP="\\[Serial\\]|${E2E_SKIP}" ;;
esac
ginkgo_args+=(
"--focus=${E2E_FOCUS}"
"--skip=${E2E_SKIP}"
"--noColor=true"
)
set -x
/usr/local/bin/ginkgo "${ginkgo_args[@]}" /usr/local/bin/e2e.test -- --disable-log-dump --repo-root=/kubernetes --provider="${E2E_PROVIDER}" --report-dir="${RESULTS_DIR}" --kubeconfig="${KUBECONFIG}" -v="${E2E_VERBOSITY}" > >(tee "${RESULTS_DIR}"/e2e.log) && ret=0 || ret=$?
set +x
saveResults
exit ${ret}