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

update tests #182

Merged
merged 2 commits into from
Apr 20, 2024
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
235 changes: 121 additions & 114 deletions internal/controller/etcdcluster_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,145 +17,152 @@ limitations under the License.
package controller

import (
"context"

"k8s.io/utils/ptr"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/client"
. "sigs.k8s.io/controller-runtime/pkg/envtest/komega"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

etcdaenixiov1alpha1 "github.com/aenix-io/etcd-operator/api/v1alpha1"
"github.com/aenix-io/etcd-operator/internal/controller/factory"
)

var _ = Describe("EtcdCluster Controller", func() {
Context("When reconciling a resource", func() {
const resourceName = "test-resource"

ctx := context.Background()
var (
reconciler *EtcdClusterReconciler
ns *corev1.Namespace
)

BeforeEach(func(ctx SpecContext) {
reconciler = &EtcdClusterReconciler{
Client: k8sClient,
Scheme: k8sClient.Scheme(),
}

typeNamespacedName := types.NamespacedName{
Name: resourceName,
Namespace: "default",
ns = &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "test-",
},
}
etcdcluster := &etcdaenixiov1alpha1.EtcdCluster{}

BeforeEach(func() {
By("creating the custom resource for the Kind EtcdCluster")
err := k8sClient.Get(ctx, typeNamespacedName, etcdcluster)
if err != nil && errors.IsNotFound(err) {
resource := &etcdaenixiov1alpha1.EtcdCluster{
ObjectMeta: metav1.ObjectMeta{
Name: resourceName,
Namespace: "default",
},
Spec: etcdaenixiov1alpha1.EtcdClusterSpec{
Replicas: ptr.To(int32(3)),
Storage: etcdaenixiov1alpha1.StorageSpec{
EmptyDir: &v1.EmptyDirVolumeSource{},
},
Expect(k8sClient.Create(ctx, ns)).Should(Succeed())
DeferCleanup(k8sClient.Delete, ns)
})

Context("When reconciling the EtcdCluster", func() {
var (
etcdcluster etcdaenixiov1alpha1.EtcdCluster
configMap corev1.ConfigMap
headlessService corev1.Service
service corev1.Service
statefulSet appsv1.StatefulSet

err error
)

BeforeEach(func(ctx SpecContext) {
etcdcluster = etcdaenixiov1alpha1.EtcdCluster{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "test-etcdcluster-",
Namespace: ns.GetName(),
},
Spec: etcdaenixiov1alpha1.EtcdClusterSpec{
Replicas: ptr.To(int32(3)),
Storage: etcdaenixiov1alpha1.StorageSpec{
EmptyDir: &corev1.EmptyDirVolumeSource{},
},
}
Expect(k8sClient.Create(ctx, resource)).To(Succeed())
},
}
Expect(k8sClient.Create(ctx, &etcdcluster)).Should(Succeed())
Eventually(Get(&etcdcluster)).Should(Succeed())
DeferCleanup(k8sClient.Delete, &etcdcluster)

configMap = corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Namespace: ns.GetName(),
Name: factory.GetClusterStateConfigMapName(&etcdcluster),
},
}
DeferCleanup(k8sClient.Delete, &configMap)
headlessService = corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Namespace: ns.GetName(),
Name: etcdcluster.GetName(),
},
}
DeferCleanup(k8sClient.Delete, &headlessService)
service = corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Namespace: ns.GetName(),
Name: factory.GetClientServiceName(&etcdcluster),
},
}
DeferCleanup(k8sClient.Delete, &service)
statefulSet = appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Namespace: ns.GetName(),
Name: etcdcluster.GetName(),
},
}
DeferCleanup(k8sClient.Delete, &statefulSet)
})

