e2e: support unique namespace each test (#135)
* e2e: support unique namespace each test * use downward api to convey namespace
This commit is contained in:
Родитель
3296330c73
Коммит
91f009e333
21
hack/e2e
21
hack/e2e
|
@ -1,21 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
mkdir -p _output/bin || true
|
||||
|
||||
option="${1}"
|
||||
case ${option} in
|
||||
"setup")
|
||||
go build -o _output/bin/e2e-setup ./test/e2e/framework/setup/main.go
|
||||
_output/bin/e2e-setup --kubeconfig ${KUBERNETES_KUBECONFIG_PATH} --controller-image "${2}"
|
||||
;;
|
||||
"teardown")
|
||||
go build -o _output/bin/e2e-teardown ./test/e2e/framework/teardown/main.go
|
||||
_output/bin/e2e-teardown --kubeconfig ${KUBERNETES_KUBECONFIG_PATH}
|
||||
;;
|
||||
*) echo "Unknown option ${option}"
|
||||
;;
|
||||
esac
|
|
@ -50,11 +50,7 @@ function build_pass {
|
|||
}
|
||||
|
||||
function e2e_pass {
|
||||
hack/e2e setup $CONTROLLER_IMAGE
|
||||
test_pass="0"
|
||||
go test -v ${TEST_PKGS} || test_pass="1"
|
||||
hack/e2e teardown
|
||||
return $test_pass
|
||||
go test -v ${TEST_PKGS} --kubeconfig $KUBERNETES_KUBECONFIG_PATH --controller-image $CONTROLLER_IMAGE
|
||||
}
|
||||
|
||||
for p in $PASSES; do
|
||||
|
|
|
@ -14,7 +14,6 @@ import (
|
|||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned"
|
||||
"k8s.io/kubernetes/pkg/util/wait"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -139,21 +138,7 @@ func (c *Controller) createTPR() error {
|
|||
return err
|
||||
}
|
||||
|
||||
err = wait.Poll(3*time.Second, 100*time.Second,
|
||||
func() (done bool, err error) {
|
||||
resp, err := k8sutil.WatchETCDCluster(c.masterHost, c.namespace, c.kclient.RESTClient.Client, "0")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if resp.StatusCode == 200 {
|
||||
return true, nil
|
||||
}
|
||||
if resp.StatusCode == 404 {
|
||||
return false, nil
|
||||
}
|
||||
return false, errors.New("Invalid status code: " + resp.Status)
|
||||
})
|
||||
return err
|
||||
return k8sutil.WaitEtcdTPRReady(c.kclient.Client, 3*time.Second, 90*time.Second, c.masterHost, c.namespace)
|
||||
}
|
||||
|
||||
func monitorEtcdCluster(host, ns string, httpClient *http.Client, watchVersion string) (<-chan *Event, <-chan error) {
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned"
|
||||
"k8s.io/kubernetes/pkg/util/intstr"
|
||||
"k8s.io/kubernetes/pkg/util/wait"
|
||||
"k8s.io/kubernetes/pkg/watch"
|
||||
)
|
||||
|
||||
|
@ -92,6 +93,12 @@ func CreateBackupReplicaSetAndService(kclient *unversioned.Client, clusterName,
|
|||
"--etcd-cluster",
|
||||
clusterName,
|
||||
},
|
||||
Env: []api.EnvVar{
|
||||
{
|
||||
Name: "MY_POD_NAMESPACE",
|
||||
ValueFrom: &api.EnvVarSource{FieldRef: &api.ObjectFieldSelector{FieldPath: "metadata.namespace"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -350,3 +357,21 @@ func WatchETCDCluster(host, ns string, httpClient *http.Client, resourceVersion
|
|||
return httpClient.Get(fmt.Sprintf("%s/apis/coreos.com/v1/namespaces/%s/etcdclusters?watch=true&resourceVersion=%s",
|
||||
host, ns, resourceVersion))
|
||||
}
|
||||
|
||||
func WaitEtcdTPRReady(httpClient *http.Client, interval, timeout time.Duration, host, ns string) error {
|
||||
return wait.Poll(interval, timeout, func() (bool, error) {
|
||||
resp, err := WatchETCDCluster(host, ns, httpClient, "0")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
switch resp.StatusCode {
|
||||
case http.StatusOK:
|
||||
return true, nil
|
||||
case http.StatusNotFound: // not set up yet. wait.
|
||||
return false, nil
|
||||
default:
|
||||
return false, fmt.Errorf("invalid status code: %v", resp.Status)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -6,21 +6,11 @@ End-to-end (e2e) testing is automated testing for real user scenarios.
|
|||
|
||||
Prerequisites:
|
||||
- a running k8s cluster and kube config. We will need to pass kube config as arguments.
|
||||
- KUBERNETES_KUBECONFIG_PATH env var is required, e.g. `KUBERNETES_KUBECONFIG_PATH=$HOME/.kube/config`
|
||||
|
||||
As first step, we need to setup environment for testing:
|
||||
```
|
||||
$ ./hack/e2e setup
|
||||
```
|
||||
This will do all preparation work such as creating etcd controller.
|
||||
- Have kubeconfig file ready.
|
||||
- Have etcd controller image ready.
|
||||
|
||||
e2e tests are written as go test. All go test techniques applies, e.g. picking what to run, timeout length.
|
||||
Let's say I want to run all tests in "test/e2e/":
|
||||
```
|
||||
$ go test -v ./test/e2e/
|
||||
```
|
||||
|
||||
Finally, we need to tear down the things we setup before:
|
||||
```
|
||||
$ ./hack/e2e teardown
|
||||
$ go test -v ./test/e2e/ --kubeconfig "$HOME/.kube/config" --controller-image gcr.io/coreos-k8s-scale-testing/kube-etcd-controller:latest
|
||||
```
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -19,10 +18,7 @@ import (
|
|||
)
|
||||
|
||||
func TestCreateCluster(t *testing.T) {
|
||||
f, err := framework.New(os.Getenv("KUBERNETES_KUBECONFIG_PATH"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f := framework.Global
|
||||
myetcd := &cluster.EtcdCluster{
|
||||
TypeMeta: unversioned.TypeMeta{
|
||||
Kind: "EtcdCluster",
|
||||
|
@ -51,7 +47,7 @@ func TestCreateCluster(t *testing.T) {
|
|||
}()
|
||||
|
||||
err = wait.Poll(5*time.Second, 1*time.Minute, func() (done bool, err error) {
|
||||
pods, err := f.KubeClient.Pods("default").List(api.ListOptions{
|
||||
pods, err := f.KubeClient.Pods(f.Namespace.Name).List(api.ListOptions{
|
||||
LabelSelector: labels.SelectorFromSet(map[string]string{
|
||||
"etcd_cluster": "my-etcd",
|
||||
}),
|
||||
|
@ -81,7 +77,7 @@ func getPodNames(pods []api.Pod) []string {
|
|||
|
||||
func postEtcdCluster(f *framework.Framework, body []byte) error {
|
||||
resp, err := f.KubeClient.Client.Post(
|
||||
f.MasterHost+"/apis/coreos.com/v1/namespaces/default/etcdclusters",
|
||||
fmt.Sprintf("%s/apis/coreos.com/v1/namespaces/%s/etcdclusters", f.MasterHost, f.Namespace.Name),
|
||||
"application/json", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -94,7 +90,7 @@ func postEtcdCluster(f *framework.Framework, body []byte) error {
|
|||
|
||||
func deleteEtcdCluster(f *framework.Framework) error {
|
||||
req, err := http.NewRequest("DELETE",
|
||||
f.MasterHost+"/apis/coreos.com/v1/namespaces/default/etcdclusters/my-etcd", nil)
|
||||
fmt.Sprintf("%s/apis/coreos.com/v1/namespaces/%s/etcdclusters/my-etcd", f.MasterHost, f.Namespace.Name), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -1,16 +1,25 @@
|
|||
package framework
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/coreos/kube-etcd-controller/pkg/util/k8sutil"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
|
||||
)
|
||||
|
||||
var Global *Framework
|
||||
|
||||
type Framework struct {
|
||||
KubeClient *unversioned.Client
|
||||
MasterHost string
|
||||
Namespace *api.Namespace
|
||||
}
|
||||
|
||||
func New(kubeconfig string) (*Framework, error) {
|
||||
func New(kubeconfig string, baseName string) (*Framework, error) {
|
||||
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -19,9 +28,67 @@ func New(kubeconfig string) (*Framework, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
namespace, err := cli.Namespaces().Create(&api.Namespace{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
GenerateName: fmt.Sprintf("e2e-test-%v-", baseName),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Framework{
|
||||
MasterHost: config.Host,
|
||||
KubeClient: cli,
|
||||
Namespace: namespace,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *Framework) Setup(ctrlImage string) error {
|
||||
if err := f.setupEtcdController(ctrlImage); err != nil {
|
||||
logrus.Errorf("fail to setup etcd controller: %v", err)
|
||||
return err
|
||||
}
|
||||
logrus.Info("setup finished successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Framework) Teardown() error {
|
||||
// TODO: delete TPR
|
||||
return f.KubeClient.Namespaces().Delete(f.Namespace.Name)
|
||||
}
|
||||
|
||||
func (f *Framework) setupEtcdController(ctrlImage string) error {
|
||||
// TODO: unify this and the yaml file in example/
|
||||
pod := &api.Pod{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "kube-etcd-controller",
|
||||
Labels: map[string]string{"name": "kube-etcd-controller"},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Name: "kube-etcd-controller",
|
||||
Image: ctrlImage,
|
||||
Env: []api.EnvVar{
|
||||
{
|
||||
Name: "MY_POD_NAMESPACE",
|
||||
ValueFrom: &api.EnvVarSource{FieldRef: &api.ObjectFieldSelector{FieldPath: "metadata.namespace"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := f.KubeClient.Pods(f.Namespace.Name).Create(pod)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = k8sutil.WaitEtcdTPRReady(f.KubeClient.Client, 5*time.Second, 90*time.Second, f.MasterHost, f.Namespace.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.Info("etcd controller created successfully")
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/coreos/kube-etcd-controller/test/e2e/framework"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/util/wait"
|
||||
)
|
||||
|
||||
const etcdTPRURL = "/apis/coreos.com/v1/etcdclusters"
|
||||
|
||||
func main() {
|
||||
kubeconfig := flag.String("kubeconfig", "", "kube config path, e.g. $HOME/.kube/config")
|
||||
ctrlImage := flag.String("controller-image", "", "controller image, e.g. gcr.io/coreos-k8s-scale-testing/kube-etcd-controller")
|
||||
flag.Parse()
|
||||
f, err := framework.New(*kubeconfig)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := setupEtcdController(f, *ctrlImage); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
logrus.Info("setup finished successfully")
|
||||
}
|
||||
|
||||
func setupEtcdController(f *framework.Framework, ctrlImage string) error {
|
||||
// TODO: unify this and the yaml file in example/
|
||||
pod := &api.Pod{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "kube-etcd-controller",
|
||||
Labels: map[string]string{"name": "kube-etcd-controller"},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Name: "kube-etcd-controller",
|
||||
Image: ctrlImage,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
_, err := f.KubeClient.Pods("default").Create(pod)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = waitTPRReady(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.Info("etcd controller created successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
func waitTPRReady(f *framework.Framework) error {
|
||||
return wait.Poll(time.Second*20, time.Minute*5, func() (bool, error) {
|
||||
resp, err := f.KubeClient.Client.Get(f.MasterHost + etcdTPRURL)
|
||||
if err != nil {
|
||||
logrus.Errorf("http GET failed: %v", err)
|
||||
return false, err
|
||||
}
|
||||
switch resp.StatusCode {
|
||||
case http.StatusOK:
|
||||
return true, nil
|
||||
case http.StatusNotFound: // not set up yet. wait.
|
||||
logrus.Info("TPR not set up yet. Keep waiting...")
|
||||
return false, nil
|
||||
default:
|
||||
return false, fmt.Errorf("unexpected status code: %v", resp.Status)
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/coreos/kube-etcd-controller/test/e2e/framework"
|
||||
)
|
||||
|
||||
func main() {
|
||||
kubeconfig := flag.String("kubeconfig", "", "kube config path, e.g. $HOME/.kube/config")
|
||||
flag.Parse()
|
||||
f, err := framework.New(*kubeconfig)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// TODO: have unique test namespace (#117) and just delete everything under that.
|
||||
if err := teardownEtcdController(f); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
logrus.Info("teardown finished successfully")
|
||||
}
|
||||
|
||||
func teardownEtcdController(f *framework.Framework) error {
|
||||
err := f.KubeClient.Pods("default").Delete("kube-etcd-controller", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.Info("etcd controller deleted successfully")
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package e2e
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/coreos/kube-etcd-controller/test/e2e/framework"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
kubeconfig := flag.String("kubeconfig", "", "kube config path, e.g. $HOME/.kube/config")
|
||||
ctrlImage := flag.String("controller-image", "", "controller image, e.g. gcr.io/coreos-k8s-scale-testing/kube-etcd-controller")
|
||||
flag.Parse()
|
||||
|
||||
f, err := framework.New(*kubeconfig, "top-level")
|
||||
if err != nil {
|
||||
logrus.Errorf("fail to create new framework: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := f.Setup(*ctrlImage); err != nil {
|
||||
logrus.Errorf("fail to setup test environment: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
framework.Global = f
|
||||
code := m.Run()
|
||||
|
||||
if err := f.Teardown(); err != nil {
|
||||
logrus.Errorf("fail to teardown test environment: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(code)
|
||||
}
|
Загрузка…
Ссылка в новой задаче