From e874214a378556998aefed32eea0a524463e4624 Mon Sep 17 00:00:00 2001 From: Enxebre Date: Thu, 20 Dec 2018 18:50:05 +0100 Subject: [PATCH] Add unit test for machine controller reconcile --- pkg/controller/machine/controller.go | 14 +- pkg/controller/machine/controller_test.go | 169 ++++++++++++++++++++++ 2 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 pkg/controller/machine/controller_test.go diff --git a/pkg/controller/machine/controller.go b/pkg/controller/machine/controller.go index 4952ce1a1af0..f2437886d7af 100644 --- a/pkg/controller/machine/controller.go +++ b/pkg/controller/machine/controller.go @@ -23,6 +23,7 @@ import ( corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/klog" clusterv1 "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1" @@ -216,7 +217,18 @@ func (c *ReconcileMachine) delete(ctx context.Context, machine *clusterv1.Machin func (c *ReconcileMachine) getCluster(ctx context.Context, machine *clusterv1.Machine) (*clusterv1.Cluster, error) { clusterList := clusterv1.ClusterList{} - err := c.Client.List(ctx, client.InNamespace(machine.Namespace), &clusterList) + listOptions := &client.ListOptions{ + Namespace: machine.Namespace, + // This is set so the fake client can be used for unit test. See: + // https://github.com/kubernetes-sigs/controller-runtime/issues/168 + Raw: &metav1.ListOptions{ + TypeMeta: metav1.TypeMeta{ + APIVersion: clusterv1.SchemeGroupVersion.String(), + Kind: "Cluster", + }, + }, + } + err := c.Client.List(ctx, listOptions, &clusterList) if err != nil { return nil, err } diff --git a/pkg/controller/machine/controller_test.go b/pkg/controller/machine/controller_test.go new file mode 100644 index 000000000000..5f4fa77a9507 --- /dev/null +++ b/pkg/controller/machine/controller_test.go @@ -0,0 +1,169 @@ +/* +Copyright 2018 The Kubernetes 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 machine + +import ( + "reflect" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +func TestReconcile(t *testing.T) { + m1 := v1alpha1.Machine{ + TypeMeta: metav1.TypeMeta{ + Kind: "Machine", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "create", + Namespace: "default", + }, + } + m2 := v1alpha1.Machine{ + TypeMeta: metav1.TypeMeta{ + Kind: "Machine", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "update", + Namespace: "default", + Finalizers: []string{v1alpha1.MachineFinalizer}, + }, + } + time := metav1.Now() + m3 := v1alpha1.Machine{ + TypeMeta: metav1.TypeMeta{ + Kind: "Machine", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "delete", + Namespace: "default", + Finalizers: []string{v1alpha1.MachineFinalizer}, + DeletionTimestamp: &time, + }, + } + c := v1alpha1.Cluster{ + TypeMeta: metav1.TypeMeta{ + Kind: "Cluster", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster", + Namespace: "default", + }, + } + cl := v1alpha1.ClusterList{ + TypeMeta: metav1.TypeMeta{ + Kind: "ClusterList", + }, + Items: []v1alpha1.Cluster{c}, + } + + type expected struct { + createCallCount int64 + existCallCount int64 + updateCallCount int64 + deleteCallCount int64 + result reconcile.Result + error bool + } + testCases := []struct { + request reconcile.Request + ExistsValue bool + expected expected + }{ + { + request: reconcile.Request{NamespacedName: types.NamespacedName{Name: m1.Name, Namespace: m1.Namespace}}, + expected: expected{ + createCallCount: 1, + existCallCount: 1, + updateCallCount: 0, + deleteCallCount: 0, + result: reconcile.Result{}, + error: false, + }, + }, + { + request: reconcile.Request{NamespacedName: types.NamespacedName{Name: m2.Name, Namespace: m2.Namespace}}, + ExistsValue: true, + expected: expected{ + createCallCount: 0, + existCallCount: 1, + updateCallCount: 1, + deleteCallCount: 0, + result: reconcile.Result{}, + error: false, + }, + }, + { + request: reconcile.Request{NamespacedName: types.NamespacedName{Name: m3.Name, Namespace: m3.Namespace}}, + ExistsValue: true, + expected: expected{ + createCallCount: 0, + existCallCount: 0, + updateCallCount: 0, + deleteCallCount: 1, + result: reconcile.Result{}, + error: false, + }, + }, + } + + for _, tc := range testCases { + a := newTestActuator() + a.ExistsValue = tc.ExistsValue + v1alpha1.AddToScheme(scheme.Scheme) + r := &ReconcileMachine{ + Client: fake.NewFakeClient(&cl, &m1, &m2, &m3), + scheme: scheme.Scheme, + actuator: a, + } + + result, err := r.Reconcile(tc.request) + gotError := (err != nil) + if tc.expected.error != gotError { + var errorExpectation string + if !tc.expected.error { + errorExpectation = "no" + } + t.Errorf("Case: %s. Expected %s error, got: %v", tc.request.Name, errorExpectation, err) + } + + if !reflect.DeepEqual(result, tc.expected.result) { + t.Errorf("Case %s. Got: %v, expected %v", tc.request.Name, result, tc.expected.result) + } + + if a.CreateCallCount != tc.expected.createCallCount { + t.Errorf("Case %s. Got: %d createCallCount, expected %d", tc.request.Name, a.CreateCallCount, tc.expected.createCallCount) + } + + if a.UpdateCallCount != tc.expected.updateCallCount { + t.Errorf("Case %s. Got: %d updateCallCount, expected %d", tc.request.Name, a.UpdateCallCount, tc.expected.updateCallCount) + } + + if a.ExistsCallCount != tc.expected.existCallCount { + t.Errorf("Case %s. Got: %d existCallCount, expected %d", tc.request.Name, a.ExistsCallCount, tc.expected.existCallCount) + } + + if a.DeleteCallCount != tc.expected.deleteCallCount { + t.Errorf("Case %s. Got: %d deleteCallCount, expected %d", tc.request.Name, a.DeleteCallCount, tc.expected.deleteCallCount) + } + } +}