Add e2e tests

This commit is contained in:
Manuel de Brito Fontes 2017-10-17 19:50:27 -03:00
parent 99a355f25d
commit 601fb7dacf
1163 changed files with 289217 additions and 14195 deletions

View file

@ -0,0 +1,60 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package framework
import "sync"
type CleanupActionHandle *int
var cleanupActionsLock sync.Mutex
var cleanupActions = map[CleanupActionHandle]func(){}
// AddCleanupAction installs a function that will be called in the event of the
// whole test being terminated. This allows arbitrary pieces of the overall
// test to hook into SynchronizedAfterSuite().
func AddCleanupAction(fn func()) CleanupActionHandle {
p := CleanupActionHandle(new(int))
cleanupActionsLock.Lock()
defer cleanupActionsLock.Unlock()
cleanupActions[p] = fn
return p
}
// RemoveCleanupAction removes a function that was installed by AddCleanupAction.
func RemoveCleanupAction(p CleanupActionHandle) {
cleanupActionsLock.Lock()
defer cleanupActionsLock.Unlock()
delete(cleanupActions, p)
}
// RunCleanupActions runs all functions installed by AddCleanupAction. It does
// not remove them (see RemoveCleanupAction) but it does run unlocked, so they
// may remove themselves.
func RunCleanupActions() {
list := []func(){}
func() {
cleanupActionsLock.Lock()
defer cleanupActionsLock.Unlock()
for _, fn := range cleanupActions {
list = append(list, fn)
}
}()
// Run unlocked.
for _, fn := range list {
fn()
}
}

View file

