Skip to content

Commit 0bb2e29

Browse files
authored
fix: use status condition for setting cluster configmap state (#94)
Before this fix only current statefulset readiness status defined cluster state in configmap.
1 parent e893b93 commit 0bb2e29

File tree

6 files changed

+152
-49
lines changed

6 files changed

+152
-49
lines changed

api/v1alpha1/etcdcluster_types.go

+10-8
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,19 @@ type EtcdCondType string
4444
type EtcdCondMessage string
4545

4646
const (
47-
EtcdCondTypeInitStarted EtcdCondType = "InitializationStarted"
48-
EtcdCondTypeInitComplete EtcdCondType = "InitializationComplete"
49-
EtcdCondTypeStatefulSetReady EtcdCondType = "StatefulSetReady"
50-
EtcdCondTypeStatefulSetNotReady EtcdCondType = "StatefulSetNotReady"
47+
EtcdCondTypeInitStarted EtcdCondType = "InitializationStarted"
48+
EtcdCondTypeInitComplete EtcdCondType = "InitializationComplete"
49+
EtcdCondTypeWaitingForFirstQuorum EtcdCondType = "WaitingForFirstQuorum"
50+
EtcdCondTypeStatefulSetReady EtcdCondType = "StatefulSetReady"
51+
EtcdCondTypeStatefulSetNotReady EtcdCondType = "StatefulSetNotReady"
5152
)
5253

5354
const (
54-
EtcdInitCondNegMessage EtcdCondMessage = "Cluster initialization started"
55-
EtcdInitCondPosMessage EtcdCondMessage = "Cluster managed resources created"
56-
EtcdReadyCondNegMessage EtcdCondMessage = "Cluster StatefulSet is not Ready"
57-
EtcdReadyCondPosMessage EtcdCondMessage = "Cluster StatefulSet is Ready"
55+
EtcdInitCondNegMessage EtcdCondMessage = "Cluster initialization started"
56+
EtcdInitCondPosMessage EtcdCondMessage = "Cluster managed resources created"
57+
EtcdReadyCondNegMessage EtcdCondMessage = "Cluster StatefulSet is not Ready"
58+
EtcdReadyCondPosMessage EtcdCondMessage = "Cluster StatefulSet is Ready"
59+
EtcdReadyCondNegWaitingForQuorum EtcdCondMessage = "Waiting for first quorum to be established"
5860
)
5961

6062
// EtcdClusterStatus defines the observed state of EtcdCluster

internal/controller/etcdcluster_controller.go

+25-5
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,31 @@ func (r *EtcdClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request)
9595
}
9696

9797
// set cluster readiness condition
98-
factory.SetCondition(instance, factory.NewCondition(etcdaenixiov1alpha1.EtcdConditionReady).
99-
WithStatus(clusterReady).
100-
WithReason(string(etcdaenixiov1alpha1.EtcdCondTypeStatefulSetReady)).
101-
WithMessage(string(etcdaenixiov1alpha1.EtcdReadyCondPosMessage)).
102-
Complete())
98+
existingCondition := factory.GetCondition(instance, etcdaenixiov1alpha1.EtcdConditionReady)
99+
if existingCondition.Reason == string(etcdaenixiov1alpha1.EtcdCondTypeWaitingForFirstQuorum) {
100+
// we should change from "waiting for first quorum establishment" to "StatefulSet ready / not ready"
101+
// only after sts gets ready first time
102+
if clusterReady {
103+
factory.SetCondition(instance, factory.NewCondition(etcdaenixiov1alpha1.EtcdConditionReady).
104+
WithStatus(true).
105+
WithReason(string(etcdaenixiov1alpha1.EtcdCondTypeStatefulSetReady)).
106+
WithMessage(string(etcdaenixiov1alpha1.EtcdReadyCondPosMessage)).
107+
Complete())
108+
}
109+
} else {
110+
reason := etcdaenixiov1alpha1.EtcdCondTypeStatefulSetNotReady
111+
message := etcdaenixiov1alpha1.EtcdReadyCondNegMessage
112+
if clusterReady {
113+
reason = etcdaenixiov1alpha1.EtcdCondTypeStatefulSetReady
114+
message = etcdaenixiov1alpha1.EtcdReadyCondPosMessage
115+
}
116+
117+
factory.SetCondition(instance, factory.NewCondition(etcdaenixiov1alpha1.EtcdConditionReady).
118+
WithStatus(clusterReady).
119+
WithReason(string(reason)).
120+
WithMessage(string(message)).
121+
Complete())
122+
}
103123
return r.updateStatus(ctx, instance)
104124
}
105125

internal/controller/factory/condition.go

+14-2
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ func FillConditions(cluster *etcdaenixiov1alpha1.EtcdCluster) {
6969
Complete())
7070
SetCondition(cluster, NewCondition(etcdaenixiov1alpha1.EtcdConditionReady).
7171
WithStatus(false).
72-
WithReason(string(etcdaenixiov1alpha1.EtcdCondTypeStatefulSetNotReady)).
73-
WithMessage(string(etcdaenixiov1alpha1.EtcdReadyCondNegMessage)).
72+
WithReason(string(etcdaenixiov1alpha1.EtcdCondTypeWaitingForFirstQuorum)).
73+
WithMessage(string(etcdaenixiov1alpha1.EtcdReadyCondNegWaitingForQuorum)).
7474
Complete())
7575
}
7676

