Add e2e tests for nginx
This commit is contained in:
parent
ca0df3a271
commit
123e0d6c05
23 changed files with 806 additions and 162 deletions
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -25,3 +25,10 @@ Session.vim
|
||||||
# coverage artifacts
|
# coverage artifacts
|
||||||
.coverprofile
|
.coverprofile
|
||||||
/gover.coverprofile
|
/gover.coverprofile
|
||||||
|
|
||||||
|
# skip go tests
|
||||||
|
*/**/*.test
|
||||||
|
|
||||||
|
# skip e2e binaries
|
||||||
|
minikube
|
||||||
|
kubectl
|
||||||
|
|
|
@ -9,7 +9,7 @@ notifications:
|
||||||
email: true
|
email: true
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.8.3
|
- 1.9
|
||||||
|
|
||||||
go_import_path: k8s.io/ingress
|
go_import_path: k8s.io/ingress
|
||||||
|
|
||||||
|
@ -18,9 +18,10 @@ env:
|
||||||
# to add additional secure variables:
|
# to add additional secure variables:
|
||||||
# docker run --rm caktux/travis-cli encrypt key=value -r kubernetes/ingress
|
# docker run --rm caktux/travis-cli encrypt key=value -r kubernetes/ingress
|
||||||
- RELEASE="ci-${TRAVIS_BUILD_ID}"
|
- RELEASE="ci-${TRAVIS_BUILD_ID}"
|
||||||
|
- DOCKER=docker
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- export PATH=$PATH:$PWD/hack/e2e-internal/
|
- export PATH=$PATH:$PWD/controllers/nginx/e2e/e2e-internal
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
include:
|
include:
|
||||||
|
@ -34,4 +35,6 @@ jobs:
|
||||||
- go get github.com/modocache/gover
|
- go get github.com/modocache/gover
|
||||||
- if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi
|
- if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi
|
||||||
- make cover
|
- make cover
|
||||||
#- make test-e2e
|
- stage: e2e
|
||||||
|
script:
|
||||||
|
- make test-e2e
|
||||||
|
|
19
Makefile
19
Makefile
|
@ -2,15 +2,6 @@ all: fmt lint vet
|
||||||
|
|
||||||
BUILDTAGS=
|
BUILDTAGS=
|
||||||
|
|
||||||
# building inside travis generates a custom version of the
|
|
||||||
# backends in order to run e2e tests agains the build.
|
|
||||||
ifdef TRAVIS_BUILD_ID
|
|
||||||
RELEASE := ci-build-${TRAVIS_BUILD_ID}
|
|
||||||
endif
|
|
||||||
|
|
||||||
# 0.0 shouldn't clobber any release builds
|
|
||||||
RELEASE?=0.0
|
|
||||||
|
|
||||||
# by default build a linux version
|
# by default build a linux version
|
||||||
GOOS?=linux
|
GOOS?=linux
|
||||||
|
|
||||||
|
@ -23,7 +14,7 @@ endif
|
||||||
# base package. It contains the common and backends code
|
# base package. It contains the common and backends code
|
||||||
PKG := "k8s.io/ingress"
|
PKG := "k8s.io/ingress"
|
||||||
|
|
||||||
GO_LIST_FILES=$(shell go list ${PKG}/... | grep -v vendor | grep -v -e "test/e2e")
|
GO_LIST_FILES=$(shell go list ${PKG}/... | grep -v vendor | grep -v -e "e2e")
|
||||||
|
|
||||||
.PHONY: fmt
|
.PHONY: fmt
|
||||||
fmt:
|
fmt:
|
||||||
|
@ -38,8 +29,8 @@ test:
|
||||||
@go test -v -race -tags "$(BUILDTAGS) cgo" ${GO_LIST_FILES}
|
@go test -v -race -tags "$(BUILDTAGS) cgo" ${GO_LIST_FILES}
|
||||||
|
|
||||||
.PHONY: test-e2e
|
.PHONY: test-e2e
|
||||||
test-e2e: ginkgo
|
test-e2e:
|
||||||
@go run hack/e2e.go -v --up --test --down
|
make -C controllers/nginx test-e2e
|
||||||
|
|
||||||
.PHONY: cover
|
.PHONY: cover
|
||||||
cover:
|
cover:
|
||||||
|
@ -70,7 +61,3 @@ docker-push:
|
||||||
.PHONE: release
|
.PHONE: release
|
||||||
release:
|
release:
|
||||||
make -C controllers/nginx release
|
make -C controllers/nginx release
|
||||||
|
|
||||||
.PHONY: ginkgo
|
|
||||||
ginkgo:
|
|
||||||
go get github.com/onsi/ginkgo/ginkgo
|
|
||||||
|
|
|
@ -10,6 +10,8 @@ DOCKER?=gcloud docker --
|
||||||
SED_I?=sed -i
|
SED_I?=sed -i
|
||||||
GOHOSTOS ?= $(shell go env GOHOSTOS)
|
GOHOSTOS ?= $(shell go env GOHOSTOS)
|
||||||
|
|
||||||
|
SKIP_TESTS?=
|
||||||
|
|
||||||
ifeq ($(GOHOSTOS),darwin)
|
ifeq ($(GOHOSTOS),darwin)
|
||||||
SED_I=sed -i ''
|
SED_I=sed -i ''
|
||||||
endif
|
endif
|
||||||
|
@ -120,11 +122,14 @@ lint:
|
||||||
|
|
||||||
test: fmt lint vet
|
test: fmt lint vet
|
||||||
@echo "+ $@"
|
@echo "+ $@"
|
||||||
@go test -v -race -tags "$(BUILDTAGS) cgo" $(shell go list ${PKG}/... | grep -v vendor)
|
@go test -v -race -tags "$(BUILDTAGS) cgo" $(shell go list ${PKG}/... | grep -v vendor |grep -v e2e)
|
||||||
|
|
||||||
|
test-e2e: sub-container-amd64
|
||||||
|
@TAG=${TAG} IMAGE=$(MULTI_ARCH_IMG) go run e2e/e2e.go --files=e2e/nginx-ingress-controller.yaml,e2e/default-backend.yaml --verbose --up --test --down
|
||||||
|
|
||||||
cover:
|
cover:
|
||||||
@echo "+ $@"
|
@echo "+ $@"
|
||||||
@go list -f '{{if len .TestGoFiles}}"go test -coverprofile={{.Dir}}/.coverprofile {{.ImportPath}}"{{end}}' $(shell go list ${PKG}/... | grep -v vendor) | xargs -L 1 sh -c
|
@go list -f '{{if len .TestGoFiles}}"go test -skipTest "${SKIP_TESTS}" -coverprofile={{.Dir}}/.coverprofile {{.ImportPath}}"{{end}}' $(shell go list ${PKG}/... | grep -v vendor) | xargs -L 1 sh -c
|
||||||
gover
|
gover
|
||||||
goveralls -coverprofile=gover.coverprofile -service travis-ci -repotoken ${COVERALLS_TOKEN}
|
goveralls -coverprofile=gover.coverprofile -service travis-ci -repotoken ${COVERALLS_TOKEN}
|
||||||
|
|
||||||
|
|
51
controllers/nginx/e2e/default-backend.yaml
Normal file
51
controllers/nginx/e2e/default-backend.yaml
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: default-http-backend
|
||||||
|
labels:
|
||||||
|
k8s-app: default-http-backend
|
||||||
|
namespace: kube-system
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
k8s-app: default-http-backend
|
||||||
|
spec:
|
||||||
|
terminationGracePeriodSeconds: 60
|
||||||
|
containers:
|
||||||
|
- name: default-http-backend
|
||||||
|
# Any image is permissable as long as:
|
||||||
|
# 1. It serves a 404 page at /
|
||||||
|
# 2. It serves 200 on a /healthz endpoint
|
||||||
|
image: gcr.io/google_containers/defaultbackend:1.0
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: 8080
|
||||||
|
scheme: HTTP
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
timeoutSeconds: 5
|
||||||
|
ports:
|
||||||
|
- containerPort: 8080
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: 10m
|
||||||
|
memory: 20Mi
|
||||||
|
requests:
|
||||||
|
cpu: 10m
|
||||||
|
memory: 20Mi
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: default-http-backend
|
||||||
|
namespace: kube-system
|
||||||
|
labels:
|
||||||
|
k8s-app: default-http-backend
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 8080
|
||||||
|
selector:
|
||||||
|
k8s-app: default-http-backend
|
6
controllers/nginx/e2e/e2e-internal/e2e-down.sh
Executable file
6
controllers/nginx/e2e/e2e-internal/e2e-down.sh
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
. ./e2e/e2e-internal/e2e-env.sh
|
||||||
|
|
||||||
|
echo "Destroying running e2e cluster..."
|
||||||
|
${MINIKUBE} --profile ${MINIKUBE_PROFILE} delete || echo "Cluster already destroyed"
|
33
controllers/nginx/e2e/e2e-internal/e2e-env.sh
Executable file
33
controllers/nginx/e2e/e2e-internal/e2e-env.sh
Executable file
|
@ -0,0 +1,33 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
export MINIKUBE_VERSION=0.22.0
|
||||||
|
export K8S_VERSION=v1.7.5
|
||||||
|
|
||||||
|
export PWD=`pwd`
|
||||||
|
export BASEDIR="$(dirname ${BASH_SOURCE})"
|
||||||
|
export KUBECTL="${BASEDIR}/kubectl"
|
||||||
|
export MINIKUBE="${BASEDIR}/minikube"
|
||||||
|
export GOOS="${GOOS:-linux}"
|
||||||
|
|
||||||
|
export MINIKUBE_WANTUPDATENOTIFICATION=false
|
||||||
|
export MINIKUBE_WANTREPORTERRORPROMPT=false
|
||||||
|
export MINIKUBE_HOME=$HOME
|
||||||
|
export CHANGE_MINIKUBE_NONE_USER=true
|
||||||
|
|
||||||
|
export KUBECONFIG=$HOME/.kube/config
|
||||||
|
|
||||||
|
export MINIKUBE_PROFILE="ingress-e2e"
|
||||||
|
|
||||||
|
export PATH=$PATH:$BASEDIR
|
||||||
|
|
||||||
|
if [ ! -e ${KUBECTL} ]; then
|
||||||
|
echo "kubectl binary is missing. downloading..."
|
||||||
|
curl -sSL http://storage.googleapis.com/kubernetes-release/release/${K8S_VERSION}/bin/${GOOS}/amd64/kubectl -o ${KUBECTL}
|
||||||
|
chmod u+x ${KUBECTL}
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -e ${MINIKUBE} ]; then
|
||||||
|
echo "minikube binary is missing. downloading..."
|
||||||
|
curl -sSLo ${MINIKUBE} https://storage.googleapis.com/minikube/releases/v${MINIKUBE_VERSION}/minikube-linux-amd64
|
||||||
|
chmod +x ${MINIKUBE}
|
||||||
|
fi
|
7
controllers/nginx/e2e/e2e-internal/e2e-status.sh
Executable file
7
controllers/nginx/e2e/e2e-internal/e2e-status.sh
Executable file
|
@ -0,0 +1,7 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -eof pipefail
|
||||||
|
|
||||||
|
. ./e2e/e2e-internal/e2e-env.sh
|
||||||
|
|
||||||
|
${MINIKUBE} --profile ${MINIKUBE_PROFILE} status
|
28
controllers/nginx/e2e/e2e-internal/e2e-up.sh
Executable file
28
controllers/nginx/e2e/e2e-internal/e2e-up.sh
Executable file
|
@ -0,0 +1,28 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -eof pipefail
|
||||||
|
|
||||||
|
. ./e2e/e2e-internal/e2e-env.sh
|
||||||
|
|
||||||
|
mkdir -p $HOME/.kube
|
||||||
|
touch $KUBECONFIG
|
||||||
|
|
||||||
|
if [ "$TRAVIS" = true ] ; then
|
||||||
|
sudo -E ${MINIKUBE} --profile ${MINIKUBE_PROFILE} start --vm-driver=none
|
||||||
|
else
|
||||||
|
${MINIKUBE} --profile ${MINIKUBE_PROFILE} start
|
||||||
|
fi
|
||||||
|
|
||||||
|
# this for loop waits until kubectl can access the api server that minikube has created
|
||||||
|
for i in {1..150} # timeout for 5 minutes
|
||||||
|
do
|
||||||
|
$KUBECTL get po &> /dev/null
|
||||||
|
if [ $? -ne 1 ]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 10
|
||||||
|
done
|
||||||
|
|
||||||
|
sleep 60
|
||||||
|
|
||||||
|
echo "Kubernetes started"
|
15
controllers/nginx/e2e/e2e-internal/run-e2e.sh
Executable file
15
controllers/nginx/e2e/e2e-internal/run-e2e.sh
Executable file
|
@ -0,0 +1,15 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -eof pipefail
|
||||||
|
|
||||||
|
. ./e2e/e2e-internal/e2e-env.sh
|
||||||
|
|
||||||
|
echo "Creating test tag for image $IMAGE:$TAG"
|
||||||
|
docker tag $IMAGE:$TAG $IMAGE:test
|
||||||
|
|
||||||
|
echo "Uploading test image to minikube"
|
||||||
|
dockerenv=$(${MINIKUBE} --profile ${MINIKUBE_PROFILE} docker-env | sed 's/export//g' | sed 's/^#.*$//g' | sed 's/"//g')
|
||||||
|
docker save $IMAGE:test | env -i $dockerenv docker load
|
||||||
|
|
||||||
|
echo "Running tests..."
|
||||||
|
go test -v k8s.io/ingress/controllers/nginx/e2e/... -run ^TestIngressSuite$ --args --alsologtostderr --v=10
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2014 The Kubernetes Authors.
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -31,14 +31,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
build = flag.Bool("build", true, "Build the backends images indicated by the env var BACKENDS required to run e2e tests.")
|
|
||||||
up = flag.Bool("up", true, "Creates a kubernetes cluster using hyperkube (containerized kubelet).")
|
up = flag.Bool("up", true, "Creates a kubernetes cluster using hyperkube (containerized kubelet).")
|
||||||
down = flag.Bool("down", true, "destroys the created cluster.")
|
down = flag.Bool("down", true, "destroys the created cluster.")
|
||||||
test = flag.Bool("test", true, "Run Ginkgo tests.")
|
test = flag.Bool("test", true, "Run tests.")
|
||||||
dump = flag.String("dump", "", "If set, dump cluster logs to this location on test or cluster-up failure")
|
dump = flag.String("dump", "", "If set, dump cluster logs to this location on test or cluster-up failure")
|
||||||
testArgs = flag.String("test-args", "", "Space-separated list of arguments to pass to Ginkgo test runner.")
|
testArgs = flag.String("test-args", "", "Space-separated list of arguments to pass to the test runner.")
|
||||||
deployment = flag.String("deployment", "bash", "up/down mechanism")
|
deployment = flag.String("deployment", "bash", "up/down mechanism")
|
||||||
verbose = flag.Bool("v", false, "If true, print all command output.")
|
verbose = flag.Bool("verbose", false, "If true, print all command output.")
|
||||||
|
files = flag.String("files", "", "Path to a file/S descriptor that will create an Ingress controller")
|
||||||
)
|
)
|
||||||
|
|
||||||
func appendError(errs []error, err error) []error {
|
func appendError(errs []error, err error) []error {
|
||||||
|
@ -57,7 +57,7 @@ func validWorkingDirectory() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to convert %s to an absolute path: %v", cwd, err)
|
return fmt.Errorf("failed to convert %s to an absolute path: %v", cwd, err)
|
||||||
}
|
}
|
||||||
if !strings.Contains(filepath.Base(acwd), "ingress") {
|
if !strings.Contains(filepath.Base(acwd), "nginx") {
|
||||||
return fmt.Errorf("must run from git root directory: %v", acwd)
|
return fmt.Errorf("must run from git root directory: %v", acwd)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -139,14 +139,12 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func run(deploy deployer) error {
|
func run(deploy deployer) error {
|
||||||
if *dump != "" {
|
if *files == "" {
|
||||||
defer writeXML(time.Now())
|
return fmt.Errorf("missing required flag --files")
|
||||||
}
|
}
|
||||||
|
|
||||||
if *build {
|
if *dump != "" {
|
||||||
if err := xmlWrap("Build", Build); err != nil {
|
defer writeXML(time.Now())
|
||||||
return fmt.Errorf("error building: %s", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if *up {
|
if *up {
|
||||||
|
@ -172,7 +170,7 @@ func run(deploy deployer) error {
|
||||||
return fmt.Errorf("starting e2e cluster: %s", err)
|
return fmt.Errorf("starting e2e cluster: %s", err)
|
||||||
}
|
}
|
||||||
if *dump != "" {
|
if *dump != "" {
|
||||||
cmd := exec.Command("./cluster/kubectl.sh", "--match-server-version=false", "get", "nodes", "-oyaml")
|
cmd := kubectlCmd("get", "nodes", "-oyaml")
|
||||||
b, err := cmd.CombinedOutput()
|
b, err := cmd.CombinedOutput()
|
||||||
if *verbose {
|
if *verbose {
|
||||||
log.Printf("kubectl get nodes:\n%s", string(b))
|
log.Printf("kubectl get nodes:\n%s", string(b))
|
||||||
|
@ -187,17 +185,22 @@ func run(deploy deployer) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Printf("deploying ingress controller")
|
||||||
|
if err := deploy.SetupController(*files); err != nil {
|
||||||
|
errs = appendError(errs, err)
|
||||||
|
}
|
||||||
|
|
||||||
if *test {
|
if *test {
|
||||||
if err := xmlWrap("IsUp", deploy.IsUp); err != nil {
|
if err := xmlWrap("IsUp", deploy.IsUp); err != nil {
|
||||||
errs = appendError(errs, err)
|
errs = appendError(errs, err)
|
||||||
} else {
|
} else {
|
||||||
errs = appendError(errs, Test())
|
errs = appendError(errs, runTests())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(errs) > 0 && *dump != "" {
|
if len(errs) > 0 && *dump != "" {
|
||||||
errs = appendError(errs, xmlWrap("DumpClusterLogs", func() error {
|
errs = appendError(errs, xmlWrap("DumpClusterLogs", func() error {
|
||||||
return DumpClusterLogs(*dump)
|
return dumpClusterLogs(*dump)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,24 +211,14 @@ func run(deploy deployer) error {
|
||||||
if len(errs) != 0 {
|
if len(errs) != 0 {
|
||||||
return fmt.Errorf("encountered %d errors: %v", len(errs), errs)
|
return fmt.Errorf("encountered %d errors: %v", len(errs), errs)
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Build() error {
|
|
||||||
// The build-release script needs stdin to ask the user whether
|
|
||||||
// it's OK to download the docker image.
|
|
||||||
cmd := exec.Command("make", "docker-build")
|
|
||||||
cmd.Stdin = os.Stdin
|
|
||||||
if err := finishRunning("build-release", cmd); err != nil {
|
|
||||||
return fmt.Errorf("error building: %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type deployer interface {
|
type deployer interface {
|
||||||
Up() error
|
Up() error
|
||||||
IsUp() error
|
IsUp() error
|
||||||
SetupKubecfg() error
|
SetupController(p string) error
|
||||||
Down() error
|
Down() error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,31 +234,36 @@ func getDeployer() (deployer, error) {
|
||||||
type bash struct{}
|
type bash struct{}
|
||||||
|
|
||||||
func (b bash) Up() error {
|
func (b bash) Up() error {
|
||||||
return finishRunning("up", exec.Command("./hack/e2e-internal/e2e-up.sh"))
|
return finishRunning("up", exec.Command("./e2e/e2e-internal/e2e-up.sh"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b bash) IsUp() error {
|
func (b bash) IsUp() error {
|
||||||
return finishRunning("get status", exec.Command("./hack/e2e-internal/e2e-status.sh"))
|
return finishRunning("get status", exec.Command("./e2e/e2e-internal/e2e-status.sh"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b bash) SetupKubecfg() error {
|
func (b bash) SetupController(p string) error {
|
||||||
|
files := strings.Split(p, ",")
|
||||||
|
for _, f := range files {
|
||||||
|
err := finishRunning("setup controller", kubectlCmd("create", "-f", f))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b bash) Down() error {
|
func (b bash) Down() error {
|
||||||
return finishRunning("teardown", exec.Command("./hack/e2e-internal/e2e-down.sh"))
|
return finishRunning("teardown", exec.Command("./e2e/e2e-internal/e2e-down.sh"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func DumpClusterLogs(location string) error {
|
func dumpClusterLogs(location string) error {
|
||||||
log.Printf("Dumping cluster logs to: %v", location)
|
log.Printf("Dumping cluster logs to: %v", location)
|
||||||
return finishRunning("dump cluster logs", exec.Command("./hack/e2e-internal/log-dump.sh", location))
|
return finishRunning("dump cluster logs", exec.Command("./e2e/e2e-internal/log-dump.sh", location))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test() error {
|
func runTests() error {
|
||||||
if *testArgs == "" {
|
return finishRunning("Ingress tests", exec.Command("./e2e/e2e-internal/run-e2e.sh", strings.Fields(*testArgs)...))
|
||||||
*testArgs = "--ginkgo.focus=\\[Feature:Ingress\\]"
|
|
||||||
}
|
|
||||||
return finishRunning("Ginkgo tests", exec.Command("./hack/e2e-internal/ginkgo-e2e.sh", strings.Fields(*testArgs)...))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func finishRunning(stepName string, cmd *exec.Cmd) error {
|
func finishRunning(stepName string, cmd *exec.Cmd) error {
|
||||||
|
@ -283,3 +281,15 @@ func finishRunning(stepName string, cmd *exec.Cmd) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func kubectlCmd(arg ...string) *exec.Cmd {
|
||||||
|
args := []string{"--context", "ingress-e2e"}
|
||||||
|
args = append(args, arg...)
|
||||||
|
|
||||||
|
kb := os.Getenv("KUBECTL")
|
||||||
|
if kb == "" {
|
||||||
|
kb = "./e2e/e2e-internal/kubectl"
|
||||||
|
}
|
||||||
|
|
||||||
|
return exec.Command(kb, args...)
|
||||||
|
}
|
62
controllers/nginx/e2e/e2e_types.go
Normal file
62
controllers/nginx/e2e/e2e_types.go
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
apiv1 "k8s.io/api/core/v1"
|
||||||
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IngressTestCase defines a test case for Ingress
|
||||||
|
type IngressTestCase struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Ingress *extensions.Ingress `json:"ingress"`
|
||||||
|
ReplicationController *apiv1.ReplicationController `json:"replicationController,omitempty"`
|
||||||
|
Deployment *extensions.Deployment `json:"deployment,omitempty"`
|
||||||
|
Service *apiv1.Service `json:"service"`
|
||||||
|
Assert []*Assert `json:"tests"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert defines a verification over the
|
||||||
|
type Assert struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Request Request `json:"request"`
|
||||||
|
Expect []*Expect `json:"expect"`
|
||||||
|
Timeout time.Duration `json:"timeout"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request defines a HTTP/s request to be executed against an Ingress
|
||||||
|
type Request struct {
|
||||||
|
Method string `json:"method"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
Query map[string]interface{} `json:"query"`
|
||||||
|
Form map[string]interface{} `json:"form"`
|
||||||
|
Body interface{} `json:"body"`
|
||||||
|
Headers map[string]string `json:"headers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expect defines the required conditions that must be true from a request response
|
||||||
|
type Expect struct {
|
||||||
|
Body []byte `json:"body"`
|
||||||
|
ContentType string `json:"contentType"`
|
||||||
|
Header []string `json:"header"`
|
||||||
|
HeaderAndValue map[string]string `json:"headerAndValue"`
|
||||||
|
Statuscode int `json:"statusCode"`
|
||||||
|
}
|
40
controllers/nginx/e2e/e2e_types_test.go
Normal file
40
controllers/nginx/e2e/e2e_types_test.go
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestReadYamlCase(t *testing.T) {
|
||||||
|
itc, err := parseTestCase("suite/0001.yaml")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error reading test case 0001: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if itc == nil {
|
||||||
|
t.Fatal("unexpected decoding of test case 0001")
|
||||||
|
}
|
||||||
|
|
||||||
|
if itc.ReplicationController != nil {
|
||||||
|
t.Fatal("unexpected replication controller in test case 0001")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(itc.Assert) != 1 {
|
||||||
|
t.Fatalf("expected 1 tests but %v returned", len(itc.Assert))
|
||||||
|
}
|
||||||
|
}
|
161
controllers/nginx/e2e/ingress_test.go
Normal file
161
controllers/nginx/e2e/ingress_test.go
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ghodss/yaml"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
|
||||||
|
"k8s.io/ingress/controllers/nginx/e2e/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIngressSuite(t *testing.T) {
|
||||||
|
client, err := util.GetClient()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error creating k8s client: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pwd, _ := os.Getwd()
|
||||||
|
filepath.Walk(path.Join(pwd, "/suite"),
|
||||||
|
func(path string, info os.FileInfo, err error) error {
|
||||||
|
if strings.HasSuffix(path, ".yaml") {
|
||||||
|
t.Log(path)
|
||||||
|
runTestCase(path, client, t)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func runTestCase(rawtc string, client kubernetes.Interface, t *testing.T) {
|
||||||
|
tc, err := parseTestCase(rawtc)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error reading Ingress test case file %v: %v", rawtc, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run(tc.Name, func(t *testing.T) {
|
||||||
|
if len(tc.Assert) == 0 {
|
||||||
|
t.Fatal("test case does not contains tests")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.Ingress == nil {
|
||||||
|
t.Fatal("the test case does not contains an Ingress rule")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("starting deploy of requirements for test case '%v'", tc.Name)
|
||||||
|
err := tc.deploy(client)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error in test case deploy process: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, assert := range tc.Assert {
|
||||||
|
t.Logf("running assert %v", assert.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tc.undeploy(client)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error in test case deploy process: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseTestCase parses a test case from a yaml file
|
||||||
|
func parseTestCase(p string) (*IngressTestCase, error) {
|
||||||
|
file, err := ioutil.ReadFile(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var itc IngressTestCase
|
||||||
|
err = yaml.Unmarshal(file, &itc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &itc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// deploy creates the kubernetes object specified in the test case
|
||||||
|
func (tc IngressTestCase) deploy(client kubernetes.Interface) error {
|
||||||
|
_, err := client.Extensions().Ingresses(getNamespace(tc.Ingress.Namespace)).Create(tc.Ingress)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.Service != nil {
|
||||||
|
_, err := client.CoreV1().Services(getNamespace(tc.Service.Namespace)).Create(tc.Service)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.ReplicationController != nil {
|
||||||
|
_, err := client.CoreV1().ReplicationControllers(getNamespace(tc.ReplicationController.Namespace)).Create(tc.ReplicationController)
|
||||||
|
return err
|
||||||
|
} else if tc.Deployment != nil {
|
||||||
|
_, err := client.Extensions().Deployments(getNamespace(tc.Deployment.Namespace)).Create(tc.Deployment)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("invalid deployment option. Please check the test case")
|
||||||
|
}
|
||||||
|
|
||||||
|
// undeploy removes the kubernetes object created by the test case
|
||||||
|
func (tc IngressTestCase) undeploy(client kubernetes.Interface) error {
|
||||||
|
err := client.Extensions().Ingresses(getNamespace(tc.Ingress.Namespace)).Delete(tc.Ingress.Name, &metav1.DeleteOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.Service != nil {
|
||||||
|
err := client.CoreV1().Services(getNamespace(tc.Service.Namespace)).Delete(tc.Service.Name, &metav1.DeleteOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.ReplicationController != nil {
|
||||||
|
err := client.CoreV1().ReplicationControllers(getNamespace(tc.ReplicationController.Namespace)).Delete(tc.ReplicationController.Name, &metav1.DeleteOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if tc.Deployment != nil {
|
||||||
|
err := client.Extensions().Deployments(getNamespace(tc.Deployment.Namespace)).Delete(tc.Deployment.Name, &metav1.DeleteOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNamespace(ns string) string {
|
||||||
|
if ns == "" {
|
||||||
|
return "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
return ns
|
||||||
|
}
|
47
controllers/nginx/e2e/nginx-ingress-controller.yaml
Normal file
47
controllers/nginx/e2e/nginx-ingress-controller.yaml
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: nginx-ingress-controller
|
||||||
|
labels:
|
||||||
|
k8s-app: nginx-ingress-controller
|
||||||
|
namespace: kube-system
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
k8s-app: nginx-ingress-controller
|
||||||
|
spec:
|
||||||
|
terminationGracePeriodSeconds: 60
|
||||||
|
containers:
|
||||||
|
- image: gcr.io/google_containers/nginx-ingress-controller-amd64:test
|
||||||
|
name: nginx-ingress-controller
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: 10254
|
||||||
|
scheme: HTTP
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: 10254
|
||||||
|
scheme: HTTP
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
timeoutSeconds: 1
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
hostPort: 80
|
||||||
|
- containerPort: 443
|
||||||
|
hostPort: 443
|
||||||
|
env:
|
||||||
|
- name: POD_NAME
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: metadata.name
|
||||||
|
- name: POD_NAMESPACE
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: metadata.namespace
|
||||||
|
args:
|
||||||
|
- /nginx-ingress-controller
|
||||||
|
- --default-backend-service=$(POD_NAMESPACE)/default-http-backend
|
62
controllers/nginx/e2e/suite/0001.yaml
Normal file
62
controllers/nginx/e2e/suite/0001.yaml
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
name: "001"
|
||||||
|
description: "simple test"
|
||||||
|
ingress:
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: echomap
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: foo.bar.com
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
backend:
|
||||||
|
serviceName: echoheaders
|
||||||
|
servicePort: 80
|
||||||
|
deployment:
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: http-svc
|
||||||
|
labels:
|
||||||
|
k8s-app: http-svc
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
k8s-app: http-svc
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: http-svc
|
||||||
|
image: gcr.io/google_containers/echoserver:1.8
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: 8080
|
||||||
|
scheme: HTTP
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
timeoutSeconds: 5
|
||||||
|
ports:
|
||||||
|
- containerPort: 8080
|
||||||
|
service:
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: http-svc
|
||||||
|
labels:
|
||||||
|
k8s-app: http-svc
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 8080
|
||||||
|
selector:
|
||||||
|
k8s-app: http-svc
|
||||||
|
tests:
|
||||||
|
- name: "get /"
|
||||||
|
expect:
|
||||||
|
- statusCode: 200
|
||||||
|
request:
|
||||||
|
method: GET
|
||||||
|
url: /
|
223
controllers/nginx/e2e/util/util.go
Normal file
223
controllers/nginx/e2e/util/util.go
Normal file
|
@ -0,0 +1,223 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
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 util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/fields"
|
||||||
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
|
"k8s.io/apimachinery/pkg/watch"
|
||||||
|
|
||||||
|
"k8s.io/api/core/v1"
|
||||||
|
apierrs "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
|
||||||
|
"k8s.io/client-go/tools/cache"
|
||||||
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PodStore struct {
|
||||||
|
cache.Store
|
||||||
|
stopCh chan struct{}
|
||||||
|
Reflector *cache.Reflector
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PodStore) List() []*v1.Pod {
|
||||||
|
objects := s.Store.List()
|
||||||
|
pods := make([]*v1.Pod, 0)
|
||||||
|
for _, o := range objects {
|
||||||
|
pods = append(pods, o.(*v1.Pod))
|
||||||
|
}
|
||||||
|
return pods
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PodStore) Stop() {
|
||||||
|
close(s.stopCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetClient() (kubernetes.Interface, error) {
|
||||||
|
profile := os.Getenv("MINIKUBE_PROFILE")
|
||||||
|
|
||||||
|
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||||
|
configOverrides := &clientcmd.ConfigOverrides{}
|
||||||
|
configOverrides.CurrentContext = profile
|
||||||
|
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
|
||||||
|
config, err := kubeConfig.ClientConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error creating kubeConfig: %s", err)
|
||||||
|
}
|
||||||
|
client, err := kubernetes.NewForConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "Error creating new client from kubeConfig.ClientConfig()")
|
||||||
|
}
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPodStore(c kubernetes.Interface, namespace string, label labels.Selector, field fields.Selector) *PodStore {
|
||||||
|
lw := &cache.ListWatch{
|
||||||
|
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||||
|
options.LabelSelector = label.String()
|
||||||
|
options.FieldSelector = field.String()
|
||||||
|
obj, err := c.Core().Pods(namespace).List(options)
|
||||||
|
return runtime.Object(obj), err
|
||||||
|
},
|
||||||
|
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||||
|
options.LabelSelector = label.String()
|
||||||
|
options.FieldSelector = field.String()
|
||||||
|
return c.Core().Pods(namespace).Watch(options)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
store := cache.NewStore(cache.MetaNamespaceKeyFunc)
|
||||||
|
stopCh := make(chan struct{})
|
||||||
|
reflector := cache.NewReflector(lw, &v1.Pod{}, store, 0)
|
||||||
|
reflector.Run(stopCh)
|
||||||
|
return &PodStore{Store: store, stopCh: stopCh, Reflector: reflector}
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartPods(c kubernetes.Interface, namespace string, pod v1.Pod, waitForRunning bool) error {
|
||||||
|
pod.ObjectMeta.Labels["name"] = pod.Name
|
||||||
|
if waitForRunning {
|
||||||
|
label := labels.SelectorFromSet(labels.Set(map[string]string{"name": pod.Name}))
|
||||||
|
err := WaitForPodsWithLabelRunning(c, namespace, label)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error waiting for pod %s to be running: %v", pod.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait up to 10 minutes for all matching pods to become Running and at least one
|
||||||
|
// matching pod exists.
|
||||||
|
func WaitForPodsWithLabelRunning(c kubernetes.Interface, ns string, label labels.Selector) error {
|
||||||
|
running := false
|
||||||
|
PodStore := NewPodStore(c, ns, label, fields.Everything())
|
||||||
|
defer PodStore.Stop()
|
||||||
|
waitLoop:
|
||||||
|
for start := time.Now(); time.Since(start) < 10*time.Minute; time.Sleep(250 * time.Millisecond) {
|
||||||
|
pods := PodStore.List()
|
||||||
|
if len(pods) == 0 {
|
||||||
|
continue waitLoop
|
||||||
|
}
|
||||||
|
for _, p := range pods {
|
||||||
|
if p.Status.Phase != v1.PodRunning {
|
||||||
|
continue waitLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
running = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !running {
|
||||||
|
return fmt.Errorf("Timeout while waiting for pods with labels %q to be running", label.String())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitForRCToStabilize waits till the RC has a matching generation/replica count between spec and status.
|
||||||
|
func WaitForRCToStabilize(t *testing.T, c kubernetes.Interface, ns, name string, timeout time.Duration) error {
|
||||||
|
options := metav1.ListOptions{FieldSelector: fields.Set{
|
||||||
|
"metadata.name": name,
|
||||||
|
"metadata.namespace": ns,
|
||||||
|
}.AsSelector().String()}
|
||||||
|
w, err := c.Core().ReplicationControllers(ns).Watch(options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = watch.Until(timeout, w, func(event watch.Event) (bool, error) {
|
||||||
|
switch event.Type {
|
||||||
|
case watch.Deleted:
|
||||||
|
return false, apierrs.NewNotFound(schema.GroupResource{Resource: "replicationcontrollers"}, "")
|
||||||
|
}
|
||||||
|
switch rc := event.Object.(type) {
|
||||||
|
case *v1.ReplicationController:
|
||||||
|
if rc.Name == name && rc.Namespace == ns &&
|
||||||
|
rc.Generation <= rc.Status.ObservedGeneration &&
|
||||||
|
*(rc.Spec.Replicas) == rc.Status.Replicas {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
t.Logf("Waiting for rc %s to stabilize, generation %v observed generation %v spec.replicas %d status.replicas %d",
|
||||||
|
name, rc.Generation, rc.Status.ObservedGeneration, *(rc.Spec.Replicas), rc.Status.Replicas)
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// WaitForService waits until the service appears (exist == true), or disappears (exist == false)
|
||||||
|
func WaitForService(t *testing.T, c kubernetes.Interface, namespace, name string, exist bool, interval, timeout time.Duration) error {
|
||||||
|
err := wait.PollImmediate(interval, timeout, func() (bool, error) {
|
||||||
|
_, err := c.Core().Services(namespace).Get(name, metav1.GetOptions{})
|
||||||
|
switch {
|
||||||
|
case err == nil:
|
||||||
|
t.Logf("Service %s in namespace %s found.", name, namespace)
|
||||||
|
return exist, nil
|
||||||
|
case apierrs.IsNotFound(err):
|
||||||
|
t.Logf("Service %s in namespace %s disappeared.", name, namespace)
|
||||||
|
return !exist, nil
|
||||||
|
case !IsRetryableAPIError(err):
|
||||||
|
t.Logf("Non-retryable failure while getting service.")
|
||||||
|
return false, err
|
||||||
|
default:
|
||||||
|
t.Logf("Get service %s in namespace %s failed: %v", name, namespace, err)
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
stateMsg := map[bool]string{true: "to appear", false: "to disappear"}
|
||||||
|
return fmt.Errorf("error waiting for service %s/%s %s: %v", namespace, name, stateMsg[exist], err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//WaitForServiceEndpointsNum waits until the amount of endpoints that implement service to expectNum.
|
||||||
|
func WaitForServiceEndpointsNum(t *testing.T, c kubernetes.Interface, namespace, serviceName string, expectNum int, interval, timeout time.Duration) error {
|
||||||
|
return wait.Poll(interval, timeout, func() (bool, error) {
|
||||||
|
t.Logf("Waiting for amount of service:%s endpoints to be %d", serviceName, expectNum)
|
||||||
|
list, err := c.Core().Endpoints(namespace).List(metav1.ListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range list.Items {
|
||||||
|
if e.Name == serviceName && countEndpointsNum(&e) == expectNum {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func countEndpointsNum(e *v1.Endpoints) int {
|
||||||
|
num := 0
|
||||||
|
for _, sub := range e.Subsets {
|
||||||
|
num += len(sub.Addresses)
|
||||||
|
}
|
||||||
|
return num
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsRetryableAPIError(err error) bool {
|
||||||
|
return apierrs.IsTimeout(err) || apierrs.IsServerTimeout(err) || apierrs.IsTooManyRequests(err) || apierrs.IsInternalError(err)
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ package controller
|
||||||
import (
|
import (
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
|
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/alias"
|
"k8s.io/ingress/core/pkg/ingress/annotations/alias"
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/auth"
|
"k8s.io/ingress/core/pkg/ingress/annotations/auth"
|
||||||
"k8s.io/ingress/core/pkg/ingress/annotations/authreq"
|
"k8s.io/ingress/core/pkg/ingress/annotations/authreq"
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
[[ $DEBUG ]] && set -x
|
|
||||||
|
|
||||||
set -eof pipefail
|
|
||||||
|
|
||||||
# include env
|
|
||||||
. hack/e2e-internal/e2e-env.sh
|
|
||||||
|
|
||||||
echo "Destroying running docker containers..."
|
|
||||||
# do not failt if the container is not running
|
|
||||||
docker rm -f kubelet || true
|
|
||||||
docker rm -f apiserver || true
|
|
||||||
docker rm -f etcd || true
|
|
|
@ -1,21 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
[[ $DEBUG ]] && set -x
|
|
||||||
|
|
||||||
export ETCD_VERSION=3.0.14
|
|
||||||
export K8S_VERSION=1.4.5
|
|
||||||
|
|
||||||
export PWD=`pwd`
|
|
||||||
export BASEDIR="$(dirname ${BASH_SOURCE})"
|
|
||||||
export KUBECTL="${BASEDIR}/kubectl"
|
|
||||||
export GOOS="${GOOS:-linux}"
|
|
||||||
|
|
||||||
if [ ! -e ${KUBECTL} ]; then
|
|
||||||
echo "kubectl binary is missing. downloading..."
|
|
||||||
curl -sSL http://storage.googleapis.com/kubernetes-release/release/v${K8S_VERSION}/bin/${GOOS}/amd64/kubectl -o ${KUBECTL}
|
|
||||||
chmod u+x ${KUBECTL}
|
|
||||||
fi
|
|
||||||
|
|
||||||
${KUBECTL} config set-cluster travis --server=http://0.0.0.0:8080
|
|
||||||
${KUBECTL} config set-context travis --cluster=travis
|
|
||||||
${KUBECTL} config use-context travis
|
|
|
@ -1,11 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
[[ $DEBUG ]] && set -x
|
|
||||||
|
|
||||||
set -eof pipefail
|
|
||||||
|
|
||||||
# include env
|
|
||||||
. hack/e2e-internal/e2e-env.sh
|
|
||||||
|
|
||||||
echo "Kubernetes information:"
|
|
||||||
${KUBECTL} version
|
|
|
@ -1,55 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
[[ $DEBUG ]] && set -x
|
|
||||||
|
|
||||||
set -eof pipefail
|
|
||||||
|
|
||||||
# include env
|
|
||||||
. hack/e2e-internal/e2e-env.sh
|
|
||||||
|
|
||||||
echo "Starting etcd..."
|
|
||||||
docker run -d \
|
|
||||||
--net=host \
|
|
||||||
--name=etcd \
|
|
||||||
quay.io/coreos/etcd:v$ETCD_VERSION
|
|
||||||
|
|
||||||
echo "Starting kubernetes..."
|
|
||||||
|
|
||||||
docker run -d --name=apiserver \
|
|
||||||
--net=host \
|
|
||||||
--pid=host \
|
|
||||||
--privileged=true \
|
|
||||||
gcr.io/google_containers/hyperkube:v${K8S_VERSION} \
|
|
||||||
/hyperkube apiserver \
|
|
||||||
--insecure-bind-address=0.0.0.0 \
|
|
||||||
--service-cluster-ip-range=10.0.0.1/24 \
|
|
||||||
--etcd_servers=http://127.0.0.1:4001 \
|
|
||||||
--v=2
|
|
||||||
|
|
||||||
docker run -d --name=kubelet \
|
|
||||||
--volume=/:/rootfs:ro \
|
|
||||||
--volume=/sys:/sys:ro \
|
|
||||||
--volume=/dev:/dev \
|
|
||||||
--volume=/var/lib/docker/:/var/lib/docker:rw \
|
|
||||||
--volume=/var/lib/kubelet/:/var/lib/kubelet:rw \
|
|
||||||
--volume=/var/run:/var/run:rw \
|
|
||||||
--net=host \
|
|
||||||
--pid=host \
|
|
||||||
--privileged=true \
|
|
||||||
gcr.io/google_containers/hyperkube:v${K8S_VERSION} \
|
|
||||||
/hyperkube kubelet \
|
|
||||||
--containerized \
|
|
||||||
--hostname-override="0.0.0.0" \
|
|
||||||
--address="0.0.0.0" \
|
|
||||||
--cluster_dns=10.0.0.10 --cluster_domain=cluster.local \
|
|
||||||
--api-servers=http://localhost:8080 \
|
|
||||||
--config=/etc/kubernetes/manifests-multi
|
|
||||||
|
|
||||||
echo "waiting until api server is available..."
|
|
||||||
until curl -o /dev/null -sIf http://0.0.0.0:8080; do \
|
|
||||||
sleep 10;
|
|
||||||
done;
|
|
||||||
|
|
||||||
echo "Kubernetes started"
|
|
||||||
echo "Kubernetes information:"
|
|
||||||
${KUBECTL} version
|
|
|
@ -1,3 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
echo "running ginkgo"
|
|
Loading…
Reference in a new issue