Skip to content

Commit

Permalink
test: add e2e tests
Browse files Browse the repository at this point in the history
Signed-off-by: Zespre Schmidt <[email protected]>
  • Loading branch information
starbops committed Nov 18, 2024
1 parent 672c05d commit 9769fca
Show file tree
Hide file tree
Showing 7 changed files with 562 additions and 2 deletions.
39 changes: 37 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ REPO ?= starbops
# Image URL to use all building/pushing image targets
MGR_IMG ?= $(REPO)/virtbmc-controller:$(VERSION)
AGT_IMG ?= $(REPO)/virtbmc:$(VERSION)

K8S_VERSION = 1.28.13
KIND_K8S_VERSION = v$(shell echo $(K8S_VERSION))
# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary.
ENVTEST_K8S_VERSION = 1.28.0
ENVTEST_K8S_VERSION = 1.28.x
export CERT_MANAGER_VERSION = v1.14.2

# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
ifeq (,$(shell go env GOBIN))
Expand Down Expand Up @@ -81,7 +85,7 @@ vet: ## Run go vet against code.

.PHONY: test
test: manifests generate generate-kubevirt-crd fmt vet envtest ## Run tests.
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test ./... -coverprofile cover.out
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $(shell go list ./... | grep -v /test/) -coverprofile cover.out

GOLANGCI_LINT = $(shell pwd)/bin/golangci-lint
GOLANGCI_LINT_VERSION ?= v1.61.0
Expand All @@ -91,6 +95,29 @@ golangci-lint:
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell dirname $(GOLANGCI_LINT)) $(GOLANGCI_LINT_VERSION) ;\
}

## Decide the image tag based on git state for e2e tests usage
VERSION = $(shell git rev-parse --short HEAD)
DIRTY :=
ifneq ($(shell git status --porcelain --untracked-files=no),)
DIRTY := -dirty
endif
export TAG = $(VERSION)$(DIRTY)

.PHONY: e2e-setup
e2e-setup: kind ## Setup end-to-end test environment.
$(KIND) create cluster --name kvbmc-e2e --config test/e2e/kind-config.yaml --image=kindest/node:$(KIND_K8S_VERSION)

.PHONY: e2e-teardown
e2e-teardown: kind ## Teardown end-to-end test environment.
$(KIND) delete cluster --name kvbmc-e2e

.PHONY: e2e-test
e2e-test: generate fmt vet kind ## Run end-to-end tests.
go test -v ./test/...

.PHONY: local-e2e-test
local-e2e-test: e2e-setup e2e-test e2e-teardown ## Run end-to-end tests locally.

.PHONY: lint
lint: golangci-lint ## Run golangci-lint linter & yamllint
$(GOLANGCI_LINT) run
Expand Down Expand Up @@ -180,10 +207,12 @@ KUBECTL ?= kubectl
KUSTOMIZE ?= $(LOCALBIN)/kustomize
CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen
ENVTEST ?= $(LOCALBIN)/setup-envtest
KIND ?= $(LOCALBIN)/kind

## Tool Versions
KUSTOMIZE_VERSION ?= v5.2.1
CONTROLLER_TOOLS_VERSION ?= v0.15.0
KIND_VERSION ?= v0.24.0

.PHONY: kustomize
kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. If wrong version is installed, it will be removed before downloading.
Expand All @@ -204,3 +233,9 @@ $(CONTROLLER_GEN): $(LOCALBIN)
envtest: $(ENVTEST) ## Download envtest-setup locally if necessary.
$(ENVTEST): $(LOCALBIN)
test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest

.PHONY: kind
kind: $(KIND) ## Download kind locally if necessary.
$(KIND): $(LOCALBIN)
test -s $(LOCALBIN)/kind || GOBIN=$(LOCALBIN) GO111MODULE=on go install sigs.k8s.io/kind@$(KIND_VERSION)

152 changes: 152 additions & 0 deletions test/e2e/e2e_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package e2e

import (
"context"
"fmt"
"os"
"os/exec"
"path"
"testing"
"time"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apiextcs "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
kubevirtv1 "kubevirt.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"

virtualmachinev1 "kubevirt.io/kubevirtbmc/api/v1"
"kubevirt.io/kubevirtbmc/test/util"
)

const (
timeout = time.Second * 60
interval = time.Millisecond * 250
)

