Files
kubesphere/pkg/test/testing.go
2019-09-02 16:54:34 +08:00

296 lines
8.2 KiB
Go

// Copyright 2018 The Kubesphere 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 test
import (
"context"
"fmt"
"io"
"io/ioutil"
"strconv"
"strings"
"testing"
"time"
"k8s.io/klog"
"github.com/prometheus/common/log"
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
extscheme "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/config"
)
type TestCtx struct {
Client client.Client
ID string
t *testing.T
cleanupFns []cleanupFn
Namespace string
}
type CleanupOptions struct {
TestContext *TestCtx
Timeout time.Duration
RetryInterval time.Duration
}
type cleanupFn func(option *CleanupOptions) error
type AddToSchemeFunc = func(*runtime.Scheme) error
func NewTestCtx(t *testing.T, namespace string) *TestCtx {
var prefix string
if t != nil {
// TestCtx is used among others for namespace names where '/' is forbidden
prefix = strings.TrimPrefix(
strings.Replace(
strings.ToLower(t.Name()),
"/",
"-",
-1,
),
"test",
)
} else {
prefix = "main"
}
id := prefix + "-" + strconv.FormatInt(time.Now().Unix(), 10)
return &TestCtx{
ID: id,
t: t,
Namespace: namespace,
}
}
func (t *TestCtx) Setup(yamlPath string, crdPath string, schemes ...AddToSchemeFunc) error {
cfg, err := config.GetConfig()
if err != nil {
klog.Error("Failed to get kubeconfig")
return err
}
for _, f := range schemes {
err = f(scheme.Scheme)
if err != nil {
klog.Errorln("Failed to add scheme")
return err
}
}
extscheme.AddToScheme(scheme.Scheme)
dynClient, err := client.New(cfg, client.Options{})
if err != nil {
return err
}
t.Client = dynClient
err = EnsureNamespace(t.Client, t.Namespace)
if err != nil {
return err
}
bytes, err := ioutil.ReadFile(yamlPath)
if err != nil {
klog.Errorln("Failed to read yaml file")
return err
}
err = t.CreateFromYAML(bytes, true)
if err != nil {
klog.Error("Failed to install controller")
return err
}
return nil
}
func WaitForController(c client.Client, namespace, name string, replica int32, retryInterval, timeout time.Duration) error {
err := wait.Poll(retryInterval, timeout, func() (done bool, err error) {
controller := &appsv1.Deployment{}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
err = c.Get(ctx, types.NamespacedName{Namespace: namespace, Name: name}, controller)
if apierrors.IsNotFound(err) {
klog.Infof("Cannot find controller %s", name)
return false, nil
}
if err != nil {
klog.Errorf("Get error %s when waiting for controller up", err.Error())
return false, err
}
if controller.Status.ReadyReplicas == replica {
return true, nil
}
return false, nil
})
return err
}
func WaitForDeletion(dynclient client.Client, obj runtime.Object, retryInterval, timeout time.Duration) error {
key, err := client.ObjectKeyFromObject(obj)
if err != nil {
return err
}
kind := obj.GetObjectKind().GroupVersionKind().Kind
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
err = wait.Poll(retryInterval, timeout, func() (done bool, err error) {
err = dynclient.Get(ctx, key, obj)
if apierrors.IsNotFound(err) {
return true, nil
}
if err != nil {
klog.Errorf("Get error %s when waiting for controller down", err.Error())
return false, err
}
klog.Infof("Waiting for %s %s to be deleted\n", kind, key)
return false, nil
})
if err != nil {
return err
}
return nil
}
func GetLogOfPod(rest *rest.RESTClient, namespace, name string, logOptions *corev1.PodLogOptions, out io.Writer) error {
req := rest.Get().Namespace(namespace).Name(name).SubResource("log").Param("follow", strconv.FormatBool(logOptions.Follow)).
Param("container", logOptions.Container).
Param("previous", strconv.FormatBool(logOptions.Previous)).
Param("timestamps", strconv.FormatBool(logOptions.Timestamps))
if logOptions.SinceSeconds != nil {
req.Param("sinceSeconds", strconv.FormatInt(*logOptions.SinceSeconds, 10))
}
if logOptions.SinceTime != nil {
req.Param("sinceTime", logOptions.SinceTime.Format(time.RFC3339))
}
if logOptions.LimitBytes != nil {
req.Param("limitBytes", strconv.FormatInt(*logOptions.LimitBytes, 10))
}
if logOptions.TailLines != nil {
req.Param("tailLines", strconv.FormatInt(*logOptions.TailLines, 10))
}
readCloser, err := req.Stream()
if err != nil {
return err
}
defer readCloser.Close()
_, err = io.Copy(out, readCloser)
return err
}
func (ctx *TestCtx) CreateFromYAML(yamlFile []byte, skipIfExists bool) error {
decode := scheme.Codecs.UniversalDeserializer().Decode
scanner := NewYAMLScanner(yamlFile)
for scanner.Scan() {
yamlSpec := scanner.Bytes()
obj, groupVersionKind, err := decode(yamlSpec, nil, nil)
if err != nil {
klog.Errorf("Error while decoding YAML object. Err was: %s", err)
return err
}
klog.Infof("Successfully decode object %v", groupVersionKind)
err = ctx.Client.Create(context.TODO(), obj)
if skipIfExists && apierrors.IsAlreadyExists(err) {
continue
}
if err != nil {
klog.Errorf("Failed to create %v to k8s", obj)
return err
}
}
if err := scanner.Err(); err != nil {
return fmt.Errorf("failed to scan manifest: (%v)", err)
}
return nil
}
func (ctx *TestCtx) GetID() string {
return ctx.ID
}
func (ctx *TestCtx) Cleanup(option *CleanupOptions) {
failed := false
for i := len(ctx.cleanupFns) - 1; i >= 0; i-- {
err := ctx.cleanupFns[i](option)
if err != nil {
failed = true
if ctx.t != nil {
ctx.t.Errorf("A cleanup function failed with error: (%v)\n", err)
} else {
log.Errorf("A cleanup function failed with error: (%v)", err)
}
}
}
if ctx.t == nil && failed {
log.Fatal("A cleanup function failed")
}
}
func (ctx *TestCtx) AddCleanupFn(fn cleanupFn) {
ctx.cleanupFns = append(ctx.cleanupFns, fn)
}
func WaitForJobSucceed(c client.Client, namespace, name string, retryInterval, timeout time.Duration) error {
return waitForJobStatus(c, namespace, name, batchv1.JobComplete, retryInterval, timeout)
}
func WaitForJobFail(c client.Client, namespace, name string, retryInterval, timeout time.Duration) error {
return waitForJobStatus(c, namespace, name, batchv1.JobFailed, retryInterval, timeout)
}
func waitForJobStatus(c client.Client, namespace, name string, jobstatus batchv1.JobConditionType, retryInterval, timeout time.Duration) error {
err := wait.Poll(retryInterval, timeout, func() (done bool, err error) {
job := &batchv1.Job{}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
err = c.Get(ctx, types.NamespacedName{Namespace: namespace, Name: name}, job)
if apierrors.IsNotFound(err) {
klog.Infof("Cannot find job %s", name)
return false, nil
}
if err != nil {
klog.Errorf("Get error %s when waiting for job up", err.Error())
return false, err
}
if len(job.Status.Conditions) > 0 && job.Status.Conditions[0].Type == jobstatus {
return true, nil
}
return false, nil
})
return err
}
func EnsureNamespace(c client.Client, namespace string) error {
ns := &corev1.Namespace{}
ns.Name = namespace
err := c.Create(context.TODO(), ns)
if err != nil {
if apierrors.IsAlreadyExists(err) {
klog.Infof("Namespace %s is existed", namespace)
return nil
}
}
return err
}
func DeleteNamespace(c client.Client, namespace string) error {
ns := &corev1.Namespace{}
ns.Name = namespace
return c.Delete(context.TODO(), ns)
}