Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unit tests with fake client #34

Merged
merged 6 commits into from
Mar 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ name: E2E
kne_ref:
description: "openconfig/kne reference (tag, commit, branch)"
type: string
default: v0.1.7
default: v0.1.8
required: true
kind_version:
description: "KinD version"
Expand Down
2 changes: 1 addition & 1 deletion .mk/e2e.mk
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# KNE_REF is a git reference to use for KNE. It can be a branch, tag, or commit hash.
KNE_REF ?= dee1995a7ec0f446b159a35fad201df9185ee75d
KNE_REF ?= v0.1.8
KNE_REPO := https://github.com/openconfig/kne.git
KNE_TEMP_DIR := /tmp/.srlcontroller-tests/kne
KNE_TEST_DEPLOYMENT_FILE := ${KNE_TEMP_DIR}/deploy/kne/kind-bridge-no-controllers.yaml
Expand Down
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,11 @@ vet: ## Run go vet against code.

.PHONY: test
test: manifests generate 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 ./... -coverpkg=./... -coverprofile cover.out

.PHONY: unit-test
unit-test: manifests generate fmt vet ## Run unit tests which do not involve envtest.
go test -cover ./api/... ./controllers/... -coverprofile cover.out

##@ Build

Expand Down
227 changes: 107 additions & 120 deletions controllers/srlinux_controller_test.go
Original file line number Diff line number Diff line change
@@ -1,145 +1,132 @@
/*
Copyright (c) 2021 Nokia. All rights reserved.


Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package controllers

import (
"context"
"fmt"
"os"
"testing"
"time"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

srlinuxv1 "github.com/srl-labs/srl-controller/api/v1"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
)

var _ = Describe("Srlinux controller", func() {
// Define utility constants for object names and testing timeouts/durations and intervals.
const (
SrlinuxName = "test-srlinux"
SrlinuxNamespace = "test"
)

Context("Srlinux controller test", func() {
ctx := context.Background()

namespace := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: SrlinuxNamespace,
},
}
"k8s.io/client-go/kubernetes/scheme"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

typeNamespaceName := types.NamespacedName{Name: SrlinuxName, Namespace: SrlinuxNamespace}

BeforeEach(func() {
By("Creating the Namespace to perform the tests")
Expect(k8sClient.Create(ctx, namespace)).Should(Succeed())
})

AfterEach(func() {
By("Deleting the Namespace to perform the tests")
_ = k8sClient.Delete(ctx, namespace)
})

It("should successfully reconcile a custom resource for Srlinux created with just an image referenced", func() {
By("Checking that Srlinux resource doesn't exist in the cluster")

srlinux := &srlinuxv1.Srlinux{}

err := k8sClient.Get(ctx, typeNamespaceName, srlinux)
ctrl "sigs.k8s.io/controller-runtime"
)

Expect(errors.IsNotFound(err)).To(BeTrue())
var (
ctx = context.TODO()
defaultCRName = "srlinux-test"
defaultNamespace = "test"
namespacedName = types.NamespacedName{Name: defaultCRName, Namespace: defaultNamespace}
defaultSrlinuxImage = "srlinux:latest"
)

By("Creating the custom resource for the Kind Srlinux")
srlinux = &srlinuxv1.Srlinux{
ObjectMeta: metav1.ObjectMeta{
Name: SrlinuxName,
Namespace: SrlinuxNamespace,
},
TypeMeta: metav1.TypeMeta{
Kind: "Srlinux",
APIVersion: "kne.srlinux.dev/v1",
},
Spec: srlinuxv1.SrlinuxSpec{
Config: &srlinuxv1.NodeConfig{
Image: "srlinux:latest",
func TestMain(m *testing.M) {
err := srlinuxv1.AddToScheme(scheme.Scheme)
if err != nil {
panic(err)
}

opts := zap.Options{
Development: true,
}
ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))

// default timers for gomega Eventually
SetDefaultEventuallyPollingInterval(100 * time.Millisecond)
SetDefaultEventuallyTimeout(30 * time.Second)

os.Exit(m.Run())
}

func TestSrlinuxReconcile(t *testing.T) {
testsCases := []struct {
descr string
clientObjs []runtime.Object
// testFn is a test function that is called for a particukar test case
testFn func(t *testing.T, c client.Client, reconciler SrlinuxReconciler, g *GomegaWithT)
}{
{
descr: "valid SR Linux CR exists",
clientObjs: []runtime.Object{
&srlinuxv1.Srlinux{
ObjectMeta: ctrl.ObjectMeta{
Name: defaultCRName,
Namespace: defaultNamespace,
},
Spec: srlinuxv1.SrlinuxSpec{
Config: &srlinuxv1.NodeConfig{
Image: defaultSrlinuxImage,
},
},
},
},
testFn: testReconcileForBasicSrlCR,
},
{
descr: "SR Linux CR doesn't exists (e.g. deleted)",
clientObjs: []runtime.Object{},
testFn: testReconcileForDeletedCR,
},
}

for _, tc := range testsCases {
t.Run(tc.descr, func(t *testing.T) {
fakeClient := fake.NewClientBuilder().WithRuntimeObjects(tc.clientObjs...).Build()

g := NewWithT(t)
reconciler := SrlinuxReconciler{
Scheme: scheme.Scheme,
Client: fakeClient,
}
Expect(k8sClient.Create(ctx, srlinux)).Should(Succeed())

By("Checking if the custom resource was successfully created")
Eventually(func() error {
found := &srlinuxv1.Srlinux{}

return k8sClient.Get(ctx, typeNamespaceName, found)
}, 10*time.Second, time.Second).Should(Succeed())

// Reconcile is triggered by the creation of the custom resource

By("Checking if Srlinux Pod was successfully created in the reconciliation")
Eventually(func() error {
found := &corev1.Pod{}

return k8sClient.Get(ctx, typeNamespaceName, found)
}, 10*time.Second, time.Second).Should(Succeed())
tc.testFn(t, fakeClient, reconciler, g)
})
}
}

By("Ensuring the Srlinux CR Status has been updated")
Eventually(func() error {
Expect(k8sClient.Get(ctx, typeNamespaceName, srlinux)).Should(Succeed())
func testReconcileForBasicSrlCR(_ *testing.T, c client.Client, reconciler SrlinuxReconciler, g *GomegaWithT) {
g.Eventually(func() bool {
res, err := reconciler.Reconcile(context.TODO(), reconcile.Request{
NamespacedName: namespacedName,
})

if srlinux.Status.Image != "srlinux:latest" {
return fmt.Errorf("got Srlinux.Status.Image: %s, want: %s", srlinux.Status.Image, "srlinux:latest")
}
return res.IsZero() && err == nil
}, 10*time.Second, time.Second).Should(BeTrue())

return nil
}, 10*time.Second, time.Second).Should(Succeed())
// check if the Pod for Srlinux CR has been created
pod := &corev1.Pod{}
g.Expect(c.Get(ctx, namespacedName, pod)).To(Succeed())

By("Deleting the custom resource for the Kind Srlinux")
Expect(k8sClient.Delete(ctx, srlinux)).Should(Succeed())
// check if CR status is updated
srlinux := &srlinuxv1.Srlinux{}
g.Expect(c.Get(ctx, namespacedName, srlinux)).To(Succeed())
g.Expect(srlinux.Status.Image).To(Equal(defaultSrlinuxImage))
}

By("Checking if the custom resource was successfully deleted")
Eventually(func() error {
found := &srlinuxv1.Srlinux{}
func testReconcileForDeletedCR(_ *testing.T, c client.Client, reconciler SrlinuxReconciler, g *GomegaWithT) {
g.Eventually(func() bool {
res, err := reconciler.Reconcile(context.TODO(), reconcile.Request{
NamespacedName: namespacedName,
})

return k8sClient.Get(ctx, typeNamespaceName, found)
}, 10*time.Second, time.Second).ShouldNot(Succeed())
return res.IsZero() && err == nil
}, 10*time.Second, time.Second).Should(BeTrue())

// because there are no controllers monitoring built-in resources in the envtest cluster,
// objects do not get deleted, even if an OwnerReference is set up
// check if CR hasn't been created by reconciliation loop
srlinux := &srlinuxv1.Srlinux{}
g.Expect(c.Get(ctx, namespacedName, srlinux)).ToNot(Succeed())

// Reconcile is triggered by the deletion of the custom resource
})
})
})
// check if CR hasn't been created
pod := &corev1.Pod{}
g.Expect(c.Get(ctx, namespacedName, pod)).ToNot(Succeed())
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ require (
github.com/creack/pty v1.1.18 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emicklei/go-restful/v3 v3.10.1 // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-logr/zapr v1.2.3 // indirect
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww=
github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0=
Expand Down
Loading