diff --git a/api/core/v1beta1/openstackcontrolplane_webhook.go b/api/core/v1beta1/openstackcontrolplane_webhook.go index f4a32fce3..d9c76bcb0 100644 --- a/api/core/v1beta1/openstackcontrolplane_webhook.go +++ b/api/core/v1beta1/openstackcontrolplane_webhook.go @@ -530,7 +530,11 @@ func (r *OpenStackControlPlane) ValidateCreateServices(basePath *field.Path) (ad } if r.Spec.Rabbitmq.Enabled { - if r.Spec.Rabbitmq.Templates != nil { + if r.Spec.Rabbitmq.Templates == nil || len(*r.Spec.Rabbitmq.Templates) == 0 { + err := field.Required(basePath.Child("rabbitmq").Child("templates"), + "At least one RabbitMQ instance must be defined when rabbitmq is enabled") + errors = append(errors, err) + } else { err := common_webhook.ValidateDNS1123Label( basePath.Child("rabbitmq").Child("templates"), maps.Keys(*r.Spec.Rabbitmq.Templates), @@ -745,19 +749,24 @@ func (r *OpenStackControlPlane) ValidateUpdateServices(old OpenStackControlPlane if old.Rabbitmq.Templates == nil { old.Rabbitmq.Templates = &map[string]rabbitmqv1.RabbitMqSpecCore{} } - if r.Spec.Rabbitmq.Templates != nil { + if r.Spec.Rabbitmq.Templates == nil || len(*r.Spec.Rabbitmq.Templates) == 0 { + err := field.Required(basePath.Child("rabbitmq").Child("templates"), + "At least one RabbitMQ instance must be defined when rabbitmq is enabled") + errors = append(errors, err) + } else { err := common_webhook.ValidateDNS1123Label( basePath.Child("rabbitmq").Child("templates"), maps.Keys(*r.Spec.Rabbitmq.Templates), memcachedv1.CrMaxLengthCorrection) // omit issue with statefulset pod label "controller-revision-hash": "-" errors = append(errors, err...) - } - oldRabbitmqs := *old.Rabbitmq.Templates - for rabbitmqName, rabbitmqSpec := range *r.Spec.Rabbitmq.Templates { - if oldRabbitmq, ok := oldRabbitmqs[rabbitmqName]; ok { - warn, errs := rabbitmqSpec.ValidateUpdate(oldRabbitmq, basePath.Child("rabbitmq").Child("template").Key(rabbitmqName), r.Namespace) - warnings = append(warnings, warn...) - errors = append(errors, errs...) + + oldRabbitmqs := *old.Rabbitmq.Templates + for rabbitmqName, rabbitmqSpec := range *r.Spec.Rabbitmq.Templates { + if oldRabbitmq, ok := oldRabbitmqs[rabbitmqName]; ok { + warn, errs := rabbitmqSpec.ValidateUpdate(oldRabbitmq, basePath.Child("rabbitmq").Child("template").Key(rabbitmqName), r.Namespace) + warnings = append(warnings, warn...) + errors = append(errors, errs...) + } } } } diff --git a/api/core/v1beta1/openstackcontrolplane_webhook_test.go b/api/core/v1beta1/openstackcontrolplane_webhook_test.go index 877393361..50ef7ec45 100644 --- a/api/core/v1beta1/openstackcontrolplane_webhook_test.go +++ b/api/core/v1beta1/openstackcontrolplane_webhook_test.go @@ -942,4 +942,131 @@ var _ = Describe("OpenStackControlPlane Webhook", func() { }) }) }) + + Context("ValidateCreateServices - RabbitMQ instance requirement", func() { + var instance *OpenStackControlPlane + var basePath *field.Path + + BeforeEach(func() { + instance = &OpenStackControlPlane{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-controlplane", + Namespace: "openstack", + }, + Spec: OpenStackControlPlaneSpec{ + Rabbitmq: RabbitmqSection{ + Enabled: true, + }, + }, + } + basePath = field.NewPath("spec") + }) + + It("should fail when RabbitMQ is enabled but no instances are defined (nil templates)", func() { + instance.Spec.Rabbitmq.Templates = nil + + warnings, errs := instance.ValidateCreateServices(basePath) + Expect(warnings).To(BeEmpty()) + Expect(errs).To(HaveLen(1)) + Expect(errs[0].Type).To(Equal(field.ErrorTypeRequired)) + Expect(errs[0].Field).To(Equal("spec.rabbitmq.templates")) + Expect(errs[0].Detail).To(ContainSubstring("At least one RabbitMQ instance must be defined")) + }) + + It("should fail when RabbitMQ is enabled but no instances are defined (empty templates)", func() { + emptyTemplates := map[string]rabbitmqv1.RabbitMqSpecCore{} + instance.Spec.Rabbitmq.Templates = &emptyTemplates + + warnings, errs := instance.ValidateCreateServices(basePath) + Expect(warnings).To(BeEmpty()) + Expect(errs).To(HaveLen(1)) + Expect(errs[0].Type).To(Equal(field.ErrorTypeRequired)) + Expect(errs[0].Field).To(Equal("spec.rabbitmq.templates")) + Expect(errs[0].Detail).To(ContainSubstring("At least one RabbitMQ instance must be defined")) + }) + + It("should succeed when RabbitMQ is enabled and at least one instance is defined", func() { + templates := map[string]rabbitmqv1.RabbitMqSpecCore{ + "rabbitmq": {}, + } + instance.Spec.Rabbitmq.Templates = &templates + + warnings, errs := instance.ValidateCreateServices(basePath) + Expect(warnings).To(BeEmpty()) + Expect(errs).To(BeEmpty()) + }) + + It("should succeed when RabbitMQ is disabled and no instances are defined", func() { + instance.Spec.Rabbitmq.Enabled = false + instance.Spec.Rabbitmq.Templates = nil + + warnings, errs := instance.ValidateCreateServices(basePath) + Expect(warnings).To(BeEmpty()) + Expect(errs).To(BeEmpty()) + }) + }) + + Context("ValidateUpdateServices - RabbitMQ instance requirement", func() { + var instance *OpenStackControlPlane + var oldSpec OpenStackControlPlaneSpec + var basePath *field.Path + + BeforeEach(func() { + oldTemplates := map[string]rabbitmqv1.RabbitMqSpecCore{ + "rabbitmq": {}, + } + oldSpec = OpenStackControlPlaneSpec{ + Rabbitmq: RabbitmqSection{ + Enabled: true, + Templates: &oldTemplates, + }, + } + + instance = &OpenStackControlPlane{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-controlplane", + Namespace: "openstack", + }, + Spec: OpenStackControlPlaneSpec{ + Rabbitmq: RabbitmqSection{ + Enabled: true, + }, + }, + } + basePath = field.NewPath("spec") + }) + + It("should fail when trying to remove all RabbitMQ instances while enabled", func() { + emptyTemplates := map[string]rabbitmqv1.RabbitMqSpecCore{} + instance.Spec.Rabbitmq.Templates = &emptyTemplates + + warnings, errs := instance.ValidateUpdateServices(oldSpec, basePath) + Expect(warnings).To(BeEmpty()) + Expect(errs).To(HaveLen(1)) + Expect(errs[0].Type).To(Equal(field.ErrorTypeRequired)) + Expect(errs[0].Field).To(Equal("spec.rabbitmq.templates")) + Expect(errs[0].Detail).To(ContainSubstring("At least one RabbitMQ instance must be defined")) + }) + + It("should succeed when updating with at least one RabbitMQ instance", func() { + templates := map[string]rabbitmqv1.RabbitMqSpecCore{ + "rabbitmq": {}, + } + instance.Spec.Rabbitmq.Templates = &templates + + warnings, errs := instance.ValidateUpdateServices(oldSpec, basePath) + Expect(warnings).To(BeEmpty()) + Expect(errs).To(BeEmpty()) + }) + + It("should succeed when disabling RabbitMQ and removing all instances", func() { + instance.Spec.Rabbitmq.Enabled = false + emptyTemplates := map[string]rabbitmqv1.RabbitMqSpecCore{} + instance.Spec.Rabbitmq.Templates = &emptyTemplates + + warnings, errs := instance.ValidateUpdateServices(oldSpec, basePath) + Expect(warnings).To(BeEmpty()) + Expect(errs).To(BeEmpty()) + }) + }) })