AfterEach(func() {
// TODO(user): Cleanup logic after each test, like removing the resource instance.
resource := &etcdaenixiov1alpha1.EtcdCluster{}
err := k8sClient.Get(ctx, typeNamespacedName, resource)
Expect(err).NotTo(HaveOccurred())
It("should reconcile a new EtcdCluster", func(ctx SpecContext) {
By("reconciling the EtcdCluster", func() {
_, err = reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&etcdcluster)})
Expect(err).ToNot(HaveOccurred())
Eventually(Get(&etcdcluster)).Should(Succeed())
Expect(etcdcluster.Status.Conditions).To(HaveLen(2))
Expect(etcdcluster.Status.Conditions[0].Type).To(Equal(etcdaenixiov1alpha1.EtcdConditionInitialized))
Expect(etcdcluster.Status.Conditions[0].Status).To(Equal(metav1.ConditionStatus("True")))
Expect(etcdcluster.Status.Conditions[1].Type).To(Equal(etcdaenixiov1alpha1.EtcdConditionReady))
Expect(etcdcluster.Status.Conditions[1].Status).To(Equal(metav1.ConditionStatus("False")))
})

By("Cleanup the specific resource instance EtcdCluster")
Expect(k8sClient.Delete(ctx, resource)).To(Succeed())
})
By("reconciling owned ConfigMap", func() {
Eventually(Get(&configMap)).Should(Succeed())
Expect(configMap.Data).Should(HaveKeyWithValue("ETCD_INITIAL_CLUSTER_STATE", "new"))
})

It("should successfully reconcile the resource", func() {
By("Reconciling the created resource")
controllerReconciler := &EtcdClusterReconciler{
Client: k8sClient,
Scheme: k8sClient.Scheme(),
}
By("reconciling owned headless Service", func() {
Eventually(Get(&headlessService)).Should(Succeed())
Expect(headlessService.Spec.ClusterIP).Should(Equal("None"))
})

_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
By("reconciling owned Service", func() {
Eventually(Get(&service)).Should(Succeed())
Expect(service.Spec.ClusterIP).ShouldNot(Equal("None"))
})

By("reconciling owned StatefulSet", func() {
Eventually(Get(&statefulSet)).Should(Succeed())
})
Expect(err).NotTo(HaveOccurred())

err = k8sClient.Get(ctx, typeNamespacedName, etcdcluster)
Expect(err).NotTo(HaveOccurred())
Expect(etcdcluster.Status.Conditions).To(HaveLen(2))
Expect(etcdcluster.Status.Conditions[0].Type).To(Equal(etcdaenixiov1alpha1.EtcdConditionInitialized))
Expect(etcdcluster.Status.Conditions[0].Status).To(Equal(metav1.ConditionStatus("True")))
Expect(etcdcluster.Status.Conditions[1].Type).To(Equal(etcdaenixiov1alpha1.EtcdConditionReady))
Expect(etcdcluster.Status.Conditions[1].Status).To(Equal(metav1.ConditionStatus("False")))

// check that ConfigMap is created
cm := &v1.ConfigMap{}
cmName := types.NamespacedName{
Namespace: typeNamespacedName.Namespace,
Name: factory.GetClusterStateConfigMapName(etcdcluster),
}
err = k8sClient.Get(ctx, cmName, cm)
Expect(err).NotTo(HaveOccurred(), "cluster configmap state should exist")
Expect(cm.Data).To(HaveKeyWithValue("ETCD_INITIAL_CLUSTER_STATE", "new"))
// check that Service is created
svc := &v1.Service{}
err = k8sClient.Get(ctx, typeNamespacedName, svc)
Expect(err).NotTo(HaveOccurred(), "cluster headless Service should exist")
Expect(svc.Spec.ClusterIP).To(Equal("None"), "cluster Service should be headless")
// check that StatefulSet is created
sts := &appsv1.StatefulSet{}
err = k8sClient.Get(ctx, typeNamespacedName, sts)
Expect(err).NotTo(HaveOccurred(), "cluster statefulset should exist")
// check that Service is created
svc = &v1.Service{}
clientSvcName := types.NamespacedName{
Namespace: typeNamespacedName.Namespace,
Name: factory.GetClientServiceName(etcdcluster),
}
err = k8sClient.Get(ctx, clientSvcName, svc)
Expect(err).NotTo(HaveOccurred(), "cluster client Service should exist")
Expect(svc.Spec.ClusterIP).NotTo(Equal("None"), "cluster client Service should NOT be headless")
})

