Add e2e boilerplate
This commit is contained in:
parent
42b58e957c
commit
16c5800545
9 changed files with 397 additions and 4 deletions
|
@ -188,7 +188,7 @@ http {
|
||||||
server {
|
server {
|
||||||
server_name {{ $server.Hostname }};
|
server_name {{ $server.Hostname }};
|
||||||
listen 80{{ if $cfg.UseProxyProtocol }} proxy_protocol{{ end }};
|
listen 80{{ if $cfg.UseProxyProtocol }} proxy_protocol{{ end }};
|
||||||
{{ if not (empty $server.SSLCertificate) }}listen 442 {{ if $cfg.UseProxyProtocol }}proxy_protocol{{ end }} ssl {{ if $cfg.UseHttp2 }}http2{{ end }};
|
{{ if not (empty $server.SSLCertificate) }}listen 442 {{ if $cfg.UseProxyProtocol }}proxy_protocol{{ end }} ssl {{ if $cfg.UseHTTP2 }}http2{{ end }};
|
||||||
{{/* comment PEM sha is required to detect changes in the generated configuration and force a reload */}}
|
{{/* comment PEM sha is required to detect changes in the generated configuration and force a reload */}}
|
||||||
# PEM sha: {{ $server.SSLPemChecksum }}
|
# PEM sha: {{ $server.SSLPemChecksum }}
|
||||||
ssl_certificate {{ $server.SSLCertificate }};
|
ssl_certificate {{ $server.SSLCertificate }};
|
||||||
|
|
|
@ -77,7 +77,7 @@ func (ic *GenericController) syncSecret(k interface{}) error {
|
||||||
}
|
}
|
||||||
sec := secObj.(*api.Secret)
|
sec := secObj.(*api.Secret)
|
||||||
if !ic.secrReferenced(sec.Name, sec.Namespace) {
|
if !ic.secrReferenced(sec.Name, sec.Namespace) {
|
||||||
glog.V(2).Infof("secret %v/%v is not used in Ingress rules. skipping ", sec.Namespace, sec.Name)
|
glog.V(3).Infof("secret %v/%v is not used in Ingress rules. skipping ", sec.Namespace, sec.Name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -100,7 +100,8 @@ type GenericController struct {
|
||||||
|
|
||||||
syncStatus status.Sync
|
syncStatus status.Sync
|
||||||
|
|
||||||
// controller for SSL certificates
|
// local store of SSL certificates
|
||||||
|
// (only certificates used in ingress)
|
||||||
sslCertTracker *sslCertTracker
|
sslCertTracker *sslCertTracker
|
||||||
// TaskQueue in charge of keep the secrets referenced from Ingress
|
// TaskQueue in charge of keep the secrets referenced from Ingress
|
||||||
// in sync with the files on disk
|
// in sync with the files on disk
|
||||||
|
@ -922,7 +923,8 @@ func (ic *GenericController) createServers(data []interface{}, upstreams map[str
|
||||||
}
|
}
|
||||||
|
|
||||||
// only add a certificate if the server does not have one previously configured
|
// only add a certificate if the server does not have one previously configured
|
||||||
if len(ing.Spec.TLS) > 0 && servers[host].SSLCertificate != "" {
|
// TODO: TLS without secret?
|
||||||
|
if len(ing.Spec.TLS) > 0 && servers[host].SSLCertificate == "" && ing.Spec.TLS[0].SecretName != "" {
|
||||||
key := fmt.Sprintf("%v/%v", ing.Namespace, ing.Spec.TLS[0].SecretName)
|
key := fmt.Sprintf("%v/%v", ing.Namespace, ing.Spec.TLS[0].SecretName)
|
||||||
bc, exists := ic.sslCertTracker.Get(key)
|
bc, exists := ic.sslCertTracker.Get(key)
|
||||||
if exists {
|
if exists {
|
||||||
|
@ -931,6 +933,8 @@ func (ic *GenericController) createServers(data []interface{}, upstreams map[str
|
||||||
servers[host].SSLCertificate = cert.PemFileName
|
servers[host].SSLCertificate = cert.PemFileName
|
||||||
servers[host].SSLPemChecksum = cert.PemSHA
|
servers[host].SSLPemChecksum = cert.PemSHA
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
glog.Warningf("secret %v does not exists", key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
14
hack/e2e-internal/e2e-down.sh
Executable file
14
hack/e2e-internal/e2e-down.sh
Executable file
|
@ -0,0 +1,14 @@
|
||||||
|
#!/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
|
21
hack/e2e-internal/e2e-env.sh
Executable file
21
hack/e2e-internal/e2e-env.sh
Executable file
|
@ -0,0 +1,21 @@
|
||||||
|
#!/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
|
11
hack/e2e-internal/e2e-status.sh
Executable file
11
hack/e2e-internal/e2e-status.sh
Executable file
|
@ -0,0 +1,11 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
[[ $DEBUG ]] && set -x
|
||||||
|
|
||||||
|
set -eof pipefail
|
||||||
|
|
||||||
|
# include env
|
||||||
|
. hack/e2e-internal/e2e-env.sh
|
||||||
|
|
||||||
|
echo "Kubernetes information:"
|
||||||
|
${KUBECTL} version
|
55
hack/e2e-internal/e2e-up.sh
Executable file
55
hack/e2e-internal/e2e-up.sh
Executable file
|
@ -0,0 +1,55 @@
|
||||||
|
#!/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
|
3
hack/e2e-internal/ginkgo-e2e.sh
Executable file
3
hack/e2e-internal/ginkgo-e2e.sh
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
echo "running ginkgo"
|
285
hack/e2e.go
Normal file
285
hack/e2e.go
Normal file
|
@ -0,0 +1,285 @@
|
||||||
|
/*
|
||||||
|
Copyright 2014 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// e2e.go runs the e2e test suite. No non-standard package dependencies; call with "go run".
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
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).")
|
||||||
|
down = flag.Bool("down", true, "destroys the created cluster.")
|
||||||
|
test = flag.Bool("test", true, "Run Ginkgo tests.")
|
||||||
|
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.")
|
||||||
|
deployment = flag.String("deployment", "bash", "up/down mechanism")
|
||||||
|
verbose = flag.Bool("v", false, "If true, print all command output.")
|
||||||
|
)
|
||||||
|
|
||||||
|
func appendError(errs []error, err error) []error {
|
||||||
|
if err != nil {
|
||||||
|
return append(errs, err)
|
||||||
|
}
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
||||||
|
func validWorkingDirectory() error {
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not get pwd: %v", err)
|
||||||
|
}
|
||||||
|
acwd, err := filepath.Abs(cwd)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to convert %s to an absolute path: %v", cwd, err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(filepath.Base(acwd), "ingress-controller") {
|
||||||
|
return fmt.Errorf("must run from git root directory: %v", acwd)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestCase struct {
|
||||||
|
XMLName xml.Name `xml:"testcase"`
|
||||||
|
ClassName string `xml:"classname,attr"`
|
||||||
|
Name string `xml:"name,attr"`
|
||||||
|
Time float64 `xml:"time,attr"`
|
||||||
|
Failure string `xml:"failure,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestSuite struct {
|
||||||
|
XMLName xml.Name `xml:"testsuite"`
|
||||||
|
Failures int `xml:"failures,attr"`
|
||||||
|
Tests int `xml:"tests,attr"`
|
||||||
|
Time float64 `xml:"time,attr"`
|
||||||
|
Cases []TestCase
|
||||||
|
}
|
||||||
|
|
||||||
|
var suite TestSuite
|
||||||
|
|
||||||
|
func xmlWrap(name string, f func() error) error {
|
||||||
|
start := time.Now()
|
||||||
|
err := f()
|
||||||
|
duration := time.Since(start)
|
||||||
|
c := TestCase{
|
||||||
|
Name: name,
|
||||||
|
ClassName: "e2e.go",
|
||||||
|
Time: duration.Seconds(),
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
c.Failure = err.Error()
|
||||||
|
suite.Failures++
|
||||||
|
}
|
||||||
|
suite.Cases = append(suite.Cases, c)
|
||||||
|
suite.Tests++
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeXML(start time.Time) {
|
||||||
|
suite.Time = time.Since(start).Seconds()
|
||||||
|
out, err := xml.MarshalIndent(&suite, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Could not marshal XML: %s", err)
|
||||||
|
}
|
||||||
|
path := filepath.Join(*dump, "junit_runner.xml")
|
||||||
|
f, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Could not create file: %s", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
if _, err := f.WriteString(xml.Header); err != nil {
|
||||||
|
log.Fatalf("Error writing XML header: %s", err)
|
||||||
|
}
|
||||||
|
if _, err := f.Write(out); err != nil {
|
||||||
|
log.Fatalf("Error writing XML data: %s", err)
|
||||||
|
}
|
||||||
|
log.Printf("Saved XML output to %s.", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if err := validWorkingDirectory(); err != nil {
|
||||||
|
log.Fatalf("Called from invalid working directory: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
deploy, err := getDeployer()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error creating deployer: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := run(deploy); err != nil {
|
||||||
|
log.Fatalf("Something went wrong: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(deploy deployer) error {
|
||||||
|
if *dump != "" {
|
||||||
|
defer writeXML(time.Now())
|
||||||
|
}
|
||||||
|
|
||||||
|
if *build {
|
||||||
|
if err := xmlWrap("Build", Build); err != nil {
|
||||||
|
return fmt.Errorf("error building: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if *up {
|
||||||
|
if err := xmlWrap("TearDown", deploy.Down); err != nil {
|
||||||
|
return fmt.Errorf("error tearing down previous cluster: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var errs []error
|
||||||
|
|
||||||
|
if *up {
|
||||||
|
// If we tried to bring the cluster up, make a courtesy
|
||||||
|
// attempt to bring it down so we're not leaving resources around.
|
||||||
|
//
|
||||||
|
// TODO: We should try calling deploy.Down exactly once. Though to
|
||||||
|
// stop the leaking resources for now, we want to be on the safe side
|
||||||
|
// and call it explictly in defer if the other one is not called.
|
||||||
|
if *down {
|
||||||
|
defer xmlWrap("Deferred TearDown", deploy.Down)
|
||||||
|
}
|
||||||
|
// Start the cluster using this version.
|
||||||
|
if err := xmlWrap("Up", deploy.Up); err != nil {
|
||||||
|
return fmt.Errorf("starting e2e cluster: %s", err)
|
||||||
|
}
|
||||||
|
if *dump != "" {
|
||||||
|
cmd := exec.Command("./cluster/kubectl.sh", "--match-server-version=false", "get", "nodes", "-oyaml")
|
||||||
|
b, err := cmd.CombinedOutput()
|
||||||
|
if *verbose {
|
||||||
|
log.Printf("kubectl get nodes:\n%s", string(b))
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
if err := ioutil.WriteFile(filepath.Join(*dump, "nodes.yaml"), b, 0644); err != nil {
|
||||||
|
errs = appendError(errs, fmt.Errorf("error writing nodes.yaml: %v", err))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errs = appendError(errs, fmt.Errorf("error running get nodes: %v", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if *test {
|
||||||
|
if err := xmlWrap("IsUp", deploy.IsUp); err != nil {
|
||||||
|
errs = appendError(errs, err)
|
||||||
|
} else {
|
||||||
|
errs = appendError(errs, Test())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) > 0 && *dump != "" {
|
||||||
|
errs = appendError(errs, xmlWrap("DumpClusterLogs", func() error {
|
||||||
|
return DumpClusterLogs(*dump)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
if *down {
|
||||||
|
errs = appendError(errs, xmlWrap("TearDown", deploy.Down))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) != 0 {
|
||||||
|
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", "backends", "backends-images", "backends-push")
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
if err := finishRunning("build-release", cmd); err != nil {
|
||||||
|
return fmt.Errorf("error building: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type deployer interface {
|
||||||
|
Up() error
|
||||||
|
IsUp() error
|
||||||
|
SetupKubecfg() error
|
||||||
|
Down() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDeployer() (deployer, error) {
|
||||||
|
switch *deployment {
|
||||||
|
case "bash":
|
||||||
|
return bash{}, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Unknown deployment strategy %q", *deployment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type bash struct{}
|
||||||
|
|
||||||
|
func (b bash) Up() error {
|
||||||
|
return finishRunning("up", exec.Command("./hack/e2e-internal/e2e-up.sh"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b bash) IsUp() error {
|
||||||
|
return finishRunning("get status", exec.Command("./hack/e2e-internal/e2e-status.sh"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b bash) SetupKubecfg() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b bash) Down() error {
|
||||||
|
return finishRunning("teardown", exec.Command("./hack/e2e-internal/e2e-down.sh"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func DumpClusterLogs(location string) error {
|
||||||
|
log.Printf("Dumping cluster logs to: %v", location)
|
||||||
|
return finishRunning("dump cluster logs", exec.Command("./hack/e2e-internal/log-dump.sh", location))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test() error {
|
||||||
|
if *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 {
|
||||||
|
if *verbose {
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
}
|
||||||
|
log.Printf("Running: %v", stepName)
|
||||||
|
defer func(start time.Time) {
|
||||||
|
log.Printf("Step '%s' finished in %s", stepName, time.Since(start))
|
||||||
|
}(time.Now())
|
||||||
|
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("error running %v: %v", stepName, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in a new issue