var (
skipCertManagerInstall = os.Getenv("CERT_MANAGER_INSTALL_SKIP") == "true"
isCertManagerAlreadyInstalled = false

controllerManagerImage = fmt.Sprintf("starbops/virtbmc-controller:%s", func() string {
if tag := os.Getenv("TAG"); tag != "" {
return tag
}
return "dev"
}())
agentImage = fmt.Sprintf("starbops/virtbmc:%s", func() string {
if tag := os.Getenv("TAG"); tag != "" {
return tag
}
return "dev"
}())

config *rest.Config
crdClient *apiextcs.Clientset
k8sClient client.Client
)

// TestE2E runs the end-to-end (e2e) test suite for the project. These tests execute in an isolated,
// temporary environment to validate project changes with the the purposed to be used in CI jobs.
// The default setup requires Kind, builds/loads the Manager Docker image locally, and installs
// CertManager.
func TestE2E(t *testing.T) {
RegisterFailHandler(Fail)
_, _ = fmt.Fprintf(GinkgoWriter, "Starting KubeVirtBMC end-to-end test suite\n")
RunSpecs(t, "E2E Suite")
}

var _ = BeforeSuite(func() {
logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))

By("building the controller-manager image")
cmd := exec.Command("make", "docker-build", fmt.Sprintf("IMG=%s", controllerManagerImage))
_, err := util.Run(cmd)
Expect(err).ToNot(HaveOccurred())

By("building the agent image")
cmd = exec.Command("make", "docker-build-virtbmc", fmt.Sprintf("IMG=%s", agentImage))
_, err = util.Run(cmd)
Expect(err).ToNot(HaveOccurred())

By("loading the built images to the Kind cluster")
err = util.LoadImageToKindClusterWithName(controllerManagerImage)
Expect(err).ToNot(HaveOccurred())
err = util.LoadImageToKindClusterWithName(agentImage)
Expect(err).ToNot(HaveOccurred())

By("creating the clientsets")
config, err = getClientConfig()
Expect(err).ToNot(HaveOccurred())
crdClient, err = apiextcs.NewForConfig(config)
Expect(err).ToNot(HaveOccurred())
Expect(kubevirtv1.AddToScheme(scheme.Scheme)).To(Succeed())
Expect(virtualmachinev1.AddToScheme(scheme.Scheme)).To(Succeed())
k8sClient, err = client.New(config, client.Options{Scheme: scheme.Scheme})
Expect(err).ToNot(HaveOccurred())

By("creating the VirtualMachine CRD")
err = util.CreateOrUpdateCRD(crdClient, "../../config/kubevirt-crd/kubevirt.io_virtualmachines.yaml")
Expect(err).ToNot(HaveOccurred())
Eventually(func() bool {
crd, err := crdClient.ApiextensionsV1().CustomResourceDefinitions().Get(
context.TODO(),
"virtualmachines.kubevirt.io",
metav1.GetOptions{},
)
if err != nil {
return false
}
for _, cond := range crd.Status.Conditions {
if cond.Type == apiextv1.Established && cond.Status == apiextv1.ConditionTrue {
return true
}
}
return false
}, timeout, interval).Should(BeTrue())

if !skipCertManagerInstall {
By("checking if cert-manager is installed already")
isCertManagerAlreadyInstalled = util.IsCertManagerCRDsInstalled()
if !isCertManagerAlreadyInstalled {
_, _ = fmt.Fprintf(GinkgoWriter, "Installing cert-manager...\n")
err = util.InstallCertManager()
Expect(err).ToNot(HaveOccurred())
} else {
_, _ = fmt.Fprintf(GinkgoWriter, "WARNING: cert-manager is already installed. Skipping installation...\n")
}
}

By("deploying the controller-manager")
cmd = exec.Command("make", "deploy", fmt.Sprintf("IMG=%s", controllerManagerImage))
_, err = util.Run(cmd)
Expect(err).ToNot(HaveOccurred())
})

var _ = AfterSuite(func() {
var err error

By("undeploying the controller-manager")
cmd := exec.Command("make", "undeploy")
_, _ = util.Run(cmd)

if !skipCertManagerInstall && !isCertManagerAlreadyInstalled {
_, _ = fmt.Fprintf(GinkgoWriter, "Uninstalling cert-manager...\n")
util.UninstallCertManager()
}

By("deleting VirtualMachine CRD")
err = util.DeleteCRD(crdClient, "../../config/kubevirt-crd/kubevirt.io_virtualmachines.yaml")
Expect(err).ToNot(HaveOccurred())
})

func getClientConfig() (*rest.Config, error) {
return clientcmd.BuildConfigFromFlags("", path.Join(os.Getenv("HOME"), ".kube", "config"))
}
140 changes: 140 additions & 0 deletions test/e2e/e2e_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package e2e

