diff --git a/go.mod b/go.mod index a24c8855a..7620d411d 100644 --- a/go.mod +++ b/go.mod @@ -41,6 +41,7 @@ require ( github.com/kylelemons/godebug v1.1.0 github.com/mholt/archiver v3.1.1+incompatible github.com/nwaples/rardecode v1.0.0 // indirect + github.com/onsi/gomega v1.5.0 github.com/openshift/api v3.9.1-0.20191008181517-e4fd21196097+incompatible // indirect github.com/openshift/cluster-network-operator v0.0.0-20191009144453-fdceef8e1a7b github.com/pierrec/lz4 v2.2.5+incompatible // indirect diff --git a/pkg/controller/istiocontrolplane/istiocontrolplane_controller_suit_test.go b/pkg/controller/istiocontrolplane/istiocontrolplane_controller_suit_test.go new file mode 100644 index 000000000..66104ba23 --- /dev/null +++ b/pkg/controller/istiocontrolplane/istiocontrolplane_controller_suit_test.go @@ -0,0 +1,81 @@ +// Copyright 2019 Istio 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 istiocontrolplane + +import ( + "os" + "path/filepath" + "sync" + "testing" + + "github.com/onsi/gomega" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/envtest" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "istio.io/operator/pkg/apis" + "istio.io/pkg/log" +) + +var cfg *rest.Config + +func TestMain(m *testing.M) { + t := &envtest.Environment{ + CRDDirectoryPaths: []string{ + filepath.Join("..", "..", "..", "deploy", "crds"), + }, + } + + apis.AddToScheme(scheme.Scheme) + + var err error + if cfg, err = t.Start(); err != nil { + log.Fatalf("error to start the test env: %s", err) + } + + code := m.Run() + + t.Stop() + os.Exit(code) +} + +// SetupTestReconcile returns a reconcile.Reconcile implementation that delegates to inner and +// writes the request to requests after Reconcile is finished. +func SetupTestReconcile(inner reconcile.Reconciler) (reconcile.Reconciler, chan reconcile.Request) { + requests := make(chan reconcile.Request) + fn := reconcile.Func(func(req reconcile.Request) (reconcile.Result, error) { + result, err := inner.Reconcile(req) + requests <- req + return result, err + }) + + return fn, requests +} + +// StartTestManager adds recFn +func StartTestManager(mgr manager.Manager, g *gomega.GomegaWithT) (chan struct{}, *sync.WaitGroup) { + stop := make(chan struct{}) + wg := &sync.WaitGroup{} + wg.Add(1) + + go func() { + defer wg.Done() + g.Expect(mgr.Start(stop)).NotTo(gomega.HaveOccurred()) + }() + + return stop, wg +} diff --git a/pkg/controller/istiocontrolplane/istiocontrolplane_controller_test.go b/pkg/controller/istiocontrolplane/istiocontrolplane_controller_test.go index 4288b517a..599e5a391 100644 --- a/pkg/controller/istiocontrolplane/istiocontrolplane_controller_test.go +++ b/pkg/controller/istiocontrolplane/istiocontrolplane_controller_test.go @@ -19,6 +19,9 @@ import ( "fmt" "strconv" "testing" + "time" + + "github.com/onsi/gomega" "istio.io/operator/pkg/apis/istio/v1alpha2" "istio.io/operator/pkg/helmreconciler" @@ -29,10 +32,26 @@ import ( "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/manager" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) +const ( + timeout = time.Second * 2 + defaultProfile = "default" + demoProfile = "demo" + minimalProfile = "minimal" + sdsProfile = "sds" +) + var ( + c client.Client + icpkey = types.NamespacedName{ + Name: "test-istiocontrolplane", + Namespace: "test-istio-operator", + } + expectedRequest = reconcile.Request{NamespacedName: icpkey} + minimalStatus = map[string]*v1alpha2.InstallStatus_VersionStatus{ "Pilot": { Status: v1alpha2.InstallStatus_HEALTHY, @@ -156,28 +175,28 @@ func TestICPController_SwitchProfile(t *testing.T) { cases := []testCase{ { description: "switch profile from minimal to default", - initialProfile: "minimal", - targetProfile: "default", + initialProfile: minimalProfile, + targetProfile: defaultProfile, }, { description: "switch profile from default to minimal", - initialProfile: "default", - targetProfile: "minimal", + initialProfile: defaultProfile, + targetProfile: minimalProfile, }, { description: "switch profile from default to demo", - initialProfile: "default", - targetProfile: "demo", + initialProfile: defaultProfile, + targetProfile: demoProfile, }, { description: "switch profile from demo to sds", - initialProfile: "demo", - targetProfile: "sds", + initialProfile: demoProfile, + targetProfile: sdsProfile, }, { description: "switch profile from sds to default", - initialProfile: "sds", - targetProfile: "default", + initialProfile: sdsProfile, + targetProfile: defaultProfile, }, } for i, c := range cases { @@ -186,16 +205,15 @@ func TestICPController_SwitchProfile(t *testing.T) { }) } } + func testSwitchProfile(t *testing.T, c testCase) { t.Helper() - name := "example-istiocontrolplane" - namespace := "istio-system" icp := &v1alpha2.IstioControlPlane{ Kind: "IstioControlPlane", ApiVersion: "install.istio.io/v1alpha2", ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, + Name: icpkey.Name, + Namespace: icpkey.Namespace, }, Spec: &v1alpha2.IstioControlPlaneSpec{ Profile: c.initialProfile, @@ -213,8 +231,8 @@ func testSwitchProfile(t *testing.T, c testCase) { req := reconcile.Request{ NamespacedName: types.NamespacedName{ - Name: name, - Namespace: namespace, + Name: icpkey.Name, + Namespace: icpkey.Namespace, }, } res, err := r.Reconcile(req) @@ -272,13 +290,13 @@ func checkICPStatus(cl client.Client, key client.ObjectKey, profile string) (boo } var status map[string]*v1alpha2.InstallStatus_VersionStatus switch profile { - case "minimal": + case minimalProfile: status = minimalStatus - case "default": + case defaultProfile: status = defaultStatus - case "sds": + case sdsProfile: status = sdsStatus - case "demo": + case demoProfile: status = demoStatus } installStatus := instance.GetStatus() @@ -298,3 +316,45 @@ func checkICPStatus(cl client.Client, key client.ObjectKey, profile string) (boo } return true, nil } + +// TestReconcile test the reconciler process with manager from end to end. +func TestReconcile(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a + // channel when it is finished. + mgr, err := manager.New(cfg, manager.Options{}) + g.Expect(err).NotTo(gomega.HaveOccurred()) + + c = mgr.GetClient() + + rec := newReconciler(mgr) + recFn, requests := SetupTestReconcile(rec) + + g.Expect(add(mgr, recFn)).NotTo(gomega.HaveOccurred()) + + stopMgr, mgrStopped := StartTestManager(mgr, g) + + defer func() { + close(stopMgr) + mgrStopped.Wait() + }() + + icp := &v1alpha2.IstioControlPlane{ + Kind: "IstioControlPlane", + ApiVersion: "install.istio.io/v1alpha2", + ObjectMeta: metav1.ObjectMeta{ + Name: icpkey.Name, + Namespace: icpkey.Namespace, + }, + Spec: &v1alpha2.IstioControlPlaneSpec{ + Profile: defaultProfile, + }, + } + + g.Expect(c.Create(context.TODO(), icp)).NotTo(gomega.HaveOccurred()) + + defer c.Delete(context.TODO(), icp) + + g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedRequest))) +}