@ -0,0 +1,145 @@
/*
Copyright 2017 Jetstack Ltd.
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 framework
import (
"fmt"
"os/exec"
"strings"
"k8s.io/api/core/v1"
apiextcs "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
const (
podName = "test-ingress-controller"
)
type RequestScheme string
// These are valid test request schemes.
const (
HTTP RequestScheme = "http"
HTTPS RequestScheme = "https"
)
// Framework supports common operations used by e2e tests; it will keep a client & a namespace for you.
type Framework struct {
BaseName string
// A Kubernetes and Service Catalog client
KubeClientSet kubernetes.Interface
APIExtensionsClientSet apiextcs.Interface
// Namespace in which all test resources should reside
Namespace *v1.Namespace
// To make sure that this framework cleans up after itself, no matter what,
// we install a Cleanup action before each test and clear it after. If we
// should abort, the AfterSuite hook should run all Cleanup actions.
cleanupHandle CleanupActionHandle
}
// NewFramework makes a new framework and sets up a BeforeEach/AfterEach for
// you (you can write additional before/after each functions).
func NewDefaultFramework(baseName string) *Framework {
f := &Framework{
BaseName: baseName,
}
BeforeEach(f.BeforeEach)
AfterEach(f.AfterEach)
return f
}
// BeforeEach gets a client and makes a namespace.
func (f *Framework) BeforeEach() {
f.cleanupHandle = AddCleanupAction(f.AfterEach)
By("Creating a kubernetes client")
kubeConfig, err := LoadConfig(TestContext.KubeConfig, TestContext.KubeContext)
Expect(err).NotTo(HaveOccurred())
f.KubeClientSet, err = kubernetes.NewForConfig(kubeConfig)
Expect(err).NotTo(HaveOccurred())
By("Building a namespace api object")
f.Namespace, err = CreateKubeNamespace(f.BaseName, f.KubeClientSet)
Expect(err).NotTo(HaveOccurred())
}
// AfterEach deletes the namespace, after reading its events.
func (f *Framework) AfterEach() {
RemoveCleanupAction(f.cleanupHandle)
By("Deleting test namespace")
err := DeleteKubeNamespace(f.KubeClientSet, f.Namespace.Name)
Expect(err).NotTo(HaveOccurred())
By("Waiting for test namespace to no longer exist")
err = WaitForKubeNamespaceNotExist(f.KubeClientSet, f.Namespace.Name)
Expect(err).NotTo(HaveOccurred())
}
// IngressNginxDescribe wrapper function for ginkgo describe. Adds namespacing.
func IngressNginxDescribe(text string, body func()) bool {
return Describe("[nginx-ingress] "+text, body)
}
// GetNginxIP returns the IP address of the minikube cluster
// where the NGINX ingress controller is running
func (f *Framework) GetNginxIP() (string, error) {
out, err := exec.Command("minikube", "ip").Output()
if err != nil {
return "", err
}
return strings.TrimSpace(string(out)), nil
}
// GetNginxPort returns the number of TCP port where NGINX is running
func (f *Framework) GetNginxPort(name string) (int, error) {
s, err := f.KubeClientSet.CoreV1().Services("ingress-nginx").Get("ingress-nginx", meta_v1.GetOptions{})
if err != nil {
return -1, err
}
for _, p := range s.Spec.Ports {
if p.NodePort != 0 && p.Name == name {
return int(p.NodePort), nil
}
}
return -1, err
}
// GetNginxURL returns the URL should be used to make a request to NGINX
func (f *Framework) GetNginxURL(scheme RequestScheme) (string, error) {
ip, err := f.GetNginxIP()
if err != nil {
return "", err
}
port, err := f.GetNginxPort(fmt.Sprintf("%v", scheme))
if err != nil {
return "", err
}
return fmt.Sprintf("%v://%v:%v", scheme, ip, port), nil
}

View file

@ -0,0 +1,56 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package framework
import (
"flag"
"os"
"github.com/onsi/ginkgo/config"
"k8s.io/client-go/tools/clientcmd"
)
const (
RecommendedConfigPathEnvVar = "INGRESSNGINXCONFIG"
)
type TestContextType struct {
KubeHost string
KubeConfig string
KubeContext string
}
var TestContext TestContextType
// Register flags common to all e2e test suites.
func RegisterCommonFlags() {
// Turn on verbose by default to get spec names
config.DefaultReporterConfig.Verbose = true
// Turn on EmitSpecProgress to get spec progress (especially on interrupt)
config.GinkgoConfig.EmitSpecProgress = true
// Randomize specs as well as suites
config.GinkgoConfig.RandomizeAllSpecs = true
flag.StringVar(&TestContext.KubeHost, "kubernetes-host", "http://127.0.0.1:8080", "The kubernetes host, or apiserver, to connect to")
flag.StringVar(&TestContext.KubeConfig, "kubernetes-config", os.Getenv(clientcmd.RecommendedConfigPathEnvVar), "Path to config containing embedded authinfo for kubernetes. Default value is from environment variable "+clientcmd.RecommendedConfigPathEnvVar)
flag.StringVar(&TestContext.KubeContext, "kubernetes-context", "", "config context to use for kuberentes. If unset, will use value from 'current-context'")
}
func RegisterParseFlags() {
RegisterCommonFlags()
flag.Parse()
}

174
test/e2e/framework/util.go Normal file
View file

@ -0,0 +1,174 @@
/*
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.
*/
package framework
import (
"fmt"
"time"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/uuid"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/clientcmd/api"
)
const (
// Poll how often to poll for conditions
Poll = 2 * time.Second
// Default time to wait for operations to complete
defaultTimeout = 30 * time.Second
)
func nowStamp() string {
return time.Now().Format(time.StampMilli)
}
func log(level string, format string, args ...interface{}) {
fmt.Fprintf(GinkgoWriter, nowStamp()+": "+level+": "+format+"\n", args...)
}
func Logf(format string, args ...interface{}) {
log("INFO", format, args...)
}
func Failf(format string, args ...interface{}) {
msg := fmt.Sprintf(format, args...)
log("INFO", msg)
Fail(nowStamp()+": "+msg, 1)
}
func Skipf(format string, args ...interface{}) {
msg := fmt.Sprintf(format, args...)
log("INFO", msg)
Skip(nowStamp() + ": " + msg)
}
func RestclientConfig(config, context string) (*api.Config, error) {
Logf(">>> config: %s\n", config)
if config == "" {
return nil, fmt.Errorf("Config file must be specified to load client config")
}
c, err := clientcmd.LoadFromFile(config)
if err != nil {
return nil, fmt.Errorf("error loading config: %v", err.Error())
}
if context != "" {
Logf(">>> context: %s\n", context)
c.CurrentContext = context
}
return c, nil
}
type ClientConfigGetter func() (*rest.Config, error)
func LoadConfig(config, context string) (*rest.Config, error) {
c, err := RestclientConfig(config, context)
if err != nil {
return nil, err
}
return clientcmd.NewDefaultClientConfig(*c, &clientcmd.ConfigOverrides{}).ClientConfig()
}
// RunId unique identifier of the e2e run
var RunId = uuid.NewUUID()
func CreateKubeNamespace(baseName string, c kubernetes.Interface) (*v1.Namespace, error) {
ns := &v1.Namespace{
ObjectMeta: metav1.ObjectMeta{
GenerateName: fmt.Sprintf("e2e-tests-%v-", baseName),
},
}
// Be robust about making the namespace creation call.
var got *v1.Namespace
err := wait.PollImmediate(Poll, defaultTimeout, func() (bool, error) {
var err error
got, err = c.Core().Namespaces().Create(ns)
if err != nil {
Logf("Unexpected error while creating namespace: %v", err)
return false, nil
}
Logf("Created namespace: %v", got.Name)
return true, nil
})
if err != nil {
return nil, err
}
return got, nil
}
func DeleteKubeNamespace(c kubernetes.Interface, namespace string) error {
return c.Core().Namespaces().Delete(namespace, nil)
}
func ExpectNoError(err error, explain ...interface{}) {
if err != nil {
Logf("Unexpected error occurred: %v", err)
}
ExpectWithOffset(1, err).NotTo(HaveOccurred(), explain...)
}
func WaitForKubeNamespaceNotExist(c kubernetes.Interface, namespace string) error {
return wait.PollImmediate(Poll, time.Minute*2, namespaceNotExist(c, namespace))
}
func namespaceNotExist(c kubernetes.Interface, namespace string) wait.ConditionFunc {
return func() (bool, error) {
_, err := c.CoreV1().Namespaces().Get(namespace, metav1.GetOptions{})
if apierrors.IsNotFound(err) {
return true, nil
}
if err != nil {
return false, err
}
return false, nil
}
}
// Waits default amount of time (PodStartTimeout) for the specified pod to become running.
// Returns an error if timeout occurs first, or pod goes in to failed state.
func WaitForPodRunningInNamespace(c kubernetes.Interface, pod *v1.Pod) error {
if pod.Status.Phase == v1.PodRunning {
return nil
}
return waitTimeoutForPodRunningInNamespace(c, pod.Name, pod.Namespace, defaultTimeout)
}
func waitTimeoutForPodRunningInNamespace(c kubernetes.Interface, podName, namespace string, timeout time.Duration) error {
return wait.PollImmediate(Poll, defaultTimeout, podRunning(c, podName, namespace))
}
func podRunning(c kubernetes.Interface, podName, namespace string) wait.ConditionFunc {
return func() (bool, error) {
pod, err := c.CoreV1().Pods(namespace).Get(podName, metav1.GetOptions{})
if err != nil {
return false, err
}
switch pod.Status.Phase {
case v1.PodRunning:
return true, nil
case v1.PodFailed, v1.PodSucceeded:
return false, fmt.Errorf("pod ran to completion")
}
return false, nil
}
}