containerized-data-importer/tests/framework/framework.go
j-griffith 0a84e2fa8c Add lint checks to remaining go src directories
This finishes up the last of the golint implementation, with the
addition of the cmd, tests and tools directories we are now running
golint tests on all of the current go source files in the project.

This change adds all the little fixes (mostly just commenting and
naming) and also enables the new diretories in the lint test that we
gate on.
2018-09-29 08:35:21 -06:00

301 lines
10 KiB
Go

package framework
import (
"flag"
"fmt"
"time"
"github.com/onsi/ginkgo"
"github.com/onsi/gomega"
"github.com/golang/glog"
"github.com/pkg/errors"
"k8s.io/api/core/v1"
apierrs "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
cdiClientset "kubevirt.io/containerized-data-importer/pkg/client/clientset/versioned"
"kubevirt.io/containerized-data-importer/pkg/common"
"kubevirt.io/containerized-data-importer/tests/utils"
"kubevirt.io/qe-tools/pkg/ginkgo-reporters"
)
const (
nsCreateTime = 30 * time.Second
nsDeleteTime = 5 * time.Minute
//NsPrefixLabel provides a cdi prefix label to identify the test namespace
NsPrefixLabel = "cdi-e2e"
cdiPodPrefix = "cdi-deployment"
)
// run-time flags
var (
kubectlPath *string
ocPath *string
cdiInstallNs *string
kubeConfig *string
master *string
goCLIPath *string
)
// Config provides some basic test config options
type Config struct {
// SkipNamespaceCreation sets whether to skip creating a namespace. Use this ONLY for tests that do not require
// a namespace at all, like basic sanity or other global tests.
SkipNamespaceCreation bool
// SkipControllerPodLookup sets whether to skip looking up the name of the cdi controller pod.
SkipControllerPodLookup bool
}
// Framework supports common operations used by functional/e2e tests. It holds the k8s and cdi clients,
// a generated unique namespace, run-time flags, and more fields will be added over time as cdi e2e
// evolves. Global BeforeEach and AfterEach are called in the Framework constructor.
type Framework struct {
Config
// NsPrefix is a prefix for generated namespace
NsPrefix string
// k8sClient provides our k8s client pointer
K8sClient *kubernetes.Clientset
// CdiClient provides our CDI client pointer
CdiClient *cdiClientset.Clientset
// RestConfig provides a pointer to our REST client config.
RestConfig *rest.Config
// Namespace provides a namespace for each test generated/unique ns per test
Namespace *v1.Namespace
// Namespace2 provides an additional generated/unique secondary ns for testing across namespaces (eg. clone tests)
Namespace2 *v1.Namespace // note: not instantiated in NewFramework
namespacesToDelete []*v1.Namespace
// ControllerPod provides a pointer to our test controller pod
ControllerPod *v1.Pod
// KubectlPath is a test run-time flag so we can find kubectl
KubectlPath string
// OcPath is a test run-time flag so we can find OpenShift Client
OcPath string
// CdiInstallNs is a test run-time flag to store the Namespace we installed CDI in
CdiInstallNs string
// KubeConfig is a test run-time flag to store the location of our test setup kubeconfig
KubeConfig string
// Master is a test run-time flag to store the id of our master node
Master string
// GoCliPath is a test run-time flag to store the location of gocli
GoCLIPath string
}
// TODO: look into k8s' SynchronizedBeforeSuite() and SynchronizedAfterSuite() code and their general
// purpose test/e2e/framework/cleanup.go function support.
// initialize run-time flags
func init() {
// By accessing something in the ginkgo_reporters package, we are ensuring that the init() is called
// That init calls flag.StringVar, and makes sure the --junit-output flag is added before we call
// flag.Parse in NewFramework. Without this, the flag is NOT added.
fmt.Fprintf(ginkgo.GinkgoWriter, "Making sure junit flag is available %v\n", ginkgo_reporters.JunitOutput)
kubectlPath = flag.String("kubectl-path", "kubectl", "The path to the kubectl binary")
ocPath = flag.String("oc-path", "oc", "The path to the oc binary")
cdiInstallNs = flag.String("cdi-namespace", "kube-system", "The namespace of the CDI controller")
kubeConfig = flag.String("kubeconfig", "/var/run/kubernetes/admin.kubeconfig", "The absolute path to the kubeconfig file")
master = flag.String("master", "", "master url:port")
goCLIPath = flag.String("gocli-path", "cli.sh", "The path to cli script")
}
// NewFrameworkOrDie calls NewFramework and handles errors by calling Fail. Config is optional, but
// if passed there can only be one.
func NewFrameworkOrDie(prefix string, config ...Config) *Framework {
cfg := Config{}
if len(config) > 0 {
cfg = config[0]
}
f, err := NewFramework(prefix, cfg)
if err != nil {
ginkgo.Fail(fmt.Sprintf("failed to create test framework with config %+v: %v", cfg, err))
}
return f
}
// NewFramework makes a new framework and sets up the global BeforeEach/AfterEach's.
// Test run-time flags are parsed and added to the Framework struct.
func NewFramework(prefix string, config Config) (*Framework, error) {
f := &Framework{
Config: config,
NsPrefix: prefix,
}
// handle run-time flags
if !flag.Parsed() {
flag.Parse()
fmt.Fprintf(ginkgo.GinkgoWriter, "** Test flags:\n")
flag.Visit(func(f *flag.Flag) {
fmt.Fprintf(ginkgo.GinkgoWriter, " %s = %q\n", f.Name, f.Value.String())
})
fmt.Fprintf(ginkgo.GinkgoWriter, "**\n")
}
f.KubectlPath = *kubectlPath
f.OcPath = *ocPath
f.CdiInstallNs = *cdiInstallNs
f.KubeConfig = *kubeConfig
f.Master = *master
f.GoCLIPath = *goCLIPath
restConfig, err := f.LoadConfig()
if err != nil {
// Can't use Expect here due this being called outside of an It block, and Expect
// requires any calls to it to be inside an It block.
return nil, errors.Wrap(err, "ERROR, unable to load RestConfig")
}
f.RestConfig = restConfig
// clients
kcs, err := f.GetKubeClient()
if err != nil {
return nil, errors.Wrap(err, "ERROR, unable to create K8SClient")
}
f.K8sClient = kcs
cs, err := f.GetCdiClient()
if err != nil {
return nil, errors.Wrap(err, "ERROR, unable to create CdiClient")
}
f.CdiClient = cs
ginkgo.BeforeEach(f.BeforeEach)
ginkgo.AfterEach(f.AfterEach)
return f, err
}
// BeforeEach provides a set of operations to run before each test
func (f *Framework) BeforeEach() {
if !f.SkipControllerPodLookup {
if f.ControllerPod == nil {
pod, err := utils.FindPodByPrefix(f.K8sClient, f.CdiInstallNs, cdiPodPrefix, common.CDILabelSelector)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
fmt.Fprintf(ginkgo.GinkgoWriter, "INFO: Located cdi-controller-pod: %q\n", pod.Name)
f.ControllerPod = pod
}
}
if !f.SkipNamespaceCreation {
// generate unique primary ns (ns2 not created here)
ginkgo.By(fmt.Sprintf("Building a %q namespace api object", f.NsPrefix))
ns, err := f.CreateNamespace(f.NsPrefix, map[string]string{
NsPrefixLabel: f.NsPrefix,
})
gomega.Expect(err).NotTo(gomega.HaveOccurred())
f.Namespace = ns
f.AddNamespaceToDelete(ns)
}
}
// AfterEach provides a set of operations to run after each test
func (f *Framework) AfterEach() {
// delete the namespace(s) in a defer in case future code added here could generate
// an exception. For now there is only a defer.
defer func() {
for _, ns := range f.namespacesToDelete {
defer func() { f.namespacesToDelete = nil }()
if ns == nil || len(ns.Name) == 0 {
continue
}
ginkgo.By(fmt.Sprintf("Destroying namespace %q for this suite.", ns.Name))
err := DeleteNS(f.K8sClient, ns.Name)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
}
}()
return
}
// CreateNamespace instantiates a new namespace object with a unique name and the passed-in label(s).
func (f *Framework) CreateNamespace(prefix string, labels map[string]string) (*v1.Namespace, error) {
ns := &v1.Namespace{
ObjectMeta: metav1.ObjectMeta{
GenerateName: fmt.Sprintf("cdi-e2e-tests-%s-", prefix),
Namespace: "",
Labels: labels,
},
Status: v1.NamespaceStatus{},
}
var nsObj *v1.Namespace
c := f.K8sClient
err := wait.PollImmediate(2*time.Second, nsCreateTime, func() (bool, error) {
var err error
nsObj, err = c.CoreV1().Namespaces().Create(ns)
if err == nil || apierrs.IsAlreadyExists(err) {
return true, nil // done
}
glog.Warningf("Unexpected error while creating %q namespace: %v", ns.GenerateName, err)
return false, err // keep trying
})
if err != nil {
return nil, err
}
fmt.Fprintf(ginkgo.GinkgoWriter, "INFO: Created new namespace %q\n", nsObj.Name)
return nsObj, nil
}
// AddNamespaceToDelete provides a wrapper around the go append function
func (f *Framework) AddNamespaceToDelete(ns *v1.Namespace) {
f.namespacesToDelete = append(f.namespacesToDelete, ns)
}
// DeleteNS provides a function to delete the specified namespace from the test cluster
func DeleteNS(c *kubernetes.Clientset, ns string) error {
return wait.PollImmediate(2*time.Second, nsDeleteTime, func() (bool, error) {
err := c.CoreV1().Namespaces().Delete(ns, nil)
if err != nil && !apierrs.IsNotFound(err) {
glog.Warningf("namespace %q Delete api err: %v", ns, err)
return false, nil // keep trying
}
// see if ns is really deleted
_, err = c.CoreV1().Namespaces().Get(ns, metav1.GetOptions{})
if apierrs.IsNotFound(err) {
return true, nil // deleted, done
}
if err != nil {
glog.Warningf("namespace %q Get api error: %v", ns, err)
}
return false, nil // keep trying
})
}
// GetCdiClient gets an instance of a kubernetes client that includes all the CDI extensions.
func (f *Framework) GetCdiClient() (*cdiClientset.Clientset, error) {
cfg, err := clientcmd.BuildConfigFromFlags(f.Master, f.KubeConfig)
if err != nil {
return nil, err
}
cdiClient, err := cdiClientset.NewForConfig(cfg)
if err != nil {
return nil, err
}
return cdiClient, nil
}
// GetKubeClient returns a Kubernetes rest client
func (f *Framework) GetKubeClient() (*kubernetes.Clientset, error) {
return GetKubeClientFromRESTConfig(f.RestConfig)
}
// LoadConfig loads our specified kubeconfig
func (f *Framework) LoadConfig() (*rest.Config, error) {
return clientcmd.BuildConfigFromFlags(f.Master, f.KubeConfig)
}
// GetKubeClientFromRESTConfig provides a function to get a K8s client using hte REST config
func GetKubeClientFromRESTConfig(config *rest.Config) (*kubernetes.Clientset, error) {
config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs}
config.APIPath = "/apis"
config.ContentType = runtime.ContentTypeJSON
return kubernetes.NewForConfig(config)
}