temp commit
This commit is contained in:
291
pkg/test/testing.go
Normal file
291
pkg/test/testing.go
Normal file
@@ -0,0 +1,291 @@
|
||||
// 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 {
|
||||
f(scheme.Scheme)
|
||||
}
|
||||
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)
|
||||
}
|
||||
73
pkg/test/yaml.go
Normal file
73
pkg/test/yaml.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
|
||||
k8syaml "k8s.io/apimachinery/pkg/util/yaml"
|
||||
)
|
||||
|
||||
const maxExecutiveEmpties = 100
|
||||
|
||||
// Scanner scans a yaml manifest file for manifest tokens delimited by "---".
|
||||
// See bufio.Scanner for semantics.
|
||||
type Scanner struct {
|
||||
reader *k8syaml.YAMLReader
|
||||
token []byte // Last token returned by split.
|
||||
err error // Sticky error.
|
||||
empties int // Count of successive empty tokens.
|
||||
done bool // Scan has finished.
|
||||
}
|
||||
|
||||
func NewYAMLScanner(b []byte) *Scanner {
|
||||
r := bufio.NewReader(bytes.NewBuffer(b))
|
||||
return &Scanner{reader: k8syaml.NewYAMLReader(r)}
|
||||
}
|
||||
|
||||
func (s *Scanner) Err() error {
|
||||
if s.err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return s.err
|
||||
}
|
||||
|
||||
func (s *Scanner) Scan() bool {
|
||||
if s.done {
|
||||
return false
|
||||
}
|
||||
|
||||
var (
|
||||
tok []byte
|
||||
err error
|
||||
)
|
||||
|
||||
for {
|
||||
tok, err = s.reader.Read()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
s.done = true
|
||||
}
|
||||
s.err = err
|
||||
return false
|
||||
}
|
||||
if len(bytes.TrimSpace(tok)) == 0 {
|
||||
s.empties++
|
||||
if s.empties > maxExecutiveEmpties {
|
||||
panic("yaml.Scan: too many empty tokens without progressing")
|
||||
}
|
||||
continue
|
||||
}
|
||||
s.empties = 0
|
||||
s.token = tok
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Scanner) Text() string {
|
||||
return string(s.token)
|
||||
}
|
||||
|
||||
func (s *Scanner) Bytes() []byte {
|
||||
return s.token
|
||||
}
|
||||
Reference in New Issue
Block a user