diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 0a4a2e996..de086ec88 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -1,20 +1,18 @@ // +build !ignore_autogenerated -/* -Copyright (c) 2019 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the LICENSE file - -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. -*/ +// Copyright (c) 2019 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the LICENSE file. +// +// 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. // autogenerated by controller-gen object, do not modify manually diff --git a/config/crd/bases/druid.sapcloud.io_etcds.yaml b/config/crd/bases/druid.sapcloud.io_etcds.yaml index 6d4eb3cad..3c84a09d3 100644 --- a/config/crd/bases/druid.sapcloud.io_etcds.yaml +++ b/config/crd/bases/druid.sapcloud.io_etcds.yaml @@ -3,6 +3,7 @@ apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: + creationTimestamp: null name: etcds.druid.sapcloud.io spec: group: druid.sapcloud.io @@ -394,39 +395,26 @@ spec: properties: deltaSnapshotMemoryLimit: type: integer - minimum: 0 - #default: 10485760 deltaSnapshotPeriod: type: string - #default: 60s - pattern: '^[-+]?([0-9]*(\.[0-9]*)?[ns|us|µs|μs|ms|S|m|h]+)+$' etcdConnectionTimeout: type: string - #default: 30s etcdQuotaBytes: type: integer - #default: 8589934592 fullSnapshotSchedule: type: string - pattern: '^(\d+|\*)(\/\d+)?(\s+(\d+|\*)(\/\d+)?){4}$' garbageCollectionPeriod: type: string - #default: 60s - pattern: '^[-+]?([0-9]*(\.[0-9]*)?[ns|us|µs|μs|ms|S|m|h]+)+$' garbageCollectionPolicy: type: string - pattern: (LimitBased|Exponential) - #default: Exponential imageRepository: type: string imageVersion: type: string port: type: integer - #default: 8080 pullPolicy: type: string - pattern: (Never|Always|IfNotPresent) resources: properties: limits: @@ -447,51 +435,31 @@ spec: snapstoreTempDir: type: string required: - - port - imageRepository - imageVersion - - fullSnapshotSchedule - - resources - - pullPolicy - - garbageCollectionPolicy - - garbageCollectionPeriod - - etcdQuotaBytes - - etcdConnectionTimeout - - snapstoreTempDir - - deltaSnapshotPeriod - - deltaSnapshotMemoryLimit type: object etcd: properties: clientPort: type: integer - #default: 2379 defragmentationSchedule: type: string - pattern: '^(\d+|\*)(\/\d+)?(\s+(\d+|\*)(\/\d+)?){4}$' enableTLS: type: boolean - #default: false imageRepository: type: string imageVersion: type: string initialClusterState: type: string - #default: new initialClusterToken: type: string - #default: new metrics: type: string - pattern: (extensive|basic) - #default: basic password: type: string pullPolicy: type: string - #default: IfNotPresent - pattern: (Never|Always|IfNotPresent) resources: properties: limits: @@ -511,19 +479,12 @@ spec: type: object serverPort: type: integer - #default: 2380 username: type: string required: - - defragmentationSchedule - - serverPort - - clientPort - imageRepository - imageVersion - - metrics - - resources - enableTLS - - pullPolicy - initialClusterToken - initialClusterState type: object @@ -533,14 +494,10 @@ spec: type: object pvcRetentionPolicy: type: string - pattern: (DeleteAll|RetainMaster|RetRetainAllainMaster|Exponential|LimitBased) - #default: DeleteAll replicas: type: integer - #default: 1 storageCapacity: type: string - #default: "16Gi" storageClass: type: string store: @@ -555,7 +512,6 @@ spec: type: string required: - storageContainer - - storePrefix - storageProvider - storeSecret type: object @@ -567,12 +523,8 @@ spec: - etcd - backup - store - - pvcRetentionPolicy - replicas - storageClass - - tlsServerSecret - - tlsClientSecret - - storageCapacity type: object status: properties: diff --git a/controllers/etcd_controller.go b/controllers/etcd_controller.go index cb4f32d26..7ec6376f7 100644 --- a/controllers/etcd_controller.go +++ b/controllers/etcd_controller.go @@ -25,6 +25,8 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" druidv1 "github.com/gardener/etcd-druid/api/v1" "github.com/gardener/etcd-druid/pkg/chartrenderer" @@ -33,11 +35,11 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" ) var ( - etcdChartPath = filepath.Join("charts", "etcd") + etcdChartPath = filepath.Join("..", "charts", "etcd") + logger = logrus.New() ) // FinalizerName is the name of the Plant finalizer. @@ -46,12 +48,18 @@ const FinalizerName = "druid.sapcloud.io/etcd-druid" // EtcdReconciler reconciles a Etcd object type EtcdReconciler struct { client.Client - Logger *logrus.Logger chartApplier kubernetes.ChartApplier Config *rest.Config ChartApplier kubernetes.ChartApplier } +func NewEtcdReconciler(mgr manager.Manager) (*EtcdReconciler, error) { + return (&EtcdReconciler{ + Client: mgr.GetClient(), + Config: mgr.GetConfig(), + }).InitializeControllerWithChartApplier() +} + // InitializeChartApplier will use EtcdReconciler client to intialize a Kubernetes client as well as // a Chart renderer. func (r *EtcdReconciler) InitializeControllerWithChartApplier() (*EtcdReconciler, error) { @@ -91,9 +99,9 @@ func (r *EtcdReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { if !reflect.DeepEqual(etcd.Spec, etcdCopy.Spec) { etcdCopy.Spec = etcd.Spec } - r.Logger.Infof("Reconciling etcd: %s", etcd.GetName()) + logger.Infof("Reconciling etcd: %s", etcd.GetName()) if !etcdCopy.DeletionTimestamp.IsZero() { - r.Logger.Infof("Deletion timestamp set for etcd: %s", etcd.GetName()) + logger.Infof("Deletion timestamp set for etcd: %s", etcd.GetName()) if err := r.removeFinalizersToDependantSecrets(etcdCopy); err != nil { if err := r.updateEtcdErrorStatus(etcdCopy, err); err != nil { return ctrl.Result{ @@ -108,7 +116,7 @@ func (r *EtcdReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { } if sets.NewString(etcd.Finalizers...).Has(FinalizerName) { - r.Logger.Infof("Removing finalizer (%s) from etcd %s", FinalizerName, etcd.GetName()) + logger.Infof("Removing finalizer (%s) from etcd %s", FinalizerName, etcd.GetName()) finalizers := sets.NewString(etcdCopy.Finalizers...) finalizers.Delete(FinalizerName) etcdCopy.Finalizers = finalizers.UnsortedList() @@ -125,30 +133,16 @@ func (r *EtcdReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { }, err } } - r.Logger.Infof("Deleted etcd %s successfully.", etcd.GetName()) + logger.Infof("Deleted etcd %s successfully.", etcd.GetName()) return ctrl.Result{}, nil } // Add Finalizers to Etcd - if etcd.DeletionTimestamp.IsZero() { - if finalizers := sets.NewString(etcd.Finalizers...); !finalizers.Has(FinalizerName) { - r.Logger.Infof("Adding finalizer (%s) to etcd %s", FinalizerName, etcd.GetName()) - finalizers.Insert(FinalizerName) - etcdCopy.Finalizers = finalizers.UnsortedList() - if err := r.Update(context.TODO(), etcdCopy); err != nil { - if err := r.updateEtcdErrorStatus(etcdCopy, err); err != nil { - return ctrl.Result{ - Requeue: true, - RequeueAfter: time.Second * 5, - }, nil - } - return ctrl.Result{ - Requeue: true, - RequeueAfter: time.Second * 5, - }, err - } - } - if err := r.addFinalizersToDependantSecrets(etcdCopy); err != nil { + if finalizers := sets.NewString(etcd.Finalizers...); !finalizers.Has(FinalizerName) { + logger.Infof("Adding finalizer (%s) to etcd %s", FinalizerName, etcd.GetName()) + finalizers.Insert(FinalizerName) + etcdCopy.Finalizers = finalizers.UnsortedList() + if err := r.Update(context.TODO(), etcdCopy); err != nil { if err := r.updateEtcdErrorStatus(etcdCopy, err); err != nil { return ctrl.Result{ Requeue: true, @@ -161,6 +155,19 @@ func (r *EtcdReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { }, err } } + if err := r.addFinalizersToDependantSecrets(etcdCopy); err != nil { + if err := r.updateEtcdErrorStatus(etcdCopy, err); err != nil { + return ctrl.Result{ + Requeue: true, + RequeueAfter: time.Second * 5, + }, nil + } + return ctrl.Result{ + Requeue: true, + RequeueAfter: time.Second * 5, + }, err + } + etcdValues := map[string]interface{}{ "defragmentationSchedule": etcd.Spec.Etcd.DefragmentationSchedule, "serverPort": etcd.Spec.Etcd.ServerPort, @@ -248,7 +255,7 @@ func (r *EtcdReconciler) addFinalizersToDependantSecrets(etcd *druidv1.Etcd) err Namespace: etcd.Namespace, }, &storeSecret) if finalizers := sets.NewString(storeSecret.Finalizers...); !finalizers.Has(FinalizerName) { - r.Logger.Infof("Adding finalizer (%s) for secret %s", FinalizerName, storeSecret.GetName()) + logger.Infof("Adding finalizer (%s) for secret %s", FinalizerName, storeSecret.GetName()) storeSecretCopy := storeSecret.DeepCopy() finalizers.Insert(FinalizerName) storeSecretCopy.Finalizers = finalizers.UnsortedList() @@ -263,7 +270,7 @@ func (r *EtcdReconciler) addFinalizersToDependantSecrets(etcd *druidv1.Etcd) err Namespace: etcd.Namespace, }, &clientSecret) if finalizers := sets.NewString(clientSecret.Finalizers...); !finalizers.Has(FinalizerName) { - r.Logger.Infof("Adding finalizer (%s) for secret %s", FinalizerName, clientSecret.GetName()) + logger.Infof("Adding finalizer (%s) for secret %s", FinalizerName, clientSecret.GetName()) clientSecretCopy := clientSecret.DeepCopy() finalizers.Insert(FinalizerName) clientSecretCopy.Finalizers = finalizers.UnsortedList() @@ -278,7 +285,7 @@ func (r *EtcdReconciler) addFinalizersToDependantSecrets(etcd *druidv1.Etcd) err Namespace: etcd.Namespace, }, &serverSecret) if finalizers := sets.NewString(serverSecret.Finalizers...); !finalizers.Has(FinalizerName) { - r.Logger.Infof("Adding finalizer (%s) for secret %s", FinalizerName, serverSecret.GetName()) + logger.Infof("Adding finalizer (%s) for secret %s", FinalizerName, serverSecret.GetName()) serverSecretCopy := serverSecret.DeepCopy() finalizers.Insert(FinalizerName) serverSecretCopy.Finalizers = finalizers.UnsortedList() @@ -299,7 +306,7 @@ func (r *EtcdReconciler) removeFinalizersToDependantSecrets(etcd *druidv1.Etcd) return err } if finalizers := sets.NewString(storeSecret.Finalizers...); finalizers.Has(FinalizerName) { - r.Logger.Infof("Removing finalizer (%s) from secret %s", FinalizerName, storeSecret.GetName()) + logger.Infof("Removing finalizer (%s) from secret %s", FinalizerName, storeSecret.GetName()) storeSecretCopy := storeSecret.DeepCopy() finalizers.Delete(FinalizerName) storeSecretCopy.Finalizers = finalizers.UnsortedList() @@ -316,7 +323,7 @@ func (r *EtcdReconciler) removeFinalizersToDependantSecrets(etcd *druidv1.Etcd) return err } if finalizers := sets.NewString(clientSecret.Finalizers...); finalizers.Has(FinalizerName) { - r.Logger.Infof("Removing finalizer (%s) from secret %s", FinalizerName, clientSecret.GetName()) + logger.Infof("Removing finalizer (%s) from secret %s", FinalizerName, clientSecret.GetName()) clientSecretCopy := clientSecret.DeepCopy() finalizers.Delete(FinalizerName) clientSecretCopy.Finalizers = finalizers.UnsortedList() @@ -331,7 +338,7 @@ func (r *EtcdReconciler) removeFinalizersToDependantSecrets(etcd *druidv1.Etcd) Namespace: etcd.Namespace, }, &serverSecret) if finalizers := sets.NewString(serverSecret.Finalizers...); finalizers.Has(FinalizerName) { - r.Logger.Infof("Removing finalizer (%s) from secret %s", FinalizerName, serverSecret.GetName()) + logger.Infof("Removing finalizer (%s) from secret %s", FinalizerName, serverSecret.GetName()) serverSecretCopy := serverSecret.DeepCopy() finalizers.Delete(FinalizerName) serverSecretCopy.Finalizers = finalizers.UnsortedList() @@ -362,7 +369,7 @@ func (r *EtcdReconciler) updateStatusFromServices(etcd *druidv1.Etcd) error { return err } etcd.Status.Endpoints = append(etcd.Status.Endpoints, endpoints) - r.Logger.Infof("etcd endpoints: %v", etcd.Status.Endpoints) + logger.Infof("etcd endpoints: %v", etcd.Status.Endpoints) return nil } diff --git a/controllers/etcd_controller_test.go b/controllers/etcd_controller_test.go new file mode 100644 index 000000000..58c9e24dd --- /dev/null +++ b/controllers/etcd_controller_test.go @@ -0,0 +1,220 @@ +// Copyright (c) 2018 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the LICENSE file +// +// 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 controllers_test + +import ( + "context" + "fmt" + "time" + + "k8s.io/apimachinery/pkg/api/resource" + + druidv1 "github.com/gardener/etcd-druid/api/v1" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +const timeout = time.Second * 30 + +var _ = Describe("Druid", func() { + Context("when adding etcd resources", func() { + + var err error + var instance *druidv1.Etcd + var c client.Client + BeforeEach(func() { + instance = getEtcd("foo1", "default") + c = mgr.GetClient() + storeSecret := instance.Spec.Store.StoreSecret + tlsClientSecret := instance.Spec.TLSClientSecretName + tlsServerName := instance.Spec.TLSClientSecretName + createSecrets(c, instance.Namespace, storeSecret, tlsClientSecret, tlsServerName) + }) + It("should call reconcile", func() { + expectedRequest := reconcile.Request{NamespacedName: types.NamespacedName{Name: instance.Name, Namespace: instance.Namespace}} + err = c.Create(context.TODO(), instance) + // The instance object may not be a valid object because it might be missing some required fields. + // Please modify the instance object by adding required fields and then remove the following if statement. + if apierrors.IsInvalid(err) { + testLog.Info("failed to create object", "got an invalid object error", err) + return + } + Expect(err).NotTo(HaveOccurred()) + defer c.Delete(context.TODO(), instance) + Eventually(requests, timeout).Should(Receive(Equal(expectedRequest))) + }) + }) + Context("when adding etcd resources", func() { + var err error + var instance *druidv1.Etcd + var c client.Client + + BeforeEach(func() { + instance = getEtcd("foo2", "default") + + // Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a + // channel when it is finished. + + Expect(err).NotTo(HaveOccurred()) + c = mgr.GetClient() + storeSecret := instance.Spec.Store.StoreSecret + tlsClientSecret := instance.Spec.TLSClientSecretName + tlsServerName := instance.Spec.TLSClientSecretName + createSecrets(c, instance.Namespace, storeSecret, tlsClientSecret, tlsServerName) + }) + It("should create statefulset", func() { + + err = c.Create(context.TODO(), instance) + // The instance object may not be a valid object because it might be missing some required fields. + // Please modify the instance object by adding required fields and then remove the following if statement. + if apierrors.IsInvalid(err) { + testLog.Error(err, "got an invalid object error") + return + } + ss := appsv1.StatefulSet{} + getStatefulset(c, instance, &ss) + testLog.Info("fetched Statefulset", "statefulset", ss.Name) + defer c.Delete(context.TODO(), instance) + Eventually(ss.Name, timeout).ShouldNot(BeEmpty()) + }) + }) +}) + +func getStatefulset(c client.Client, instance *druidv1.Etcd, ss *appsv1.StatefulSet) { + ctx, cancel := context.WithTimeout(context.TODO(), timeout) + defer cancel() + wait.PollImmediateUntil(1*time.Second, func() (bool, error) { + req := types.NamespacedName{ + Name: fmt.Sprintf("etcd-%s", instance.Name), + Namespace: instance.Namespace, + } + if err := c.Get(context.TODO(), req, ss); err != nil { + if errors.IsNotFound(err) { + // Object not found, return. Created objects are automatically garbage collected. + // For additional cleanup logic use finalizers. + return false, nil + } + return false, err + } + return true, nil + }, ctx.Done()) +} +func getEtcd(name, namespace string) *druidv1.Etcd { + instance := &druidv1.Etcd{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo2", + Namespace: "default", + }, + Spec: druidv1.Spec{ + Annotations: map[string]string{ + "app": "etcd-statefulset", + "role": "test", + }, + Labels: map[string]string{ + "app": "etcd-statefulset", + "role": "test", + }, + PVCRetentionPolicy: druidv1.PolicyDeleteAll, + Replicas: 1, + StorageClass: "gardener.cloud-fast", + TLSClientSecretName: "etcd-client-tls", + TLSServerSecretName: "etcd-server-tls", + StorageCapacity: "80Gi", + Store: druidv1.StoreSpec{ + StoreSecret: "etcd-backup", + StorageContainer: "shoot--dev--i308301-1--b3caa", + StorageProvider: "S3", + StorePrefix: "etcd-test", + }, + Backup: druidv1.BackupSpec{ + PullPolicy: corev1.PullIfNotPresent, + ImageRepository: "eu.gcr.io/gardener-project/gardener/etcdbrctl", + ImageVersion: "0.8.0-dev", + Port: 8080, + FullSnapshotSchedule: "0 */24 * * *", + GarbageCollectionPolicy: druidv1.GarbageCollectionPolicyExponential, + EtcdQuotaBytes: 8589934592, + EtcdConnectionTimeout: "300s", + SnapstoreTempDir: "/var/etcd/data/temp", + GarbageCollectionPeriod: "43200s", + DeltaSnapshotPeriod: "300s", + DeltaSnapshotMemoryLimit: 104857600, + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + "cpu": parseQuantity("500m"), + "memory": parseQuantity("2Gi"), + }, + Requests: corev1.ResourceList{ + "cpu": parseQuantity("23m"), + "memory": parseQuantity("128Mi"), + }, + }, + }, + Etcd: druidv1.EtcdSpec{ + EnableTLS: false, + MetricLevel: druidv1.Basic, + ImageRepository: "quay.io/coreos/etcd", + ImageVersion: "v3.3.13", + DefragmentationSchedule: "0 */24 * * *", + Resources: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + "cpu": parseQuantity("2500m"), + "memory": parseQuantity("4Gi"), + }, + Requests: corev1.ResourceList{ + "cpu": parseQuantity("500m"), + "memory": parseQuantity("1000Mi"), + }, + }, + ClientPort: 2379, + ServerPort: 2380, + PullPolicy: corev1.PullIfNotPresent, + InitialClusterState: "new", + InitialClusterToken: "new", + }, + }, + } + return instance +} + +func parseQuantity(q string) resource.Quantity { + val, _ := resource.ParseQuantity(q) + return val +} + +func createSecrets(c client.Client, namespace string, secrets ...string) { + for _, secret := range secrets { + secret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secret, + Namespace: namespace, + }, + Data: map[string][]byte{ + "test": []byte("test"), + }, + } + c.Create(context.TODO(), &secret) + } +} diff --git a/controllers/suite_test.go b/controllers/suite_test.go index 1bb4a69ef..b0cb57469 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -12,22 +12,28 @@ // See the License for the specific language governing permissions and // limitations under the License. -package controllers +package controllers_test import ( "path/filepath" + "sync" "testing" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" druidv1 "github.com/gardener/etcd-druid/api/v1" + appsv1 "k8s.io/api/apps/v1" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" - logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + . "github.com/gardener/etcd-druid/controllers" + "sigs.k8s.io/controller-runtime/pkg/manager" // +kubebuilder:scaffold:imports ) @@ -37,6 +43,14 @@ import ( var cfg *rest.Config var k8sClient client.Client var testEnv *envtest.Environment +var mgr manager.Manager +var recFn reconcile.Reconciler +var requests chan reconcile.Request +var stopMgr chan struct{} +var mgrStopped *sync.WaitGroup +var ( + testLog = ctrl.Log.WithName("test") +) func TestAPIs(t *testing.T) { RegisterFailHandler(Fail) @@ -47,17 +61,18 @@ func TestAPIs(t *testing.T) { } var _ = BeforeSuite(func(done Done) { - logf.SetLogger(zap.LoggerTo(GinkgoWriter, true)) - + var err error + //logf.SetLogger(zap.LoggerTo(GinkgoWriter, true)) + ctrl.SetLogger(zap.Logger(true)) By("bootstrapping test environment") testEnv = &envtest.Environment{ CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, } - cfg, err := testEnv.Start() + testLog.Info("Starting tests") + cfg, err = testEnv.Start() Expect(err).ToNot(HaveOccurred()) Expect(cfg).ToNot(BeNil()) - err = druidv1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) @@ -67,11 +82,60 @@ var _ = BeforeSuite(func(done Done) { Expect(err).ToNot(HaveOccurred()) Expect(k8sClient).ToNot(BeNil()) + Expect(cfg).ToNot(BeNil()) + mgr, err = manager.New(cfg, manager.Options{}) + Expect(err).NotTo(HaveOccurred()) + + Expect(cfg).ToNot(BeNil()) + mgr, err = manager.New(cfg, manager.Options{}) + + Expect(err).NotTo(HaveOccurred()) + + er, err := NewEtcdReconciler(mgr) + Expect(err).NotTo(HaveOccurred()) + + recFn, requests = SetupTestReconcile(er) + err = SetupWithManager(mgr, recFn) + Expect(err).NotTo(HaveOccurred()) + + stopMgr, mgrStopped = StartTestManager(mgr) + close(done) }, 60) var _ = AfterSuite(func() { - By("tearing down the test environment") - err := testEnv.Stop() - Expect(err).ToNot(HaveOccurred()) + close(stopMgr) + mgrStopped.Wait() + }) + +// 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) (chan struct{}, *sync.WaitGroup) { + stop := make(chan struct{}) + wg := &sync.WaitGroup{} + go func() { + wg.Add(1) + Expect(mgr.Start(stop)).NotTo(HaveOccurred()) + wg.Done() + }() + return stop, wg +} + +func SetupWithManager(mgr ctrl.Manager, r reconcile.Reconciler) error { + return ctrl.NewControllerManagedBy(mgr). + For(&druidv1.Etcd{}). + Owns(&appsv1.StatefulSet{}). + Complete(r) +} diff --git a/go.mod b/go.mod index 2b3ab56bb..4bab6d4c0 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/go-logr/logr v0.1.0 github.com/onsi/ginkgo v1.8.0 github.com/onsi/gomega v1.5.0 + github.com/sirupsen/logrus v1.4.2 golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc k8s.io/api v0.0.0-20190826194732-9f642ccb7a30 k8s.io/apimachinery v0.0.0-20190827074644-f378a67c6af3 diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt index f3a63f96e..4ed0fd64e 100644 --- a/hack/boilerplate.go.txt +++ b/hack/boilerplate.go.txt @@ -1,4 +1,4 @@ -// Copyright (c) 2018 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the LICENSE file. +// Copyright (c) 2019 SAP SE or an SAP affiliate company. All rights reserved. This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the LICENSE file. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/main.go b/main.go index 28b0f50c6..d4c89d564 100644 --- a/main.go +++ b/main.go @@ -19,8 +19,6 @@ import ( "flag" "os" - "github.com/sirupsen/logrus" - druidv1 "github.com/gardener/etcd-druid/api/v1" "github.com/gardener/etcd-druid/controllers" "k8s.io/apimachinery/pkg/runtime" @@ -65,7 +63,6 @@ func main() { ec, err := (&controllers.EtcdReconciler{ Client: mgr.GetClient(), - Logger: logrus.New(), Config: mgr.GetConfig(), }).InitializeControllerWithChartApplier() if err != nil {