It("should successfully reconcile the resource twice and mark as ready", func() {
By("Reconciling the created resource twice (second time after marking sts as ready)")
controllerReconciler := &EtcdClusterReconciler{
Client: k8sClient,
Scheme: k8sClient.Scheme(),
}
It("should successfully reconcile the resource twice and mark as ready", func(ctx SpecContext) {
By("reconciling the EtcdCluster", func() {
_, err = reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&etcdcluster)})
Expect(err).ToNot(HaveOccurred())
})

_, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
By("setting owned StatefulSet to ready state", func() {
Eventually(Get(&statefulSet)).Should(Succeed())
Eventually(UpdateStatus(&statefulSet, func() {
statefulSet.Status.ReadyReplicas = *etcdcluster.Spec.Replicas
statefulSet.Status.Replicas = *etcdcluster.Spec.Replicas
})).Should(Succeed())
})
Expect(err).NotTo(HaveOccurred())

// check that StatefulSet is created
sts := &appsv1.StatefulSet{}
err = k8sClient.Get(ctx, typeNamespacedName, sts)
Expect(err).NotTo(HaveOccurred(), "cluster statefulset should exist")
// mark sts as ready
sts.Status.ReadyReplicas = *etcdcluster.Spec.Replicas
sts.Status.Replicas = *etcdcluster.Spec.Replicas
Expect(k8sClient.Status().Update(ctx, sts)).To(Succeed())
// reconcile and check EtcdCluster status
_, err = controllerReconciler.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,

By("reconciling the EtcdCluster after owned StatefulSet is ready", func() {
_, err = reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&etcdcluster)})
Expect(err).ToNot(HaveOccurred())
Eventually(Get(&etcdcluster)).Should(Succeed())
Expect(etcdcluster.Status.Conditions[1].Type).To(Equal(etcdaenixiov1alpha1.EtcdConditionReady))
Expect(string(etcdcluster.Status.Conditions[1].Status)).To(Equal("True"))
})
Expect(err).NotTo(HaveOccurred())
// check EtcdCluster status
err = k8sClient.Get(ctx, typeNamespacedName, etcdcluster)
Expect(err).NotTo(HaveOccurred())
Expect(etcdcluster.Status.Conditions[1].Type).To(Equal(etcdaenixiov1alpha1.EtcdConditionReady))
Expect(string(etcdcluster.Status.Conditions[1].Status)).To(Equal("True"))
})
})
})
30 changes: 16 additions & 14 deletions internal/controller/factory/condition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,21 +55,23 @@ var _ = Describe("Condition builder", func() {
})
timestamp := etcdCluster.Status.Conditions[idx].LastTransitionTime

By("setting condition without status change")
SetCondition(etcdCluster, NewCondition(etcdaenixiov1alpha1.EtcdConditionInitialized).
WithStatus(false).
WithReason(string(etcdaenixiov1alpha1.EtcdCondTypeInitStarted)).
WithMessage("test").
Complete())
Expect(etcdCluster.Status.Conditions[idx].LastTransitionTime).To(Equal(timestamp))
By("setting condition without status change", func() {
SetCondition(etcdCluster, NewCondition(etcdaenixiov1alpha1.EtcdConditionInitialized).
WithStatus(false).
WithReason(string(etcdaenixiov1alpha1.EtcdCondTypeInitStarted)).
WithMessage("test").
Complete())
Expect(etcdCluster.Status.Conditions[idx].LastTransitionTime).To(Equal(timestamp))
})

By("setting condition with status changed")
SetCondition(etcdCluster, NewCondition(etcdaenixiov1alpha1.EtcdConditionInitialized).
WithStatus(true).
WithReason(string(etcdaenixiov1alpha1.EtcdCondTypeInitStarted)).
WithMessage("test").
Complete())
Expect(etcdCluster.Status.Conditions[idx].LastTransitionTime).NotTo(Equal(timestamp))
By("setting condition with status changed", func() {
SetCondition(etcdCluster, NewCondition(etcdaenixiov1alpha1.EtcdConditionInitialized).
WithStatus(true).
WithReason(string(etcdaenixiov1alpha1.EtcdCondTypeInitStarted)).
WithMessage("test").
Complete())
Expect(etcdCluster.Status.Conditions[idx].LastTransitionTime).NotTo(Equal(timestamp))
})
})
})

Expand Down
Loading
Loading