Skip to content

Commit dde6e7b

Browse files
authored
feat: Refactor resource generation (#84)
Closes #55
1 parent 091824e commit dde6e7b

File tree

9 files changed

+419
-162
lines changed

9 files changed

+419
-162
lines changed

internal/controller/etcdcluster_controller.go

Lines changed: 4 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ import (
3030
"k8s.io/apimachinery/pkg/api/errors"
3131
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3232
"k8s.io/apimachinery/pkg/runtime"
33-
"k8s.io/apimachinery/pkg/util/intstr"
3433
ctrl "sigs.k8s.io/controller-runtime"
3534
"sigs.k8s.io/controller-runtime/pkg/client"
3635
"sigs.k8s.io/controller-runtime/pkg/log"
@@ -130,179 +129,26 @@ func (r *EtcdClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request)
130129

131130
// ensureClusterObjects creates or updates all objects owned by cluster CR
132131
func (r *EtcdClusterReconciler) ensureClusterObjects(
133-
ctx context.Context, cluster *etcdaenixiov1alpha1.EtcdCluster, isClusterInitialized bool) error {
132+
ctx context.Context, cluster *etcdaenixiov1alpha1.EtcdCluster, isClusterReady bool) error {
134133
// 1. create or update configmap <name>-cluster-state
135-
if err := r.ensureClusterStateConfigMap(ctx, cluster, isClusterInitialized); err != nil {
134+
if err := factory.CreateOrUpdateClusterStateConfigMap(ctx, cluster, isClusterReady, r.Client, r.Scheme); err != nil {
136135
return err
137136
}
138-
if err := r.ensureClusterService(ctx, cluster); err != nil {
137+
if err := factory.CreateOrUpdateClusterService(ctx, cluster, r.Client, r.Scheme); err != nil {
139138
return err
140139
}
141140
// 2. create or update statefulset
142141
if err := factory.CreateOrUpdateStatefulSet(ctx, cluster, r.Client, r.Scheme); err != nil {
143142
return err
144143
}
145144
// 3. create or update ClusterIP Service
146-
if err := r.ensureClusterClientService(ctx, cluster); err != nil {
145+
if err := factory.CreateOrUpdateClientService(ctx, cluster, r.Client, r.Scheme); err != nil {
147146
return err
148147
}
149148

150149
return nil
151150
}
152151

153-
func (r *EtcdClusterReconciler) ensureClusterService(ctx context.Context, cluster *etcdaenixiov1alpha1.EtcdCluster) error {
154-
svc := &corev1.Service{}
155-
err := r.Get(ctx, client.ObjectKey{
156-
Namespace: cluster.Namespace,
157-
Name: cluster.Name,
158-
}, svc)
159-
// Service exists, skip creation
160-
if err == nil {
161-
return nil
162-
}
163-
if !errors.IsNotFound(err) {
164-
return fmt.Errorf("cannot get cluster service: %w", err)
165-
}
166-
167-
svc = &corev1.Service{
168-
ObjectMeta: metav1.ObjectMeta{
169-
Name: cluster.Name,
170-
Namespace: cluster.Namespace,
171-
Labels: map[string]string{
172-
"app.kubernetes.io/name": "etcd",
173-
"app.kubernetes.io/instance": cluster.Name,
174-
"app.kubernetes.io/managed-by": "etcd-operator",
175-
},
176-
},
177-
Spec: corev1.ServiceSpec{
178-
Ports: []corev1.ServicePort{
179-
{Name: "peer", TargetPort: intstr.FromInt32(2380), Port: 2380, Protocol: corev1.ProtocolTCP},
180-
{Name: "client", TargetPort: intstr.FromInt32(2379), Port: 2379, Protocol: corev1.ProtocolTCP},
181-
},
182-
Type: corev1.ServiceTypeClusterIP,
183-
ClusterIP: "None",
184-
Selector: map[string]string{
185-
"app.kubernetes.io/name": "etcd",
186-
"app.kubernetes.io/instance": cluster.Name,
187-
"app.kubernetes.io/managed-by": "etcd-operator",
188-
},
189-
PublishNotReadyAddresses: true,
190-
},
191-
}
192-
if err = ctrl.SetControllerReference(cluster, svc, r.Scheme); err != nil {
193-
return fmt.Errorf("cannot set controller reference: %w", err)
194-
}
195-
if err = r.Create(ctx, svc); err != nil {
196-
return fmt.Errorf("cannot create cluster service: %w", err)
197-
}
198-
return nil
199-
}
200-
201-
func (r *EtcdClusterReconciler) ensureClusterClientService(ctx context.Context, cluster *etcdaenixiov1alpha1.EtcdCluster) error {
202-
svc := &corev1.Service{}
203-
err := r.Get(ctx, client.ObjectKey{
204-
Namespace: cluster.Namespace,
205-
Name: r.getClientServiceName(cluster),
206-
}, svc)
207-
// Service exists, skip creation
208-
if err == nil {
209-
return nil
210-
}
211-
if !errors.IsNotFound(err) {
212-
return fmt.Errorf("cannot get cluster client service: %w", err)
213-
}
214-
215-
svc = &corev1.Service{
216-
ObjectMeta: metav1.ObjectMeta{
217-
Name: r.getClientServiceName(cluster),
218-
Namespace: cluster.Namespace,
219-
Labels: map[string]string{
220-
"app.kubernetes.io/name": "etcd",
221-
"app.kubernetes.io/instance": cluster.Name,
222-
"app.kubernetes.io/managed-by": "etcd-operator",
223-
},
224-
},
225-
Spec: corev1.ServiceSpec{
226-
Ports: []corev1.ServicePort{
227-
{Name: "client", TargetPort: intstr.FromInt32(2379), Port: 2379, Protocol: corev1.ProtocolTCP},
228-
},
229-
Type: corev1.ServiceTypeClusterIP,
230-
Selector: map[string]string{
231-
"app.kubernetes.io/name": "etcd",
232-
"app.kubernetes.io/instance": cluster.Name,
233-
"app.kubernetes.io/managed-by": "etcd-operator",
234-
},
235-
},
236-
}
237-
if err = ctrl.SetControllerReference(cluster, svc, r.Scheme); err != nil {
238-
return fmt.Errorf("cannot set controller reference: %w", err)
239-
}
240-
if err = r.Create(ctx, svc); err != nil {
241-
return fmt.Errorf("cannot create cluster client service: %w", err)
242-
}
243-
return nil
244-
}
245-
246-
// ensureClusterStateConfigMap creates or updates cluster state configmap.
247-
func (r *EtcdClusterReconciler) ensureClusterStateConfigMap(
248-
ctx context.Context, cluster *etcdaenixiov1alpha1.EtcdCluster, isClusterInitialized bool) error {
249-
configMap := &corev1.ConfigMap{}
250-
err := r.Get(ctx, client.ObjectKey{
251-
Namespace: cluster.Namespace,
252-
Name: factory.GetClusterStateConfigMapName(cluster),
253-
}, configMap)
254-
// configmap exists, skip editing.
255-
if err == nil {
256-
if isClusterInitialized {
257-
// update cluster state to existing
258-
configMap.Data["ETCD_INITIAL_CLUSTER_STATE"] = "existing"
259-
if err = r.Update(ctx, configMap); err != nil {
260-
return fmt.Errorf("cannot update cluster state configmap: %w", err)
261-
}
262-
}
263-
return nil
264-
}
265-
266-
// configmap does not exist, create with cluster state "new"
267-
if errors.IsNotFound(err) {
268-
initialCluster := ""
269-
for i := int32(0); i < *cluster.Spec.Replicas; i++ {
270-
if i > 0 {
271-
initialCluster += ","
272-
}
273-
initialCluster += fmt.Sprintf("%s-%d=https://%s-%d.%s.%s.svc:2380",
274-
cluster.Name, i,
275-
cluster.Name, i, cluster.Name, cluster.Namespace,
276-
)
277-
}
278-
279-
configMap = &corev1.ConfigMap{
280-
ObjectMeta: metav1.ObjectMeta{
281-
Namespace: cluster.Namespace,
282-
Name: factory.GetClusterStateConfigMapName(cluster),
283-
},
284-
Data: map[string]string{
285-
"ETCD_INITIAL_CLUSTER_STATE": "new",
286-
"ETCD_INITIAL_CLUSTER": initialCluster,
287-
"ETCD_INITIAL_CLUSTER_TOKEN": cluster.Name + "-" + cluster.Namespace,
288-
},
289-
}
290-
if err := ctrl.SetControllerReference(cluster, configMap, r.Scheme); err != nil {
291-
return fmt.Errorf("cannot set controller reference: %w", err)
292-
}
293-
if err := r.Create(ctx, configMap); err != nil {
294-
return fmt.Errorf("cannot create cluster state configmap: %w", err)
295-
}
296-
return nil
297-
}
298-
299-
return fmt.Errorf("cannot get cluster state configmap: %w", err)
300-
}
301-
302-
func (r *EtcdClusterReconciler) getClientServiceName(cluster *etcdaenixiov1alpha1.EtcdCluster) string {
303-
return cluster.Name + "-client"
304-
}
305-
306152
// updateStatusOnErr wraps error and updates EtcdCluster status
307153
func (r *EtcdClusterReconciler) updateStatusOnErr(ctx context.Context, cluster *etcdaenixiov1alpha1.EtcdCluster, err error) (ctrl.Result, error) {
308154
res, statusErr := r.updateStatus(ctx, cluster)

internal/controller/etcdcluster_controller_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ var _ = Describe("EtcdCluster Controller", func() {
119119
svc = &v1.Service{}
120120
clientSvcName := types.NamespacedName{
121121
Namespace: typeNamespacedName.Namespace,
122-
Name: controllerReconciler.getClientServiceName(etcdcluster),
122+
Name: factory.GetClientServiceName(etcdcluster),
123123
}
124124
err = k8sClient.Get(ctx, clientSvcName, svc)
125125
Expect(err).NotTo(HaveOccurred(), "cluster client Service should exist")

internal/controller/factory/builders.go

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,15 @@ import (
2121
"fmt"
2222

2323
appsv1 "k8s.io/api/apps/v1"
24+
corev1 "k8s.io/api/core/v1"
2425
"k8s.io/apimachinery/pkg/api/errors"
2526
"k8s.io/apimachinery/pkg/labels"
2627
"k8s.io/apimachinery/pkg/types"
2728
"sigs.k8s.io/controller-runtime/pkg/client"
2829
"sigs.k8s.io/controller-runtime/pkg/log"
2930
)
3031

31-
func reconcileSTS(ctx context.Context, rclient client.Client, crdName string, sts *appsv1.StatefulSet) error {
32+
func reconcileStatefulSet(ctx context.Context, rclient client.Client, crdName string, sts *appsv1.StatefulSet) error {
3233
logger := log.FromContext(ctx)
3334

3435
currentSts := &appsv1.StatefulSet{}
@@ -47,3 +48,42 @@ func reconcileSTS(ctx context.Context, rclient client.Client, crdName string, st
4748
sts.Status = currentSts.Status
4849
return rclient.Update(ctx, sts)
4950
}
51+
52+
func reconcileConfigMap(ctx context.Context, rclient client.Client, crdName string, configMap *corev1.ConfigMap) error {
53+
logger := log.FromContext(ctx)
54+
55+
currentConfigMap := &corev1.ConfigMap{}
56+
err := rclient.Get(ctx, types.NamespacedName{Namespace: configMap.Namespace, Name: configMap.Name}, currentConfigMap)
57+
if err != nil {
58+
if errors.IsNotFound(err) {
59+
logger.V(2).Info("creating new configMap", "cm_name", configMap.Name, "crd_object", crdName)
60+
return rclient.Create(ctx, configMap)
61+
}
62+
return fmt.Errorf("cannot get existing configMap: %s, for crd_object: %s, err: %w", configMap.Name, crdName, err)
63+
}
64+
configMap.Annotations = labels.Merge(currentConfigMap.Annotations, configMap.Annotations)
65+
if configMap.ResourceVersion != "" {
66+
configMap.ResourceVersion = currentConfigMap.ResourceVersion
67+
}
68+
return rclient.Update(ctx, configMap)
69+
}
70+
71+
func reconcileService(ctx context.Context, rclient client.Client, crdName string, svc *corev1.Service) error {
72+
logger := log.FromContext(ctx)
73+
74+
currentSvc := &corev1.Service{}
75+
err := rclient.Get(ctx, types.NamespacedName{Namespace: svc.Namespace, Name: svc.Name}, currentSvc)
76+
if err != nil {
77+
if errors.IsNotFound(err) {
78+
logger.V(2).Info("creating new service", "svc_name", svc.Name, "crd_object", crdName)
79+
return rclient.Create(ctx, svc)
80+
}
81+
return fmt.Errorf("cannot get existing service: %s, for crd_object: %s, err: %w", svc.Name, crdName, err)
82+
}
83+
svc.Annotations = labels.Merge(currentSvc.Annotations, svc.Annotations)
84+
if svc.ResourceVersion != "" {
85+
svc.ResourceVersion = currentSvc.ResourceVersion
86+
}
87+
svc.Status = currentSvc.Status
88+
return rclient.Update(ctx, svc)
89+
}

internal/controller/factory/configMap.go

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,61 @@ limitations under the License.
1616

1717
package factory
1818

19-
import etcdaenixiov1alpha1 "github.com/aenix-io/etcd-operator/api/v1alpha1"
19+
import (
20+
"context"
21+
"fmt"
22+
23+
corev1 "k8s.io/api/core/v1"
24+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25+
"k8s.io/apimachinery/pkg/runtime"
26+
ctrl "sigs.k8s.io/controller-runtime"
27+
"sigs.k8s.io/controller-runtime/pkg/client"
28+
29+
etcdaenixiov1alpha1 "github.com/aenix-io/etcd-operator/api/v1alpha1"
30+
)
2031

2132
func GetClusterStateConfigMapName(cluster *etcdaenixiov1alpha1.EtcdCluster) string {
2233
return cluster.Name + "-cluster-state"
2334
}
35+
36+
func CreateOrUpdateClusterStateConfigMap(
37+
ctx context.Context,
38+
cluster *etcdaenixiov1alpha1.EtcdCluster,
39+
isClusterReady bool,
40+
rclient client.Client,
41+
rscheme *runtime.Scheme,
42+
) error {
43+
initialCluster := ""
44+
for i := int32(0); i < *cluster.Spec.Replicas; i++ {
45+
if i > 0 {
46+
initialCluster += ","
47+
}
48+
initialCluster += fmt.Sprintf("%s-%d=https://%s-%d.%s.%s.svc:2380",
49+
cluster.Name, i,
50+
cluster.Name, i, cluster.Name, cluster.Namespace,
51+
)
52+
}
53+
54+
configMap := &corev1.ConfigMap{
55+
ObjectMeta: metav1.ObjectMeta{
56+
Namespace: cluster.Namespace,
57+
Name: GetClusterStateConfigMapName(cluster),
58+
},
59+
Data: map[string]string{
60+
"ETCD_INITIAL_CLUSTER_STATE": "new",
61+
"ETCD_INITIAL_CLUSTER": initialCluster,
62+
"ETCD_INITIAL_CLUSTER_TOKEN": cluster.Name + "-" + cluster.Namespace,
63+
},
64+
}
65+
66+
if isClusterReady {
67+
// update cluster state to existing
68+
configMap.Data["ETCD_INITIAL_CLUSTER_STATE"] = "existing"
69+
}
70+
71+
if err := ctrl.SetControllerReference(cluster, configMap, rscheme); err != nil {
72+
return fmt.Errorf("cannot set controller reference: %w", err)
73+
}
74+
75+
return reconcileConfigMap(ctx, rclient, cluster.Name, configMap)
76+
}

0 commit comments

Comments
 (0)