Skip to content

Commit 2b20eaf

Browse files
mkuratczykansd
andauthored
Don't restart the cluster after scale-out (#1612)
* Don't restart the cluster after scale-out Co-authored-by: David Ansari <[email protected]> --------- Co-authored-by: David Ansari <[email protected]>
1 parent 59c0b45 commit 2b20eaf

File tree

4 files changed

+106
-4
lines changed

4 files changed

+106
-4
lines changed

controllers/reconcile_rabbitmq_configurations.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func (r *RabbitmqClusterReconciler) annotateIfNeeded(ctx context.Context, logger
3434
annotationKey string
3535
)
3636

37-
switch builder.(type) {
37+
switch b := builder.(type) {
3838

3939
case *resource.RabbitmqPluginsConfigMapBuilder:
4040
if operationResult != controllerutil.OperationResultUpdated {
@@ -45,7 +45,7 @@ func (r *RabbitmqClusterReconciler) annotateIfNeeded(ctx context.Context, logger
4545
annotationKey = pluginsUpdateAnnotation
4646

4747
case *resource.ServerConfigMapBuilder:
48-
if operationResult != controllerutil.OperationResultUpdated {
48+
if operationResult != controllerutil.OperationResultUpdated || !b.UpdateRequiresStsRestart {
4949
return nil
5050
}
5151
obj = &corev1.ConfigMap{}

controllers/reconcile_rabbitmq_configurations_test.go

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
package controllers_test
22

33
import (
4-
rabbitmqv1beta1 "github.com/rabbitmq/cluster-operator/v2/api/v1beta1"
54
"strings"
65
"time"
76

7+
rabbitmqv1beta1 "github.com/rabbitmq/cluster-operator/v2/api/v1beta1"
8+
89
. "github.com/onsi/ginkgo/v2"
910
. "github.com/onsi/gomega"
1011

1112
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
"k8s.io/utils/ptr"
1214
)
1315

1416
var _ = Describe("Reconcile rabbitmq Configurations", func() {
@@ -78,4 +80,35 @@ var _ = Describe("Reconcile rabbitmq Configurations", func() {
7880
Entry(nil, "advancedConfig"),
7981
Entry(nil, "envConfig"),
8082
)
83+
84+
Context("scale out", func() {
85+
It("does not restart StatefulSet", func() {
86+
cluster = &rabbitmqv1beta1.RabbitmqCluster{
87+
ObjectMeta: metav1.ObjectMeta{
88+
Namespace: defaultNamespace,
89+
Name: "rabbitmq-scale-out",
90+
},
91+
}
92+
Expect(client.Create(ctx, cluster)).To(Succeed())
93+
waitForClusterCreation(ctx, cluster, client)
94+
95+
cfm := configMap(ctx, cluster, "server-conf")
96+
Expect(cfm.Annotations).ShouldNot(HaveKey("rabbitmq.com/serverConfUpdatedAt"))
97+
sts := statefulSet(ctx, cluster)
98+
Expect(sts.Annotations).ShouldNot(HaveKey("rabbitmq.com/lastRestartAt"))
99+
100+
Expect(updateWithRetry(cluster, func(r *rabbitmqv1beta1.RabbitmqCluster) {
101+
r.Spec.Replicas = ptr.To(int32(5))
102+
})).To(Succeed())
103+
104+
Consistently(func() map[string]string {
105+
return configMap(ctx, cluster, "server-conf").Annotations
106+
}, 3, 0.3).ShouldNot(HaveKey("rabbitmq.com/serverConfUpdatedAt"))
107+
108+
Consistently(func() map[string]string {
109+
sts := statefulSet(ctx, cluster)
110+
return sts.Spec.Template.Annotations
111+
}, 3, 0.3).ShouldNot(HaveKey("rabbitmq.com/lastRestartAt"))
112+
})
113+
})
81114
})

internal/resource/configmap.go

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222

2323
"github.com/rabbitmq/cluster-operator/v2/internal/metadata"
2424
corev1 "k8s.io/api/core/v1"
25+
"k8s.io/apimachinery/pkg/api/equality"
2526
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2627
)
2728

@@ -59,10 +60,11 @@ prometheus.ssl.port = 15691
5960

6061
type ServerConfigMapBuilder struct {
6162
*RabbitmqResourceBuilder
63+
UpdateRequiresStsRestart bool
6264
}
6365

6466
func (builder *RabbitmqResourceBuilder) ServerConfigMap() *ServerConfigMapBuilder {
65-
return &ServerConfigMapBuilder{builder}
67+
return &ServerConfigMapBuilder{builder, true}
6668
}
6769

6870
func (builder *ServerConfigMapBuilder) Build() (client.Object, error) {
@@ -82,6 +84,7 @@ func (builder *ServerConfigMapBuilder) UpdateMayRequireStsRecreate() bool {
8284

8385
func (builder *ServerConfigMapBuilder) Update(object client.Object) error {
8486
configMap := object.(*corev1.ConfigMap)
87+
previousConfigMap := configMap.DeepCopy()
8588

8689
ini.PrettySection = false // Remove trailing new line because rabbitmq.conf has only a default section.
8790
operatorConfiguration, err := ini.Load([]byte(defaultRabbitmqConf))
@@ -247,6 +250,43 @@ func (builder *ServerConfigMapBuilder) Update(object client.Object) error {
247250
return fmt.Errorf("failed setting controller reference: %w", err)
248251
}
249252

253+
updatedConfigMap := configMap.DeepCopy()
254+
if err := removeConfigNotRequiringNodeRestart(previousConfigMap); err != nil {
255+
return err
256+
}
257+
if err := removeConfigNotRequiringNodeRestart(updatedConfigMap); err != nil {
258+
return err
259+
}
260+
if equality.Semantic.DeepEqual(previousConfigMap, updatedConfigMap) {
261+
builder.UpdateRequiresStsRestart = false
262+
}
263+
264+
return nil
265+
}
266+
267+
// removeConfigNotRequiringNodeRestart removes configuration data that does not require a restart of RabbitMQ nodes.
268+
// For example, the target cluster size hint changes after adding nodes to a cluster, but there's no reason
269+
// to restart already running nodes.
270+
func removeConfigNotRequiringNodeRestart(configMap *corev1.ConfigMap) error {
271+
operatorConf := configMap.Data["operatorDefaults.conf"]
272+
if operatorConf == "" {
273+
return nil
274+
}
275+
conf, err := ini.Load([]byte(operatorConf))
276+
if err != nil {
277+
return fmt.Errorf("failed to load operatorDefaults.conf when deciding whether to restart STS: %w", err)
278+
}
279+
defaultSection := conf.Section("")
280+
for _, key := range defaultSection.KeyStrings() {
281+
if strings.HasPrefix(key, "cluster_formation.target_cluster_size_hint") {
282+
defaultSection.DeleteKey(key)
283+
}
284+
}
285+
var b strings.Builder
286+
if _, err := conf.WriteTo(&b); err != nil {
287+
return fmt.Errorf("failed to write operatorDefaults.conf when deciding whether to restart STS: %w", err)
288+
}
289+
configMap.Data["operatorDefaults.conf"] = b.String()
250290
return nil
251291
}
252292

internal/resource/configmap_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ package resource_test
1212
import (
1313
"bytes"
1414
"fmt"
15+
1516
"k8s.io/utils/ptr"
1617

1718
. "github.com/onsi/ginkgo/v2"
@@ -579,6 +580,34 @@ CONSOLE_LOG=new`
579580
})
580581
})
581582
})
583+
584+
Describe("UpdateRequiresStsRestart", func() {
585+
BeforeEach(func() {
586+
Expect(configMapBuilder.Update(configMap)).To(Succeed())
587+
Expect(configMapBuilder.UpdateRequiresStsRestart).To(BeTrue())
588+
})
589+
When("the config does not change", func() {
590+
It("does not restart StatefulSet", func() {
591+
Expect(configMapBuilder.Update(configMap)).To(Succeed())
592+
Expect(configMapBuilder.UpdateRequiresStsRestart).To(BeFalse())
593+
})
594+
})
595+
When("the only config change is cluster formation nodes", func() {
596+
It("does not require the StatefulSet to be restarted", func() {
597+
instance.Spec.Replicas = ptr.To(int32(3))
598+
Expect(configMapBuilder.Update(configMap)).To(Succeed())
599+
Expect(configMapBuilder.UpdateRequiresStsRestart).To(BeFalse())
600+
})
601+
})
602+
When("config change includes more than cluster formation nodes", func() {
603+
It("requires the StatefulSet to be restarted", func() {
604+
instance.Spec.Replicas = ptr.To(int32(3))
605+
instance.Spec.Rabbitmq.AdditionalConfig = "foo = bar"
606+
Expect(configMapBuilder.Update(configMap)).To(Succeed())
607+
Expect(configMapBuilder.UpdateRequiresStsRestart).To(BeTrue())
608+
})
609+
})
610+
})
582611
})
583612

584613
Context("UpdateMayRequireStsRecreate", func() {

0 commit comments

Comments
 (0)