2017-10-17 22:50:27 +00:00
|
|
|
/*
|
|
|
|
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"
|
2017-11-10 02:00:38 +00:00
|
|
|
"time"
|
2017-10-17 22:50:27 +00:00
|
|
|
|
2018-04-01 20:09:27 +00:00
|
|
|
appsv1beta1 "k8s.io/api/apps/v1beta1"
|
2017-10-17 22:50:27 +00:00
|
|
|
"k8s.io/api/core/v1"
|
|
|
|
apiextcs "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
2017-11-10 02:00:38 +00:00
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
2018-04-01 20:09:27 +00:00
|
|
|
"k8s.io/apimachinery/pkg/fields"
|
2017-11-12 16:52:55 +00:00
|
|
|
"k8s.io/apimachinery/pkg/util/wait"
|
2017-10-17 22:50:27 +00:00
|
|
|
"k8s.io/client-go/kubernetes"
|
2017-11-12 16:52:55 +00:00
|
|
|
restclient "k8s.io/client-go/rest"
|
2017-10-17 22:50:27 +00:00
|
|
|
|
2017-11-22 13:54:44 +00:00
|
|
|
"github.com/golang/glog"
|
2018-04-01 20:09:27 +00:00
|
|
|
"github.com/pkg/errors"
|
2018-01-23 23:37:47 +00:00
|
|
|
|
2017-10-17 22:50:27 +00:00
|
|
|
. "github.com/onsi/ginkgo"
|
|
|
|
. "github.com/onsi/gomega"
|
|
|
|
)
|
|
|
|
|
2018-03-07 16:37:30 +00:00
|
|
|
// RequestScheme define a scheme used in a test request.
|
2017-10-17 22:50:27 +00:00
|
|
|
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
|
2017-11-12 16:52:55 +00:00
|
|
|
KubeConfig *restclient.Config
|
2017-10-17 22:50:27 +00:00
|
|
|
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,
|
2018-03-07 16:37:30 +00:00
|
|
|
// we install a Cleanup action before each test and clear it after. If we
|
2017-10-17 22:50:27 +00:00
|
|
|
// should abort, the AfterSuite hook should run all Cleanup actions.
|
|
|
|
cleanupHandle CleanupActionHandle
|
2017-11-10 02:00:38 +00:00
|
|
|
|
|
|
|
NginxHTTPURL string
|
|
|
|
NginxHTTPSURL string
|
2017-10-17 22:50:27 +00:00
|
|
|
}
|
|
|
|
|
2017-11-10 02:00:38 +00:00
|
|
|
// NewDefaultFramework makes a new framework and sets up a BeforeEach/AfterEach for
|
2017-10-17 22:50:27 +00:00
|
|
|
// 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())
|
|
|
|
|
2017-11-12 16:52:55 +00:00
|
|
|
f.KubeConfig = kubeConfig
|
2017-10-17 22:50:27 +00:00
|
|
|
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())
|
2017-11-10 02:00:38 +00:00
|
|
|
|
|
|
|
By("Building NGINX HTTP URL")
|
|
|
|
f.NginxHTTPURL, err = f.GetNginxURL(HTTP)
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
|
|
|
|
By("Building NGINX HTTPS URL")
|
|
|
|
f.NginxHTTPSURL, err = f.GetNginxURL(HTTPS)
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
2017-10-17 22:50:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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")
|
2017-11-10 02:00:38 +00:00
|
|
|
err = WaitForNoPodsInNamespace(f.KubeClientSet, f.Namespace.Name)
|
2017-10-17 22:50:27 +00:00
|
|
|
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) {
|
2017-11-10 02:00:38 +00:00
|
|
|
s, err := f.KubeClientSet.CoreV1().Services("ingress-nginx").Get("ingress-nginx", metav1.GetOptions{})
|
2017-10-17 22:50:27 +00:00
|
|
|
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
|
|
|
|
}
|
2017-11-10 02:00:38 +00:00
|
|
|
|
2017-11-12 16:52:55 +00:00
|
|
|
// WaitForNginxServer waits until the nginx configuration contains a particular server section
|
|
|
|
func (f *Framework) WaitForNginxServer(name string, matcher func(cfg string) bool) error {
|
|
|
|
// initial wait to allow the update of the ingress controller
|
2017-11-10 02:00:38 +00:00
|
|
|
time.Sleep(5 * time.Second)
|
2017-11-12 16:52:55 +00:00
|
|
|
return wait.PollImmediate(Poll, time.Minute*2, f.matchNginxConditions(name, matcher))
|
|
|
|
}
|
|
|
|
|
2018-01-17 12:26:53 +00:00
|
|
|
// WaitForNginxConfiguration waits until the nginx configuration contains a particular configuration
|
|
|
|
func (f *Framework) WaitForNginxConfiguration(matcher func(cfg string) bool) error {
|
|
|
|
// initial wait to allow the update of the ingress controller
|
|
|
|
time.Sleep(5 * time.Second)
|
|
|
|
return wait.PollImmediate(Poll, time.Minute*2, f.matchNginxConditions("", matcher))
|
|
|
|
}
|
|
|
|
|
2017-11-14 20:50:08 +00:00
|
|
|
// NginxLogs returns the logs of the nginx ingress controller pod running
|
|
|
|
func (f *Framework) NginxLogs() (string, error) {
|
|
|
|
l, err := f.KubeClientSet.CoreV1().Pods("ingress-nginx").List(metav1.ListOptions{
|
|
|
|
LabelSelector: "app=ingress-nginx",
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(l.Items) == 0 {
|
|
|
|
return "", fmt.Errorf("no nginx ingress controller pod is running")
|
|
|
|
}
|
|
|
|
|
2018-01-23 20:09:41 +00:00
|
|
|
for _, pod := range l.Items {
|
|
|
|
if strings.HasPrefix(pod.GetName(), "nginx-ingress-controller") &&
|
|
|
|
len(pod.Status.ContainerStatuses) > 0 &&
|
|
|
|
pod.Status.ContainerStatuses[0].State.Running != nil {
|
|
|
|
return f.Logs(&pod)
|
|
|
|
}
|
2017-11-14 20:50:08 +00:00
|
|
|
}
|
|
|
|
|
2018-01-23 20:09:41 +00:00
|
|
|
return "", fmt.Errorf("no nginx ingress controller pod is running")
|
2017-11-14 20:50:08 +00:00
|
|
|
}
|
|
|
|
|
2017-11-12 16:52:55 +00:00
|
|
|
func (f *Framework) matchNginxConditions(name string, matcher func(cfg string) bool) wait.ConditionFunc {
|
|
|
|
return func() (bool, error) {
|
|
|
|
l, err := f.KubeClientSet.CoreV1().Pods("ingress-nginx").List(metav1.ListOptions{
|
|
|
|
LabelSelector: "app=ingress-nginx",
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(l.Items) == 0 {
|
|
|
|
return false, fmt.Errorf("no nginx ingress controller pod is running")
|
|
|
|
}
|
|
|
|
|
2018-01-17 12:26:53 +00:00
|
|
|
var cmd string
|
|
|
|
if name == "" {
|
|
|
|
cmd = fmt.Sprintf("cat /etc/nginx/nginx.conf")
|
|
|
|
} else {
|
|
|
|
cmd = fmt.Sprintf("cat /etc/nginx/nginx.conf | awk '/## start server %v/,/## end server %v/'", name, name)
|
|
|
|
}
|
|
|
|
|
2018-01-23 20:09:41 +00:00
|
|
|
var pod *v1.Pod
|
2018-01-23 23:37:47 +00:00
|
|
|
Loop:
|
2018-01-23 20:09:41 +00:00
|
|
|
for _, p := range l.Items {
|
2018-01-23 23:37:47 +00:00
|
|
|
if strings.HasPrefix(p.GetName(), "nginx-ingress-controller") {
|
|
|
|
for _, cs := range p.Status.ContainerStatuses {
|
|
|
|
if cs.State.Running != nil && cs.Name == "nginx-ingress-controller" {
|
|
|
|
pod = &p
|
|
|
|
break Loop
|
|
|
|
}
|
|
|
|
}
|
2018-01-23 20:09:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if pod == nil {
|
|
|
|
return false, fmt.Errorf("no nginx ingress controller pod is running")
|
|
|
|
}
|
|
|
|
|
|
|
|
o, err := f.ExecCommand(pod, cmd)
|
2017-11-12 16:52:55 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
2017-11-22 13:54:44 +00:00
|
|
|
var match bool
|
|
|
|
errs := InterceptGomegaFailures(func() {
|
|
|
|
if matcher(o) {
|
|
|
|
match = true
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
glog.V(2).Infof("Errors waiting for conditions: %v", errs)
|
|
|
|
|
|
|
|
if match {
|
2017-11-12 16:52:55 +00:00
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return false, nil
|
|
|
|
}
|
2017-11-10 02:00:38 +00:00
|
|
|
}
|
2018-04-01 20:09:27 +00:00
|
|
|
|
|
|
|
// UpdateDeployment runs the given updateFunc on the deployment and waits for it to be updated
|
|
|
|
func UpdateDeployment(kubeClientSet kubernetes.Interface, namespace string, name string, replicas int, updateFunc func(d *appsv1beta1.Deployment) error) error {
|
|
|
|
deployment, err := kubeClientSet.AppsV1beta1().Deployments(namespace).Get(name, metav1.GetOptions{})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = updateFunc(deployment); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = WaitForPodsReady(kubeClientSet, 60*time.Second, replicas, namespace, metav1.ListOptions{
|
|
|
|
LabelSelector: fields.SelectorFromSet(fields.Set(deployment.Spec.Template.ObjectMeta.Labels)).String(),
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "failed to wait for nginx-ingress-controller pods to restart")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|