@@ -96,3 +96,15 @@ func SetCondition(
9696
}
9797
cluster.Status.Conditions[idx] = condition
9898
}
99+
100+
// GetCondition returns condition from cluster status conditions by type or nil if not present.
101+
func GetCondition(cluster *etcdaenixiov1alpha1.EtcdCluster, condType string) *metav1.Condition {
102+
idx := slices.IndexFunc(cluster.Status.Conditions, func(c metav1.Condition) bool {
103+
return c.Type == condType
104+
})
105+
if idx == -1 {
106+
return nil
107+
}
108+
109+
return &cluster.Status.Conditions[idx]
110+
}

internal/controller/factory/condition_test.go

+53
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package factory
1818

1919
import (
2020
"slices"
21+
"time"
2122

2223
etcdaenixiov1alpha1 "github.com/aenix-io/etcd-operator/api/v1alpha1"
2324
. "github.com/onsi/ginkgo/v2"
@@ -71,4 +72,56 @@ var _ = Describe("Condition builder", func() {
7172
Expect(etcdCluster.Status.Conditions[idx].LastTransitionTime).NotTo(Equal(timestamp))
7273
})
7374
})
75+
76+
Context("when retrieving conditions", func() {
77+
It("should return nil if condition of such type is not present", func() {
78+
etcdCluster := &etcdaenixiov1alpha1.EtcdCluster{
79+
Status: etcdaenixiov1alpha1.EtcdClusterStatus{
80+
Conditions: []metav1.Condition{
81+
{
82+
Type: etcdaenixiov1alpha1.EtcdConditionInitialized,
83+
Status: metav1.ConditionTrue,
84+
ObservedGeneration: 0,
85+
LastTransitionTime: metav1.NewTime(time.Now()),
86+
Reason: string(etcdaenixiov1alpha1.EtcdCondTypeInitComplete),
87+
Message: string(etcdaenixiov1alpha1.EtcdInitCondPosMessage),
88+
},
89+
},
90+
},
91+
}
92+
93+
Expect(GetCondition(etcdCluster, etcdaenixiov1alpha1.EtcdConditionReady)).To(BeNil())
94+
})
95+
96+
It("should return correct condition from the list", func() {
97+
expectedCond := metav1.Condition{
98+
Type: etcdaenixiov1alpha1.EtcdConditionReady,
99+
Status: metav1.ConditionTrue,
100+
ObservedGeneration: 0,
101+
LastTransitionTime: metav1.NewTime(time.Now()),
102+
Reason: string(etcdaenixiov1alpha1.EtcdCondTypeStatefulSetReady),
103+
Message: string(etcdaenixiov1alpha1.EtcdReadyCondPosMessage),
104+
}
105+
106+
etcdCluster := &etcdaenixiov1alpha1.EtcdCluster{
107+
Status: etcdaenixiov1alpha1.EtcdClusterStatus{
108+
Conditions: []metav1.Condition{
109+
expectedCond,
110+
{
111+
Type: etcdaenixiov1alpha1.EtcdConditionInitialized,
112+
Status: metav1.ConditionTrue,
113+
ObservedGeneration: 0,
114+
LastTransitionTime: metav1.NewTime(time.Now()),
115+
Reason: string(etcdaenixiov1alpha1.EtcdCondTypeInitComplete),
116+
Message: string(etcdaenixiov1alpha1.EtcdInitCondPosMessage),
117+
},
118+
},
119+
},
120+
}
121+
foundCond := GetCondition(etcdCluster, etcdaenixiov1alpha1.EtcdConditionReady)
122+
if Expect(foundCond).NotTo(BeNil()) {
123+
Expect(*foundCond).To(Equal(expectedCond))
124+
}
125+
})
126+
})
74127
})

internal/controller/factory/configMap.go