import (
"context"
"strings"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
kubevirtv1 "kubevirt.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"

virtualmachinev1 "kubevirt.io/kubevirtbmc/api/v1"
)

const (
kubeVirtBMCNamespace = "kubevirtbmc-system"
)

var _ = Describe("KubeVirtBMC controller manager", Ordered, func() {

It("should run successfully", func() {
By("validating the controller-manager pod exists")
var podList corev1.PodList
sets := labels.Set{
"control-plane": "controller-manager",
}
err := k8sClient.List(context.TODO(), &podList, &client.ListOptions{
LabelSelector: labels.SelectorFromSet(sets),
})
Expect(err).ToNot(HaveOccurred())
Expect(podList.Items).To(HaveLen(1), "expected one controller-manager pod exists")

By("validating the controller-manager pod is ready to serve")
var pod corev1.Pod
podLookupKey := types.NamespacedName{
Namespace: podList.Items[0].Namespace,
Name: podList.Items[0].Name,
}
Eventually(func() bool {
err := k8sClient.Get(context.TODO(), podLookupKey, &pod)
if err != nil {
return false
}
for _, condition := range pod.Status.Conditions {
if condition.Type == corev1.PodReady && condition.Status == corev1.ConditionTrue {
return true
}
}
return false
}, timeout, interval).Should(BeTrue())
})

Context("initially", func() {
It("should have no VirtualMachineBMCs", func() {
var vmBMCList virtualmachinev1.VirtualMachineBMCList
err := k8sClient.List(context.TODO(), &vmBMCList, &client.ListOptions{})
Expect(err).ToNot(HaveOccurred())
Expect(vmBMCList.Items).To(HaveLen(0))
})
})

Context("a new virtual machine", func() {
var (
vm kubevirtv1.VirtualMachine
createdVMBMC *virtualmachinev1.VirtualMachineBMC
)

Specify("a VirtualMachine", func() {
vm = kubevirtv1.VirtualMachine{
ObjectMeta: metav1.ObjectMeta{
Name: "test-vm",
Namespace: "default",
},
Spec: kubevirtv1.VirtualMachineSpec{
Running: func(b bool) *bool { return &b }(true),
Template: &kubevirtv1.VirtualMachineInstanceTemplateSpec{
Spec: kubevirtv1.VirtualMachineInstanceSpec{
Domain: kubevirtv1.DomainSpec{
Devices: kubevirtv1.Devices{
Disks: []kubevirtv1.Disk{},
Interfaces: []kubevirtv1.Interface{},
},
},
},
},
},
}
err := k8sClient.Create(context.TODO(), &vm)
Expect(err).ToNot(HaveOccurred())
})

It("should create a VirtualMachineBMC", func() {
vmBMCLookupKey := types.NamespacedName{
Name: strings.Join([]string{vm.Namespace, vm.Name}, "-"),
Namespace: kubeVirtBMCNamespace,
}
createdVMBMC = &virtualmachinev1.VirtualMachineBMC{}
Eventually(func() bool {
err := k8sClient.Get(context.TODO(), vmBMCLookupKey, createdVMBMC)
return err == nil
}, timeout, interval).Should(BeTrue())
})

It("should create a agent Pod", func() {
agentPodLookupKey := types.NamespacedName{
Name: strings.Join([]string{createdVMBMC.Name, "virtbmc"}, "-"),
Namespace: kubeVirtBMCNamespace,
}
pod := &corev1.Pod{}
Eventually(func() bool {
err := k8sClient.Get(context.TODO(), agentPodLookupKey, pod)
if err != nil {
return false
}
for _, condition := range pod.Status.Conditions {
if condition.Type == corev1.PodReady && condition.Status == corev1.ConditionTrue {
return true
}
}
return false
}, timeout, interval).Should(BeTrue())
})

It("should create a agent Service", func() {
agentSvcLookupKey := types.NamespacedName{
Name: strings.Join([]string{createdVMBMC.Name, "virtbmc"}, "-"),
Namespace: kubeVirtBMCNamespace,
}
svc := &corev1.Service{}
Eventually(func() bool {
err := k8sClient.Get(context.TODO(), agentSvcLookupKey, svc)
return err == nil
}, timeout, interval).Should(BeTrue())
})
})
})
7 changes: 7 additions & 0 deletions test/e2e/kind-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apiVersion: kind.x-k8s.io/v1alpha4
kind: Cluster
nodes:
- role: control-plane
- role: worker
- role: worker

Loading

0 comments on commit 9769fca

Please sign in to comment.