Skip to content

Commit 590bdcd

Browse files
mkuratczykZerpet
andauthored
Optionally enable all feature flags automatically (#1892)
* Add `autoEnableAllFeatureFlags` property Default to false. When set to true, all feature flags are automatically enabled immediately after each upgrade (we don't check if there are new feature flags - in most cases, this command is a no-op). Note that when upgrade to RabbitMQ 4.2, this means Khepri is gets enabled just after the upgrade (again, if the property is set to true). Co-authored-by: Aitor Pérez Cedres <[email protected]>
1 parent ac50713 commit 590bdcd

File tree

5 files changed

+110
-34
lines changed

5 files changed

+110
-34
lines changed

api/v1beta1/rabbitmqcluster_types.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ import (
1515

1616
appsv1 "k8s.io/api/apps/v1"
1717

18+
"slices"
19+
1820
corev1 "k8s.io/api/core/v1"
1921
k8sresource "k8s.io/apimachinery/pkg/api/resource"
2022
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21-
"slices"
2223
)
2324

2425
const DisableDefaultTopologySpreadAnnotation = "rabbitmq.com/disable-default-topology-spread-constraints"
@@ -81,6 +82,9 @@ type RabbitmqClusterSpec struct {
8182
// Has no effect if the cluster only consists of one node.
8283
// For more information, see https://www.rabbitmq.com/rabbitmq-queues.8.html#rebalance
8384
SkipPostDeploySteps bool `json:"skipPostDeploySteps,omitempty"`
85+
// Set to true to automatically enable all feature flags after each upgrade
86+
// For more information, see https://www.rabbitmq.com/docs/feature-flags
87+
AutoEnableAllFeatureFlags bool `json:"autoEnableAllFeatureFlags,omitempty"`
8488
// TerminationGracePeriodSeconds is the timeout that each rabbitmqcluster pod will have to terminate gracefully.
8589
// It defaults to 604800 seconds ( a week long) to ensure that the container preStop lifecycle hook can finish running.
8690
// For more information, see: https://github.com/rabbitmq/cluster-operator/blob/main/docs/design/20200520-graceful-pod-termination.md

config/crd/bases/rabbitmq.com_rabbitmqclusters.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -936,6 +936,11 @@ spec:
936936
x-kubernetes-list-type: atomic
937937
type: object
938938
type: object
939+
autoEnableAllFeatureFlags:
940+
description: |-
941+
Set to true to automatically enable all feature flags after each upgrade
942+
For more information, see https://www.rabbitmq.com/docs/feature-flags
943+
type: boolean
939944
delayStartSeconds:
940945
default: 30
941946
description: |-

controllers/reconcile_cli.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func (r *RabbitmqClusterReconciler) runRabbitmqCLICommandsIfAnnotated(ctx contex
4949
}
5050

5151
// If RabbitMQ cluster is newly created, enable all feature flags since some are disabled by default
52-
if sts.Annotations != nil && sts.Annotations[stsCreateAnnotation] != "" {
52+
if sts.Annotations != nil && sts.Annotations[stsCreateAnnotation] != "" || rmq.Spec.AutoEnableAllFeatureFlags {
5353
if err := r.runEnableFeatureFlagsCommand(ctx, rmq, sts); err != nil {
5454
return 0, err
5555
}

controllers/reconcile_cli_test.go

Lines changed: 97 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package controllers_test
22

33
import (
4+
"sigs.k8s.io/controller-runtime/pkg/envtest/komega"
45
"time"
56

67
"k8s.io/apimachinery/pkg/types"
@@ -17,14 +18,9 @@ import (
1718
var _ = Describe("Reconcile CLI", func() {
1819
var (
1920
cluster *rabbitmqv1beta1.RabbitmqCluster
20-
annotations map[string]string
2121
defaultNamespace = "default"
2222
)
2323

24-
BeforeEach(func() {
25-
annotations = map[string]string{}
26-
})
27-
2824
AfterEach(func() {
2925
Expect(client.Delete(ctx, cluster)).To(Succeed())
3026
waitForClusterDeletion(ctx, cluster, client)
@@ -79,6 +75,7 @@ var _ = Describe("Reconcile CLI", func() {
7975
Expect(client.Create(ctx, cluster)).To(Succeed())
8076
waitForClusterCreation(ctx, cluster, client)
8177
})
78+
8279
When("the cluster is updated", func() {
8380
var sts *appsv1.StatefulSet
8481

@@ -94,46 +91,114 @@ var _ = Describe("Reconcile CLI", func() {
9491
})
9592

9693
It("triggers the controller to run rabbitmq-queues rebalance all", func() {
94+
k := komega.New(client)
95+
9796
By("setting an annotation on the CR", func() {
98-
Eventually(func() map[string]string {
99-
rmq := &rabbitmqv1beta1.RabbitmqCluster{}
100-
Expect(client.Get(ctx, types.NamespacedName{Name: cluster.Name, Namespace: cluster.Namespace}, rmq)).To(Succeed())
101-
annotations = rmq.ObjectMeta.Annotations
102-
return annotations
103-
}, 5).Should(HaveKey("rabbitmq.com/queueRebalanceNeededAt"))
104-
_, err := time.Parse(time.RFC3339, annotations["rabbitmq.com/queueRebalanceNeededAt"])
97+
rmq := &rabbitmqv1beta1.RabbitmqCluster{}
98+
rmq.Name = "rabbitmq-three"
99+
rmq.Namespace = defaultNamespace
100+
Eventually(k.Object(rmq)).Within(time.Second * 5).WithPolling(time.Second).Should(HaveField("ObjectMeta.Annotations", HaveKey("rabbitmq.com/queueRebalanceNeededAt")))
101+
102+
_, err := time.Parse(time.RFC3339, rmq.Annotations["rabbitmq.com/queueRebalanceNeededAt"])
105103
Expect(err).NotTo(HaveOccurred(), "annotation rabbitmq.com/queueRebalanceNeededAt was not a valid RFC3339 timestamp")
106104
})
107105

108106
By("not removing the annotation when all replicas are updated but not yet ready", func() {
109-
sts.Status.CurrentReplicas = 3
110-
sts.Status.CurrentRevision = "some-new-revision"
111-
sts.Status.UpdatedReplicas = 3
112-
sts.Status.UpdateRevision = "some-new-revision"
113-
sts.Status.ReadyReplicas = 2
114-
Expect(client.Status().Update(ctx, sts)).To(Succeed())
115-
Eventually(func() map[string]string {
116-
rmq := &rabbitmqv1beta1.RabbitmqCluster{}
117-
err := client.Get(ctx, types.NamespacedName{Name: cluster.Name, Namespace: cluster.Namespace}, rmq)
118-
Expect(err).To(BeNil())
119-
annotations = rmq.ObjectMeta.Annotations
120-
return annotations
121-
}, 5).Should(HaveKey("rabbitmq.com/queueRebalanceNeededAt"))
107+
// Setup transition
108+
Eventually(k.UpdateStatus(sts, func() {
109+
sts.Status.CurrentReplicas = 3
110+
sts.Status.CurrentRevision = "some-new-revision"
111+
sts.Status.UpdatedReplicas = 3
112+
sts.Status.UpdateRevision = "some-new-revision"
113+
sts.Status.ReadyReplicas = 2
114+
})).Should(Succeed())
115+
116+
// by not removing the annotation
117+
rmq := &rabbitmqv1beta1.RabbitmqCluster{}
118+
rmq.Name = "rabbitmq-three"
119+
rmq.Namespace = defaultNamespace
120+
Eventually(k.Object(rmq)).Within(time.Second * 5).WithPolling(time.Second).Should(HaveField("ObjectMeta.Annotations", HaveKey("rabbitmq.com/queueRebalanceNeededAt")))
121+
122+
// by not running the commands
122123
Expect(fakeExecutor.ExecutedCommands()).NotTo(ContainElement(command{"sh", "-c", "rabbitmq-queues rebalance all"}))
123-
_, err := time.Parse(time.RFC3339, annotations["rabbitmq.com/queueRebalanceNeededAt"])
124+
Expect(fakeExecutor.ExecutedCommands()).NotTo(ContainElement(command{"bash", "-c", "rabbitmqctl enable_feature_flag all"}))
125+
_, err := time.Parse(time.RFC3339, rmq.Annotations["rabbitmq.com/queueRebalanceNeededAt"])
124126
Expect(err).NotTo(HaveOccurred(), "Annotation rabbitmq.com/queueRebalanceNeededAt was not a valid RFC3339 timestamp")
125127
})
126128

127129
By("removing the annotation once all Pods are up, and triggering the queue rebalance", func() {
130+
// setup transition to all pods ready
128131
sts.Status.ReadyReplicas = 3
129132
Expect(client.Status().Update(ctx, sts)).To(Succeed())
130-
Eventually(func() map[string]string {
131-
rmq := &rabbitmqv1beta1.RabbitmqCluster{}
132-
err := client.Get(ctx, types.NamespacedName{Name: cluster.Name, Namespace: cluster.Namespace}, rmq)
133-
Expect(err).To(BeNil())
134-
return rmq.ObjectMeta.Annotations
135-
}, 5).ShouldNot(HaveKey("rabbitmq.com/queueRebalanceNeededAt"))
133+
134+
// by not having the annotation
135+
rmq := &rabbitmqv1beta1.RabbitmqCluster{}
136+
rmq.Name = "rabbitmq-three"
137+
rmq.Namespace = defaultNamespace
138+
Eventually(k.Object(rmq)).Within(time.Second * 5).WithPolling(time.Second).ShouldNot(HaveField("ObjectMeta.Annotations", HaveKey("rabbitmq.com/queueRebalanceNeededAt")))
139+
140+
// by executing the commands
136141
Expect(fakeExecutor.ExecutedCommands()).To(ContainElement(command{"sh", "-c", "rabbitmq-queues rebalance all"}))
142+
Expect(fakeExecutor.ExecutedCommands()).To(ContainElement(command{"bash", "-c", "rabbitmqctl enable_feature_flag all"}))
143+
})
144+
})
145+
})
146+
147+
Describe("autoEnableAllFeatureFlags", func() {
148+
var (
149+
sts *appsv1.StatefulSet
150+
k komega.Komega
151+
)
152+
153+
BeforeEach(func() {
154+
k = komega.New(client)
155+
sts = statefulSet(ctx, cluster)
156+
sts.Status = appsv1.StatefulSetStatus{
157+
Replicas: 3,
158+
ReadyReplicas: 3,
159+
CurrentReplicas: 3,
160+
UpdatedReplicas: 3,
161+
CurrentRevision: "the last one",
162+
UpdateRevision: "the last one",
163+
AvailableReplicas: 3,
164+
}
165+
sts.Annotations = make(map[string]string)
166+
Expect(client.Status().Update(ctx, sts)).To(Succeed())
167+
})
168+
169+
When("disabled", func() {
170+
It("doesn't call enable_feature_flag CLI", func() {
171+
// BeforeEach updated the STS from zero replicas ready to all ready
172+
// Initial rabbitmqcluster create command has reconciles pending to have
173+
// all replicas ready to execute commands. We have to reset the "registry"
174+
// of commands.
175+
fakeExecutor.ResetExecutedCommands()
176+
177+
Eventually(k.Update(cluster, func() {
178+
if cluster != nil {
179+
cluster.Spec.AutoEnableAllFeatureFlags = false
180+
}
181+
})).Should(Succeed())
182+
Consistently(fakeExecutor.ExecutedCommands).Within(time.Second * 5).WithPolling(time.Second).
183+
ShouldNot(ContainElement(command{"bash", "-c", "rabbitmqctl enable_feature_flag all"}))
184+
})
185+
})
186+
187+
When("enabled", func() {
188+
It("calls enable_feature_flag CLI", func() {
189+
// BeforeEach updated the STS from zero replicas ready to all ready
190+
// Initial rabbitmqcluster create command has reconciles pending to have
191+
// all replicas ready to execute commands. We have to reset the "registry"
192+
// of commands.
193+
fakeExecutor.ResetExecutedCommands()
194+
195+
Eventually(k.Update(cluster, func() {
196+
if cluster != nil {
197+
cluster.Spec.AutoEnableAllFeatureFlags = true
198+
}
199+
})).Should(Succeed())
200+
Eventually(fakeExecutor.ExecutedCommands).Within(time.Second * 5).WithPolling(time.Second).
201+
Should(ContainElement(command{"bash", "-c", "rabbitmqctl enable_feature_flag all"}))
137202
})
138203
})
139204
})

docs/api/rabbitmq.com.ref.asciidoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,8 @@ Must be provided together with ImagePullSecrets in order to use an image in a pr
382382
Set to true to prevent the operator rebalancing queue leaders after a cluster update.
383383
Has no effect if the cluster only consists of one node.
384384
For more information, see https://www.rabbitmq.com/rabbitmq-queues.8.html#rebalance
385+
| *`autoEnableAllFeatureFlags`* __boolean__ | Set to true to automatically enable all feature flags after each upgrade
386+
For more information, see https://www.rabbitmq.com/docs/feature-flags
385387
| *`terminationGracePeriodSeconds`* __integer__ | TerminationGracePeriodSeconds is the timeout that each rabbitmqcluster pod will have to terminate gracefully.
386388
It defaults to 604800 seconds ( a week long) to ensure that the container preStop lifecycle hook can finish running.
387389
For more information, see: https://github.com/rabbitmq/cluster-operator/blob/main/docs/design/20200520-graceful-pod-termination.md

0 commit comments

Comments
 (0)