+5-9
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package factory
1919
import (
2020
"context"
2121
"fmt"
22-
"slices"
2322

2423
corev1 "k8s.io/api/core/v1"
2524
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -75,13 +74,10 @@ func CreateOrUpdateClusterStateConfigMap(
7574
return reconcileConfigMap(ctx, rclient, cluster.Name, configMap)
7675
}
7776

78-
// isEtcdClusterReady returns true if condition "Ready" has status equal to "True", otherwise false.
77+
// isEtcdClusterReady returns true if condition "Ready" has progressed
78+
// from reason v1alpha1.EtcdCondTypeWaitingForFirstQuorum.
7979
func isEtcdClusterReady(cluster *etcdaenixiov1alpha1.EtcdCluster) bool {
80-
idx := slices.IndexFunc(cluster.Status.Conditions, func(condition metav1.Condition) bool {
81-
return condition.Type == etcdaenixiov1alpha1.EtcdConditionReady
82-
})
83-
if idx == -1 {
84-
return false
85-
}
86-
return cluster.Status.Conditions[idx].Status == metav1.ConditionTrue
80+
cond := GetCondition(cluster, etcdaenixiov1alpha1.EtcdConditionReady)
81+
return cond != nil && (cond.Reason == string(etcdaenixiov1alpha1.EtcdCondTypeStatefulSetReady) ||
82+
cond.Reason == string(etcdaenixiov1alpha1.EtcdCondTypeStatefulSetNotReady))
8783
}

internal/controller/factory/configmap_test.go

+45-25
Original file line numberDiff line numberDiff line change
@@ -54,31 +54,51 @@ var _ = Describe("CreateOrUpdateClusterStateConfigMap handlers", func() {
5454

5555
It("should successfully ensure the configmap", func() {
5656
cm := &corev1.ConfigMap{}
57-
58-
By("creating the configmap for initial cluster")
59-
err := CreateOrUpdateClusterStateConfigMap(ctx, etcdcluster, k8sClient, k8sClient.Scheme())
60-
Expect(err).NotTo(HaveOccurred())
61-
62-
err = k8sClient.Get(ctx, typeNamespacedName, cm)
63-
cmUid := cm.UID
64-
Expect(err).NotTo(HaveOccurred())
65-
Expect(cm.Data["ETCD_INITIAL_CLUSTER_STATE"]).To(Equal("new"))
66-
67-
By("updating the configmap for initialized cluster")
68-
SetCondition(etcdcluster, NewCondition(etcdaenixiov1alpha1.EtcdConditionReady).
69-
WithStatus(true).Complete())
70-
err = CreateOrUpdateClusterStateConfigMap(ctx, etcdcluster, k8sClient, k8sClient.Scheme())
71-
Expect(err).NotTo(HaveOccurred())
72-
73-
err = k8sClient.Get(ctx, typeNamespacedName, cm)
74-
Expect(err).NotTo(HaveOccurred())
75-
Expect(cm.Data["ETCD_INITIAL_CLUSTER_STATE"]).To(Equal("existing"))
76-
// Check that we are updating the same configmap
77-
Expect(cm.UID).To(Equal(cmUid))
78-
79-
By("deleting the configmap")
80-
81-
Expect(k8sClient.Delete(ctx, cm)).To(Succeed())
57+
var err error
58+
var cmUid types.UID
59+
By("creating the configmap for initial cluster", func() {
60+
err = CreateOrUpdateClusterStateConfigMap(ctx, etcdcluster, k8sClient, k8sClient.Scheme())
61+
Expect(err).NotTo(HaveOccurred())
62+
63+
err = k8sClient.Get(ctx, typeNamespacedName, cm)
64+
cmUid = cm.UID
65+
Expect(err).NotTo(HaveOccurred())
66+
Expect(cm.Data["ETCD_INITIAL_CLUSTER_STATE"]).To(Equal("new"))
67+
})
68+
69+
By("updating the configmap for initialized cluster", func() {
70+
SetCondition(etcdcluster, NewCondition(etcdaenixiov1alpha1.EtcdConditionReady).
71+
WithReason(string(etcdaenixiov1alpha1.EtcdCondTypeStatefulSetReady)).
72+
WithStatus(true).
73+
Complete())
74+
err = CreateOrUpdateClusterStateConfigMap(ctx, etcdcluster, k8sClient, k8sClient.Scheme())
75+
Expect(err).NotTo(HaveOccurred())
76+
77+
err = k8sClient.Get(ctx, typeNamespacedName, cm)
78+
Expect(err).NotTo(HaveOccurred())
79+
Expect(cm.Data["ETCD_INITIAL_CLUSTER_STATE"]).To(Equal("existing"))
80+
// Check that we are updating the same configmap
81+
Expect(cm.UID).To(Equal(cmUid))
82+
})
83+
84+
By("updating the configmap back to new", func() {
85+
SetCondition(etcdcluster, NewCondition(etcdaenixiov1alpha1.EtcdConditionReady).
86+
WithReason(string(etcdaenixiov1alpha1.EtcdCondTypeWaitingForFirstQuorum)).
87+
WithStatus(true).
88+
Complete())
89+
err = CreateOrUpdateClusterStateConfigMap(ctx, etcdcluster, k8sClient, k8sClient.Scheme())
90+
Expect(err).NotTo(HaveOccurred())
91+
92+
err = k8sClient.Get(ctx, typeNamespacedName, cm)
93+
Expect(err).NotTo(HaveOccurred())
94+
Expect(cm.Data["ETCD_INITIAL_CLUSTER_STATE"]).To(Equal("new"))
95+
// Check that we are updating the same configmap
96+
Expect(cm.UID).To(Equal(cmUid))
97+
})
98+
99+
By("deleting the configmap", func() {
100+
Expect(k8sClient.Delete(ctx, cm)).To(Succeed())
101+
})
82102
})
83103

84104
It("should fail on creating the configMap with invalid owner reference", func() {

0 commit comments

Comments
 (0)