diff --git a/api/v1beta1/rabbitmqcluster_types.go b/api/v1beta1/rabbitmqcluster_types.go index 980c668ba..46c3d95de 100644 --- a/api/v1beta1/rabbitmqcluster_types.go +++ b/api/v1beta1/rabbitmqcluster_types.go @@ -385,6 +385,10 @@ type RabbitmqClusterConfigurationSpec struct { // For more information on env config, see https://www.rabbitmq.com/man/rabbitmq-env.conf.5.html // +kubebuilder:validation:MaxLength:=100000 EnvConfig string `json:"envConfig,omitempty"` + // Erlang Inet configuration to apply to the Erlang VM running rabbit. + // See also: https://www.erlang.org/doc/apps/erts/inet_cfg.html + // +kubebuilder:validation:MaxLength:=2000 + ErlangInetConfig string `json:"erlangInetConfig,omitempty"` } // The settings for the persistent storage desired for each Pod in the RabbitmqCluster. @@ -407,6 +411,10 @@ type RabbitmqClusterServiceSpec struct { Type corev1.ServiceType `json:"type,omitempty"` // Annotations to add to the Service. Annotations map[string]string `json:"annotations,omitempty"` + // IPFamilyPolicy represents the dual-stack-ness requested or required by a Service + // See also: https://pkg.go.dev/k8s.io/api/core/v1#IPFamilyPolicy + // +kubebuilder:validation:Enum=SingleStack;PreferDualStack;RequireDualStack + IPFamilyPolicy *corev1.IPFamilyPolicy `json:"ipFamilyPolicy,omitempty"` } func (cluster *RabbitmqCluster) TLSEnabled() bool { diff --git a/api/v1beta1/rabbitmqcluster_types_test.go b/api/v1beta1/rabbitmqcluster_types_test.go index eb5566b41..7c9904d66 100644 --- a/api/v1beta1/rabbitmqcluster_types_test.go +++ b/api/v1beta1/rabbitmqcluster_types_test.go @@ -135,6 +135,24 @@ var _ = Describe("RabbitmqCluster", func() { Expect(apierrors.IsInvalid(k8sClient.Create(context.Background(), invalidService))).To(BeTrue()) Expect(k8sClient.Create(context.Background(), invalidService)).To(MatchError(ContainSubstring("supported values: \"ClusterIP\", \"LoadBalancer\", \"NodePort\""))) }) + + By("checking the IP family policy", func() { + invalidSvc := generateRabbitmqClusterObject("madeup-family-policy") + policy := corev1.IPFamilyPolicy("my-awesome-policy") + invalidSvc.Spec.Service.IPFamilyPolicy = &policy + Expect(apierrors.IsInvalid(k8sClient.Create(context.Background(), invalidSvc))).To(BeTrue()) + }) + }) + + It("can be created with Erlang configuration", func() { + created := generateRabbitmqClusterObject("erlang-configuration") + erlangConfig := "{some_config, 123}." + created.Spec.Rabbitmq.ErlangInetConfig = erlangConfig + Expect(k8sClient.Create(context.Background(), created)).To(Succeed()) + + got := &RabbitmqCluster{} + Expect(k8sClient.Get(context.Background(), getKey(created), got)).To(Succeed()) + Expect(got.Spec.Rabbitmq.ErlangInetConfig).To(Equal(erlangConfig)) }) Describe("ChildResourceName", func() { diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 1e15e99b3..e2318bed7 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -1,5 +1,4 @@ //go:build !ignore_autogenerated -// +build !ignore_autogenerated /* RabbitMQ Cluster Operator @@ -325,6 +324,11 @@ func (in *RabbitmqClusterServiceSpec) DeepCopyInto(out *RabbitmqClusterServiceSp (*out)[key] = val } } + if in.IPFamilyPolicy != nil { + in, out := &in.IPFamilyPolicy, &out.IPFamilyPolicy + *out = new(v1.IPFamilyPolicy) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RabbitmqClusterServiceSpec. diff --git a/config/crd/bases/rabbitmq.com_rabbitmqclusters.yaml b/config/crd/bases/rabbitmq.com_rabbitmqclusters.yaml index e2e76d8d6..9b4dc9f8f 100644 --- a/config/crd/bases/rabbitmq.com_rabbitmqclusters.yaml +++ b/config/crd/bases/rabbitmq.com_rabbitmqclusters.yaml @@ -10,7 +10,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.12.1 + controller-gen.kubebuilder.io/version: v0.13.0 name: rabbitmqclusters.rabbitmq.com spec: group: rabbitmq.com @@ -4127,6 +4127,10 @@ spec: description: Modify to add to the rabbitmq-env.conf file. Modifying this property on an existing RabbitmqCluster will trigger a StatefulSet rolling restart and will cause rabbitmq downtime. For more information on env config, see https://www.rabbitmq.com/man/rabbitmq-env.conf.5.html maxLength: 100000 type: string + erlangInetConfig: + description: 'Erlang Inet configuration to apply to the Erlang VM running rabbit. See also: https://www.erlang.org/doc/apps/erts/inet_cfg.html' + maxLength: 2000 + type: string type: object replicas: default: 1 @@ -4233,6 +4237,13 @@ spec: type: string description: Annotations to add to the Service. type: object + ipFamilyPolicy: + description: 'IPFamilyPolicy represents the dual-stack-ness requested or required by a Service See also: https://pkg.go.dev/k8s.io/api/core/v1#IPFamilyPolicy' + enum: + - SingleStack + - PreferDualStack + - RequireDualStack + type: string type: default: ClusterIP description: 'Type of Service to create for the cluster. Must be one of: ClusterIP, LoadBalancer, NodePort. For more info see https://pkg.go.dev/k8s.io/api/core/v1#ServiceType' diff --git a/controllers/rabbitmqcluster_controller.go b/controllers/rabbitmqcluster_controller.go index f8853c99f..b83c97d5b 100644 --- a/controllers/rabbitmqcluster_controller.go +++ b/controllers/rabbitmqcluster_controller.go @@ -151,6 +151,8 @@ func (r *RabbitmqClusterReconciler) Reconcile(ctx context.Context, req ctrl.Requ logger.Info("Start reconciling") + // FIXME: marshalling is expensive. We are marshalling only for the sake of logging. + // Implement Stringer interface instead instanceSpec, err := json.Marshal(rabbitmqCluster.Spec) if err != nil { logger.Error(err, "Failed to marshal cluster spec") @@ -290,6 +292,7 @@ func (r *RabbitmqClusterReconciler) updateStatusConditions(ctx context.Context, if !reflect.DeepEqual(rmq.Status.Conditions, oldConditions) { if err = r.Status().Update(ctx, rmq); err != nil { + // FIXME: must fetch again to avoid the conflict if k8serrors.IsConflict(err) { logger.Info("failed to update status because of conflict; requeueing...", "namespace", rmq.Namespace, diff --git a/controllers/rabbitmqcluster_controller_test.go b/controllers/rabbitmqcluster_controller_test.go index 57f0d9270..96eac4062 100644 --- a/controllers/rabbitmqcluster_controller_test.go +++ b/controllers/rabbitmqcluster_controller_test.go @@ -13,6 +13,7 @@ package controllers_test import ( "context" "fmt" + "k8s.io/utils/ptr" "time" "k8s.io/utils/pointer" @@ -339,6 +340,23 @@ var _ = Describe("RabbitmqClusterController", func() { Expect(clientSvc.Spec.Type).Should(Equal(corev1.ServiceTypeLoadBalancer)) Expect(clientSvc.Annotations).Should(HaveKeyWithValue("annotations", "cr-annotation")) }) + + It("creates the service with the expected IP family policy", func() { + cluster = &rabbitmqv1beta1.RabbitmqCluster{ + ObjectMeta: metav1.ObjectMeta{Name: "rabbit-with-ip-family", Namespace: defaultNamespace}, + } + cluster.Spec.Service.IPFamilyPolicy = ptr.To(corev1.IPFamilyPolicyPreferDualStack) + + Expect(client.Create(ctx, cluster)).To(Succeed()) + + clientSvc := service(ctx, cluster, "") + Expect(clientSvc.Spec.IPFamilyPolicy).ToNot(BeNil()) + Expect(clientSvc.Spec.IPFamilyPolicy).To(BeEquivalentTo(ptr.To("PreferDualStack"))) + + headlessSvc := service(ctx, cluster, "nodes") + Expect(headlessSvc.Spec.IPFamilyPolicy).ToNot(BeNil()) + Expect(headlessSvc.Spec.IPFamilyPolicy).To(BeEquivalentTo(ptr.To("PreferDualStack"))) + }) }) Context("Resource requirements configurations", func() { diff --git a/docs/api/rabbitmq.com.ref.asciidoc b/docs/api/rabbitmq.com.ref.asciidoc index a44afb481..31716ccda 100644 --- a/docs/api/rabbitmq.com.ref.asciidoc +++ b/docs/api/rabbitmq.com.ref.asciidoc @@ -154,6 +154,7 @@ RabbitMQ-related configuration. | *`additionalConfig`* __string__ | Modify to add to the rabbitmq.conf file in addition to default configurations set by the operator. Modifying this property on an existing RabbitmqCluster will trigger a StatefulSet rolling restart and will cause rabbitmq downtime. For more information on this config, see https://www.rabbitmq.com/configure.html#config-file | *`advancedConfig`* __string__ | Specify any rabbitmq advanced.config configurations to apply to the cluster. For more information on advanced config, see https://www.rabbitmq.com/configure.html#advanced-config-file | *`envConfig`* __string__ | Modify to add to the rabbitmq-env.conf file. Modifying this property on an existing RabbitmqCluster will trigger a StatefulSet rolling restart and will cause rabbitmq downtime. For more information on env config, see https://www.rabbitmq.com/man/rabbitmq-env.conf.5.html +| *`erlangInetConfig`* __string__ | Erlang Inet configuration to apply to the Erlang VM running rabbit. See also: https://www.erlang.org/doc/apps/erts/inet_cfg.html |=== @@ -283,6 +284,7 @@ Settable attributes for the Service resource. | Field | Description | *`type`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#servicetype-v1-core[$$ServiceType$$]__ | Type of Service to create for the cluster. Must be one of: ClusterIP, LoadBalancer, NodePort. For more info see https://pkg.go.dev/k8s.io/api/core/v1#ServiceType | *`annotations`* __object (keys:string, values:string)__ | Annotations to add to the Service. +| *`ipFamilyPolicy`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#ipfamilypolicy-v1-core[$$IPFamilyPolicy$$]__ | IPFamilyPolicy represents the dual-stack-ness requested or required by a Service See also: https://pkg.go.dev/k8s.io/api/core/v1#IPFamilyPolicy |=== diff --git a/docs/examples/ipv6/.ci-skip b/docs/examples/ipv6/.ci-skip new file mode 100644 index 000000000..e69de29bb diff --git a/docs/examples/ipv6/README.md b/docs/examples/ipv6/README.md new file mode 100644 index 000000000..55cf36500 --- /dev/null +++ b/docs/examples/ipv6/README.md @@ -0,0 +1,10 @@ +# IPv6 example + +This example shows the required configuration to force the Erlang VM to use +IPv6. RabbitMQ relies on Erlang's INET module for network interaction. + +You can deploy this example using: + +```shell +kubectl apply -f rabbitmq.yaml +``` diff --git a/docs/examples/ipv6/rabbitmq.yaml b/docs/examples/ipv6/rabbitmq.yaml new file mode 100644 index 000000000..4d0d956d6 --- /dev/null +++ b/docs/examples/ipv6/rabbitmq.yaml @@ -0,0 +1,30 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: rabbits + labels: + app: rabbitmq +--- +apiVersion: rabbitmq.com/v1beta1 +kind: RabbitmqCluster +metadata: + name: rabbit-ipv6 + namespace: rabbits + labels: + app: rabbitmq +spec: + resources: + requests: {} + limits: {} + rabbitmq: + erlangInetConfig: | + {inet6, true}. + envConfig: | + SERVER_ADDITIONAL_ERL_ARGS="-kernel inetrc '/etc/rabbitmq/erl_inetrc' -proto_dist inet6_tcp" + RABBITMQ_CTL_ERL_ARGS="-proto_dist inet6_tcp" + additionalConfig: | + cluster_formation.k8s.host = kubernetes.default.svc.cluster.local + replicas: 3 + service: + ipFamilyPolicy: "PreferDualStack" diff --git a/go.mod b/go.mod index 5a2e99cbe..e7d9f9b31 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( k8s.io/apimachinery v0.28.2 k8s.io/client-go v0.28.1 k8s.io/klog/v2 v2.100.1 - k8s.io/utils v0.0.0-20230505201702-9f6742963106 + k8s.io/utils v0.0.0-20230726121419-3b25d923346b sigs.k8s.io/controller-runtime v0.15.1 sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20220217024943-cfd92767d28e sigs.k8s.io/controller-tools v0.13.0 diff --git a/go.sum b/go.sum index 474e3b194..cdb61e666 100644 --- a/go.sum +++ b/go.sum @@ -566,8 +566,8 @@ k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20230816210353-14e408962443 h1:CAIciCnJnSOQxPd0xvpV6JU3D4AJvnYbImPpFpO9Hnw= k8s.io/kube-openapi v0.0.0-20230816210353-14e408962443/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM= -k8s.io/utils v0.0.0-20230505201702-9f6742963106 h1:EObNQ3TW2D+WptiYXlApGNLVy0zm/JIBVY9i+M4wpAU= -k8s.io/utils v0.0.0-20230505201702-9f6742963106/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= +k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/controller-runtime v0.15.1 h1:9UvgKD4ZJGcj24vefUFgZFP3xej/3igL9BsOUTb/+4c= sigs.k8s.io/controller-runtime v0.15.1/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk= sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20220217024943-cfd92767d28e h1:Z4+OH6QT2Xy2aqyN5BjyBEGnINrFzkHlMqLCyrE0A+g= diff --git a/internal/resource/configmap.go b/internal/resource/configmap.go index ac14f2749..e2c8c0df6 100644 --- a/internal/resource/configmap.go +++ b/internal/resource/configmap.go @@ -98,7 +98,7 @@ func (builder *ServerConfigMapBuilder) Update(object client.Object) error { return err } - userConfiguration := ini.Empty(ini.LoadOptions{}) + userConfiguration := ini.Empty() userConfigurationSection := userConfiguration.Section("") if builder.Instance.TLSEnabled() { @@ -241,6 +241,7 @@ func (builder *ServerConfigMapBuilder) Update(object client.Object) error { updateProperty(configMap.Data, "advanced.config", rmqProperties.AdvancedConfig) updateProperty(configMap.Data, "rabbitmq-env.conf", rmqProperties.EnvConfig) + updateProperty(configMap.Data, "erl_inetrc", rmqProperties.ErlangInetConfig) if err := controllerutil.SetControllerReference(builder.Instance, configMap, builder.Scheme); err != nil { return fmt.Errorf("failed setting controller reference: %w", err) diff --git a/internal/resource/configmap_test.go b/internal/resource/configmap_test.go index 93d66d1d1..5254de5a6 100644 --- a/internal/resource/configmap_test.go +++ b/internal/resource/configmap_test.go @@ -183,7 +183,7 @@ var _ = Describe("GenerateServerConfigMap", func() { Expect(configMap.Data).To(HaveKeyWithValue("advanced.config", "[my-awesome-config].")) }) - It("does set data.advancedConfig when empty", func() { + It("does not set data.advancedConfig when empty", func() { instance.Spec.Rabbitmq.AdvancedConfig = "" Expect(configMapBuilder.Update(configMap)).To(Succeed()) Expect(configMap.Data).ToNot(HaveKey("advanced.config")) @@ -559,6 +559,26 @@ CONSOLE_LOG=new` Expect(configMapBuilder.Update(configMap)).To(Succeed()) Expect(configMap.Annotations).To(BeEmpty()) }) + + Context("Erlang INET configuration", func() { + It("sets erlangInetRc key", func() { + instance.Spec.Rabbitmq.ErlangInetConfig = "{any-config, is-set}." + Expect(configMapBuilder.Update(configMap)).To(Succeed()) + Expect(configMap.Data).To(HaveKeyWithValue("erl_inetrc", "{any-config, is-set}.")) + }) + + When("erlangInetRc is removed", func() { + It("deletes the key", func() { + instance.Spec.Rabbitmq.ErlangInetConfig = "any string is set, rabbit will do validation" + Expect(configMapBuilder.Update(configMap)).To(Succeed()) + Expect(configMap.Data).To(HaveKey("erl_inetrc")) + + instance.Spec.Rabbitmq.ErlangInetConfig = "" + Expect(configMapBuilder.Update(configMap)).To(Succeed()) + Expect(configMap.Data).ToNot(HaveKey("erl_inetrc")) + }) + }) + }) }) Context("UpdateMayRequireStsRecreate", func() { diff --git a/internal/resource/headless_service.go b/internal/resource/headless_service.go index c4a2cf550..3e8c86165 100644 --- a/internal/resource/headless_service.go +++ b/internal/resource/headless_service.go @@ -59,17 +59,18 @@ func (builder *HeadlessServiceBuilder) Update(object client.Object) error { { Protocol: corev1.ProtocolTCP, Port: 4369, - TargetPort: intstr.FromInt(4369), + TargetPort: intstr.FromInt32(4369), Name: "epmd", }, { Protocol: corev1.ProtocolTCP, Port: 25672, - TargetPort: intstr.FromInt(25672), + TargetPort: intstr.FromInt32(25672), Name: "cluster-rpc", // aka distribution port }, }, PublishNotReadyAddresses: true, + IPFamilyPolicy: builder.Instance.Spec.Service.IPFamilyPolicy, } if err := controllerutil.SetControllerReference(builder.Instance, service, builder.Scheme); err != nil { diff --git a/internal/resource/headless_service_test.go b/internal/resource/headless_service_test.go index 79a4e5fcd..06f7c61b8 100644 --- a/internal/resource/headless_service_test.go +++ b/internal/resource/headless_service_test.go @@ -19,6 +19,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" defaultscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/utils/ptr" ) var _ = Describe("HeadlessService", func() { @@ -170,11 +171,11 @@ var _ = Describe("HeadlessService", func() { }, }, } - err := serviceBuilder.Update(service) - Expect(err).NotTo(HaveOccurred()) }) It("sets the required Spec", func() { + Expect(serviceBuilder.Update(service)).To(Succeed()) + expectedSpec := corev1.ServiceSpec{ Type: corev1.ServiceTypeClusterIP, ClusterIP: "None", @@ -198,7 +199,38 @@ var _ = Describe("HeadlessService", func() { }, PublishNotReadyAddresses: true, } + Expect(service.Spec).To(Equal(expectedSpec)) + }) + + It("sets the IP family", func() { + dualStack := ptr.To(corev1.IPFamilyPolicyPreferDualStack) + instance.Spec.Service.IPFamilyPolicy = dualStack + Expect(serviceBuilder.Update(service)).To(Succeed()) + expectedSpec := corev1.ServiceSpec{ + Type: corev1.ServiceTypeClusterIP, + ClusterIP: "None", + Selector: map[string]string{ + "app.kubernetes.io/name": "rabbit-spec", + }, + SessionAffinity: corev1.ServiceAffinityNone, + Ports: []corev1.ServicePort{ + { + Protocol: corev1.ProtocolTCP, + Port: 4369, + TargetPort: intstr.FromInt32(4369), + Name: "epmd", + }, + { + Protocol: corev1.ProtocolTCP, + Port: 25672, + TargetPort: intstr.FromInt32(25672), + Name: "cluster-rpc", + }, + }, + PublishNotReadyAddresses: true, + IPFamilyPolicy: dualStack, + } Expect(service.Spec).To(Equal(expectedSpec)) }) }) diff --git a/internal/resource/service.go b/internal/resource/service.go index dd8776d81..80919a860 100644 --- a/internal/resource/service.go +++ b/internal/resource/service.go @@ -58,6 +58,7 @@ func (builder *ServiceBuilder) Update(object client.Object) error { service.Labels = metadata.GetLabels(builder.Instance.Name, builder.Instance.Labels) service.Spec.Type = builder.Instance.Spec.Service.Type service.Spec.Selector = metadata.LabelSelector(builder.Instance.Name) + service.Spec.IPFamilyPolicy = builder.Instance.Spec.Service.IPFamilyPolicy service.Spec.Ports = builder.updatePorts(service.Spec.Ports) diff --git a/internal/resource/service_test.go b/internal/resource/service_test.go index 543a640a2..59b703861 100644 --- a/internal/resource/service_test.go +++ b/internal/resource/service_test.go @@ -21,6 +21,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" defaultscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/utils/pointer" + "k8s.io/utils/ptr" ) var _ = Context("Services", func() { @@ -694,6 +695,31 @@ var _ = Context("Services", func() { }) }) + Context("IP family policy", func() { + var ( + svc *corev1.Service + serviceBuilder *resource.ServiceBuilder + ) + + BeforeEach(func() { + serviceBuilder = builder.Service() + instance = generateRabbitmqCluster() + + svc = &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "rabbit-service-type-update-service", + Namespace: "foo-namespace", + }, + } + }) + + It("sets the IP family policy", func() { + instance.Spec.Service.IPFamilyPolicy = ptr.To(corev1.IPFamilyPolicyPreferDualStack) + Expect(serviceBuilder.Update(svc)).To(Succeed()) + Expect(svc.Spec.IPFamilyPolicy).To(BeEquivalentTo(ptr.To("PreferDualStack"))) + }) + }) + When("Override is provided", func() { var ( svc *corev1.Service diff --git a/internal/resource/statefulset.go b/internal/resource/statefulset.go index d058889cf..cb276ae62 100644 --- a/internal/resource/statefulset.go +++ b/internal/resource/statefulset.go @@ -337,7 +337,7 @@ func sortVolumeMounts(mounts []corev1.VolumeMount) { func (builder *StatefulSetBuilder) podTemplateSpec(previousPodAnnotations map[string]string) corev1.PodTemplateSpec { // default pod annotations - defaultPodAnnotations := make(map[string]string, 0) + defaultPodAnnotations := make(map[string]string) if builder.Instance.VaultEnabled() { defaultPodAnnotations = appendVaultAnnotations(defaultPodAnnotations, builder.Instance) @@ -429,7 +429,7 @@ func (builder *StatefulSetBuilder) podTemplateSpec(previousPodAnnotations map[st appendDefaultUserSecretVolumeProjection(volumes, builder.Instance, builder.Instance.Spec.SecretBackend.ExternalSecret.Name) } - if builder.Instance.Spec.Rabbitmq.AdvancedConfig != "" || builder.Instance.Spec.Rabbitmq.EnvConfig != "" { + if builder.rabbitmqConfigurationIsSet() { volumes = append(volumes, corev1.Volume{ Name: "server-conf", VolumeSource: corev1.VolumeSource{ @@ -496,6 +496,12 @@ func (builder *StatefulSetBuilder) podTemplateSpec(previousPodAnnotations map[st }) } + if builder.Instance.Spec.Rabbitmq.ErlangInetConfig != "" { + rabbitmqContainerVolumeMounts = append(rabbitmqContainerVolumeMounts, corev1.VolumeMount{ + Name: "server-conf", MountPath: "/etc/rabbitmq/erl_inetrc", SubPath: "erl_inetrc", + }) + } + tlsSpec := builder.Instance.Spec.TLS if builder.Instance.SecretTLSEnabled() { rabbitmqContainerVolumeMounts = append(rabbitmqContainerVolumeMounts, corev1.VolumeMount{ @@ -642,6 +648,12 @@ func (builder *StatefulSetBuilder) podTemplateSpec(previousPodAnnotations map[st return podTemplateSpec } +func (builder *StatefulSetBuilder) rabbitmqConfigurationIsSet() bool { + return builder.Instance.Spec.Rabbitmq.AdvancedConfig != "" || + builder.Instance.Spec.Rabbitmq.EnvConfig != "" || + builder.Instance.Spec.Rabbitmq.ErlangInetConfig != "" +} + func defaultUserCredentialUpdater(instance *rabbitmqv1beta1.RabbitmqCluster) corev1.Container { managementURI := "http://127.0.0.1:15672" if instance.TLSEnabled() { diff --git a/internal/resource/statefulset_test.go b/internal/resource/statefulset_test.go index ff19e93c8..eb9304e6c 100644 --- a/internal/resource/statefulset_test.go +++ b/internal/resource/statefulset_test.go @@ -1140,10 +1140,11 @@ default_pass = {{ .Data.data.password }} Context("Rabbitmq container volume mounts", func() { DescribeTable("Volume mounts depending on spec configuration and '/var/lib/rabbitmq/' always mounts before '/var/lib/rabbitmq/mnesia/' ", - func(rabbitmqEnv, advancedConfig string) { + func(rabbitmqEnv, advancedConfig, erlInet string) { stsBuilder := builder.StatefulSet() stsBuilder.Instance.Spec.Rabbitmq.EnvConfig = rabbitmqEnv stsBuilder.Instance.Spec.Rabbitmq.AdvancedConfig = advancedConfig + stsBuilder.Instance.Spec.Rabbitmq.ErlangInetConfig = erlInet Expect(stsBuilder.Update(statefulSet)).To(Succeed()) expectedVolumeMounts := []corev1.VolumeMount{ @@ -1166,6 +1167,11 @@ default_pass = {{ .Data.data.password }} Name: "server-conf", MountPath: "/etc/rabbitmq/advanced.config", SubPath: "advanced.config"}) } + if erlInet != "" { + expectedVolumeMounts = append(expectedVolumeMounts, corev1.VolumeMount{ + Name: "server-conf", MountPath: "/etc/rabbitmq/erl_inetrc", SubPath: "erl_inetrc"}) + } + container := extractContainer(statefulSet.Spec.Template.Spec.Containers, "rabbitmq") Expect(container.VolumeMounts).To(ConsistOf(expectedVolumeMounts)) Expect(container.VolumeMounts[0]).To(Equal(corev1.VolumeMount{ @@ -1177,18 +1183,23 @@ default_pass = {{ .Data.data.password }} MountPath: "/var/lib/rabbitmq/mnesia/", })) }, - Entry("Both env and advanced configs are set", "rabbitmq-env-is-set", "advanced-config-is-set"), - Entry("Only env config is set", "rabbitmq-env-is-set", ""), - Entry("Only advanced config is set", "", "advanced-config-is-set"), - Entry("No configs are set", "", ""), + Entry("All env + advanced + erl-inet configs are set", "rabbitmq-env-is-set", "advanced-config-is-set", "erl-inet-rc-is-set"), + Entry("Both env and advanced configs are set", "rabbitmq-env-is-set", "advanced-config-is-set", ""), + Entry("Both advanced and erl-inet configs are set", "", "advanced-config-is-set", "erl-inet-rc-is-set"), + Entry("Both env and erl-inet configs are set", "rabbitmq-env-is-set", "", "erl-inet-rc-is-set"), + Entry("Only env config is set", "rabbitmq-env-is-set", "", ""), + Entry("Only advanced config is set", "", "advanced-config-is-set", ""), + Entry("Only erl-inet config is set", "", "", "erl-inet-rc-is-set"), + Entry("No configs are set", "", "", ""), ) }) Context("Volumes", func() { - DescribeTable("Volumes based on user configuration", func(rabbitmqEnv, advancedConfig string) { + DescribeTable("Volumes based on user configuration", func(rabbitmqEnv, advancedConfig, erlInetRc string) { stsBuilder := builder.StatefulSet() stsBuilder.Instance.Spec.Rabbitmq.EnvConfig = rabbitmqEnv stsBuilder.Instance.Spec.Rabbitmq.AdvancedConfig = advancedConfig + stsBuilder.Instance.Spec.Rabbitmq.ErlangInetConfig = erlInetRc Expect(stsBuilder.Update(statefulSet)).To(Succeed()) expectedVolumes := []corev1.Volume{ @@ -1279,7 +1290,7 @@ default_pass = {{ .Data.data.password }} }, } - if rabbitmqEnv != "" || advancedConfig != "" { + if rabbitmqEnv != "" || advancedConfig != "" || erlInetRc != "" { expectedVolumes = append(expectedVolumes, corev1.Volume{ Name: "server-conf", VolumeSource: corev1.VolumeSource{ @@ -1292,10 +1303,14 @@ default_pass = {{ .Data.data.password }} Expect(statefulSet.Spec.Template.Spec.Volumes).To(ConsistOf(expectedVolumes)) }, - Entry("Both env and advanced configs are set", "rabbitmq-env-is-set", "advanced-config-is-set"), - Entry("Only env config is set", "rabbitmq-env-is-set", ""), - Entry("Only advanced config is set", "", "advanced-config-is-set"), - Entry("No configs are set", "", ""), + Entry("All env + advanced + erl-inet configs are set", "rabbitmq-env-is-set", "advanced-config-is-set", "erl-inet-rc-is-set"), + Entry("Both env and advanced configs are set", "rabbitmq-env-is-set", "advanced-config-is-set", ""), + Entry("Both advanced and erl-inet configs are set", "", "advanced-config-is-set", "erl-inet-rc-is-set"), + Entry("Both env and erl-inet configs are set", "rabbitmq-env-is-set", "", "erl-inet-rc-is-set"), + Entry("Only env config is set", "rabbitmq-env-is-set", "", ""), + Entry("Only advanced config is set", "", "advanced-config-is-set", ""), + Entry("Only erl-inet config is set", "", "", "erl-inet-rc-is-set"), + Entry("No configs are set", "", "", ""), ) It("defines an emptyDir volume when storage == 0", func() { diff --git a/internal/status/zz_generated.deepcopy.go b/internal/status/zz_generated.deepcopy.go index 34b5ba51d..87ca68bc8 100644 --- a/internal/status/zz_generated.deepcopy.go +++ b/internal/status/zz_generated.deepcopy.go @@ -1,5 +1,4 @@ //go:build !ignore_autogenerated -// +build !ignore_autogenerated /* RabbitMQ Cluster Operator diff --git a/main.go b/main.go index 24d1be22b..42df76acf 100644 --- a/main.go +++ b/main.go @@ -105,12 +105,20 @@ func main() { LeaderElection: true, LeaderElectionNamespace: operatorNamespace, LeaderElectionID: "rabbitmq-cluster-operator-leader-election", - Namespace: operatorScopeNamespace, + // Namespace is deprecated. Advice is to use Cache.Namespaces instead } - if strings.Contains(operatorScopeNamespace, ",") { - options.Namespace = "" - options.NewCache = cache.MultiNamespacedCacheBuilder(strings.Split(operatorScopeNamespace, ",")) + if operatorScopeNamespace != "" { + if strings.Contains(operatorScopeNamespace, ",") { + namespaces := strings.Split(operatorScopeNamespace, ",") + options.Cache.Namespaces = namespaces + log.Info("limiting watch to specific namespaces for RabbitMQ resources", "namespaces", namespaces) + } else { + options.Cache = cache.Options{ + Namespaces: []string{operatorScopeNamespace}, + } + log.Info("limiting watch to one namespace", "namespace", operatorScopeNamespace) + } } if leaseDuration := getEnvInDuration("LEASE_DURATION"); leaseDuration != 0 {