diff --git a/api/v1beta1/rabbitmqcluster_types.go b/api/v1beta1/rabbitmqcluster_types.go index 236ada443..6bf2c163a 100644 --- a/api/v1beta1/rabbitmqcluster_types.go +++ b/api/v1beta1/rabbitmqcluster_types.go @@ -21,6 +21,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +const DisableDefaultTopologySpreadAnnotation = "rabbitmq.com/disable-default-topology-spread-constraints" + // +kubebuilder:object:root=true // +kubebuilder:subresource:status // +kubebuilder:printcolumn:name="AllReplicasReady",type="string",JSONPath=".status.conditions[?(@.type == 'AllReplicasReady')].status" @@ -506,6 +508,14 @@ func (cluster RabbitmqCluster) PVCName(i int) string { return strings.Join([]string{"persistence", cluster.Name, "server", strconv.Itoa(i)}, "-") } +func (cluster RabbitmqCluster) DisableDefaultTopologySpreadConstraints() bool { + value, ok := cluster.Annotations[DisableDefaultTopologySpreadAnnotation] + if ok && strings.TrimSpace(value) == "true" { + return true + } + return false +} + func init() { SchemeBuilder.Register(&RabbitmqCluster{}, &RabbitmqClusterList{}) } diff --git a/internal/resource/statefulset.go b/internal/resource/statefulset.go index bf8555217..21be0715c 100644 --- a/internal/resource/statefulset.go +++ b/internal/resource/statefulset.go @@ -551,19 +551,7 @@ func (builder *StatefulSetBuilder) podTemplateSpec(previousPodAnnotations map[st Labels: metadata.Label(builder.Instance.Name), }, Spec: corev1.PodSpec{ - TopologySpreadConstraints: []corev1.TopologySpreadConstraint{ - { - MaxSkew: 1, - // "topology.kubernetes.io/zone" is a well-known label. - // It is automatically set by kubelet if the cloud provider provides the zone information. - // See: https://kubernetes.io/docs/reference/kubernetes-api/labels-annotations-taints/#topologykubernetesiozone - TopologyKey: "topology.kubernetes.io/zone", - WhenUnsatisfiable: corev1.ScheduleAnyway, - LabelSelector: &metav1.LabelSelector{ - MatchLabels: metadata.LabelSelector(builder.Instance.Name), - }, - }, - }, + TopologySpreadConstraints: builder.defaultTopologySpreadConstraints(), SecurityContext: &corev1.PodSecurityContext{ FSGroup: ptr.To(int64(0)), RunAsUser: &rabbitmqUID, @@ -1088,6 +1076,27 @@ func (builder *StatefulSetBuilder) updateContainerPortsOnlyTLSListeners() []core return ports } +func (builder *StatefulSetBuilder) defaultTopologySpreadConstraints() []corev1.TopologySpreadConstraint { + + if builder.Instance.DisableDefaultTopologySpreadConstraints() { + return []corev1.TopologySpreadConstraint{} + } + + return []corev1.TopologySpreadConstraint{ + { + MaxSkew: 1, + // "topology.kubernetes.io/zone" is a well-known label. + // It is automatically set by kubelet if the cloud provider provides the zone information. + // See: https://kubernetes.io/docs/reference/kubernetes-api/labels-annotations-taints/#topologykubernetesiozone + TopologyKey: "topology.kubernetes.io/zone", + WhenUnsatisfiable: corev1.ScheduleAnyway, + LabelSelector: &metav1.LabelSelector{ + MatchLabels: metadata.LabelSelector(builder.Instance.Name), + }, + }, + } +} + func copyLabelsAnnotations(base *metav1.ObjectMeta, override rabbitmqv1beta1.EmbeddedLabelsAnnotations) { if override.Labels != nil { base.Labels = mergeMap(base.Labels, override.Labels) diff --git a/internal/resource/statefulset_test.go b/internal/resource/statefulset_test.go index cf38db590..f7bdaa826 100644 --- a/internal/resource/statefulset_test.go +++ b/internal/resource/statefulset_test.go @@ -2138,6 +2138,87 @@ default_pass = {{ .Data.data.password }} }) + Context("TopologySpreadConstraints composition", func() { + BeforeEach(func() { + instance.Spec.Override.StatefulSet = &rabbitmqv1beta1.StatefulSet{ + Spec: &rabbitmqv1beta1.StatefulSetSpec{ + Template: &rabbitmqv1beta1.PodTemplateSpec{ + Spec: &corev1.PodSpec{}, + }, + }, + } + builder = &resource.RabbitmqResourceBuilder{ + Instance: &instance, + Scheme: scheme, + } + }) + When("Default TopologySpreadConstraints", func() { + It("uses the default", func() { + stsBuilder := builder.StatefulSet() + Expect(stsBuilder.Update(statefulSet)).To(Succeed()) + Expect(statefulSet.Spec.Template.Spec.TopologySpreadConstraints).To(ConsistOf( + corev1.TopologySpreadConstraint{ + MaxSkew: 1, + TopologyKey: "topology.kubernetes.io/zone", + WhenUnsatisfiable: corev1.ScheduleAnyway, + LabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app.kubernetes.io/name": instance.Name, + }, + }, + }, + )) + }) + }) + When("Disable Default TopologySpreadConstraints", func() { + It("does not have any constraint", func() { + instance.Annotations = map[string]string{rabbitmqv1beta1.DisableDefaultTopologySpreadAnnotation: "true"} + stsBuilder := builder.StatefulSet() + Expect(stsBuilder.Update(statefulSet)).To(Succeed()) + Expect(statefulSet.Spec.Template.Spec.TopologySpreadConstraints).To(BeEmpty()) + }) + }) + When("Disable Default TopologySpreadConstraints and override has a value", func() { + It("overrides the default", func() { + instance.Annotations = map[string]string{rabbitmqv1beta1.DisableDefaultTopologySpreadAnnotation: "true"} + instance.Spec.Override.StatefulSet = &rabbitmqv1beta1.StatefulSet{ + Spec: &rabbitmqv1beta1.StatefulSetSpec{ + Template: &rabbitmqv1beta1.PodTemplateSpec{ + Spec: &corev1.PodSpec{ + TopologySpreadConstraints: []corev1.TopologySpreadConstraint{ + { + MaxSkew: 1, + TopologyKey: "my-topology", + WhenUnsatisfiable: corev1.DoNotSchedule, + LabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "key": "value", + }, + }, + }, + }, + }, + }, + }, + } + stsBuilder := builder.StatefulSet() + Expect(stsBuilder.Update(statefulSet)).To(Succeed()) + Expect(statefulSet.Spec.Template.Spec.TopologySpreadConstraints).To(ConsistOf( + corev1.TopologySpreadConstraint{ + MaxSkew: 1, + TopologyKey: "my-topology", + WhenUnsatisfiable: corev1.DoNotSchedule, + LabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "key": "value", + }, + }, + }, + )) + }) + }) + }) + Context("Rabbitmq Container volume mounts", func() { It("Overrides the volume mounts list while making sure that '/var/lib/rabbitmq/' mounts before '/var/lib/rabbitmq/mnesia/' ", func() { instance.Spec.Override.StatefulSet = &rabbitmqv1beta1.StatefulSet{