From 568c4d845ce6283ab31392e8a39b4b60bfb255d6 Mon Sep 17 00:00:00 2001 From: Slawek Kaplonski Date: Wed, 30 Jul 2025 16:53:50 +0200 Subject: [PATCH] Add support for the neutron-fwaas plugin This patch adds "enableFwaas" flag to the Neutron CRD. Setting this flag to `True` will enable `firewall_v2` service plugin and configure OVN service provider for the FIREWALL_V2 resource if OVN mechanism driver is used. Closes: #OSPRH-15214 Signed-off-by: Slawek Kaplonski --- .../neutron.openstack.org_neutronapis.yaml | 4 + api/v1beta1/neutronapi_types.go | 5 ++ .../neutron.openstack.org_neutronapis.yaml | 4 + controllers/neutronapi_controller.go | 2 + templates/neutronapi/config/01-neutron.conf | 13 +++ test/functional/neutronapi_controller_test.go | 79 +++++++++++++++++++ 6 files changed, 107 insertions(+) diff --git a/api/bases/neutron.openstack.org_neutronapis.yaml b/api/bases/neutron.openstack.org_neutronapis.yaml index bf2fabd7..5bf0f078 100644 --- a/api/bases/neutron.openstack.org_neutronapis.yaml +++ b/api/bases/neutron.openstack.org_neutronapis.yaml @@ -89,6 +89,10 @@ spec: description: DefaultConfigOverwrite - interface to overwrite default config files like policy.yaml type: object + enableFwaas: + default: false + description: EnableFwaas - enable NeutronFwaas service plugin + type: boolean extraMounts: description: ExtraMounts containing conf files items: diff --git a/api/v1beta1/neutronapi_types.go b/api/v1beta1/neutronapi_types.go index 055ff047..e2520867 100644 --- a/api/v1beta1/neutronapi_types.go +++ b/api/v1beta1/neutronapi_types.go @@ -156,6 +156,11 @@ type NeutronAPISpecCore struct { // TopologyRef to apply the Topology defined by the associated CR referenced // by name TopologyRef *topologyv1.TopoRef `json:"topologyRef,omitempty"` + + // +kubebuilder:validation:Optional + // +kubebuilder:default=false + // EnableFwaas - enable NeutronFwaas service plugin + EnableFwaas bool `json:"enableFwaas"` } type NeutronApiTLS struct { diff --git a/config/crd/bases/neutron.openstack.org_neutronapis.yaml b/config/crd/bases/neutron.openstack.org_neutronapis.yaml index bf2fabd7..5bf0f078 100644 --- a/config/crd/bases/neutron.openstack.org_neutronapis.yaml +++ b/config/crd/bases/neutron.openstack.org_neutronapis.yaml @@ -89,6 +89,10 @@ spec: description: DefaultConfigOverwrite - interface to overwrite default config files like policy.yaml type: object + enableFwaas: + default: false + description: EnableFwaas - enable NeutronFwaas service plugin + type: boolean extraMounts: description: ExtraMounts containing conf files items: diff --git a/controllers/neutronapi_controller.go b/controllers/neutronapi_controller.go index 269029a4..32e8071e 100644 --- a/controllers/neutronapi_controller.go +++ b/controllers/neutronapi_controller.go @@ -1732,6 +1732,8 @@ func (r *NeutronAPIReconciler) generateServiceSecrets( templateParameters["OVNDB_TLS"] = instance.Spec.TLS.Ovn.Enabled() } + templateParameters["EnableFwaas"] = instance.Spec.EnableFwaas + // create httpd vhost template parameters httpdVhostConfig := map[string]interface{}{} for _, endpt := range []service.Endpoint{service.EndpointInternal, service.EndpointPublic} { diff --git a/templates/neutronapi/config/01-neutron.conf b/templates/neutronapi/config/01-neutron.conf index 6c246124..fb2949ca 100644 --- a/templates/neutronapi/config/01-neutron.conf +++ b/templates/neutronapi/config/01-neutron.conf @@ -4,9 +4,17 @@ bind_port = 9697 transport_url={{ .TransportURL }} core_plugin = {{ .CorePlugin }} {{ if .IsOVN }} + {{ if .EnableFwaas }} +service_plugins = qos,ovn-router,trunk,segments,port_forwarding,log,firewall_v2 + {{ else }} service_plugins = qos,ovn-router,trunk,segments,port_forwarding,log + {{ end }} {{ else }} + {{ if .EnableFwaas }} +service_plugins = qos,trunk,segments,port_forwarding,log,firewall_v2 + {{ else }} service_plugins = qos,trunk,segments,port_forwarding,log + {{ end }} {{ end }} dns_domain = openstackgate.local dhcp_agent_notification = false @@ -119,3 +127,8 @@ memcache_dead_retry = 30 policy_file = /etc/neutron/policy.yaml enforce_scope = True enforce_new_defaults = True + +{{ if and .EnableFwaas .IsOVN }} +[service_providers] +service_provider = FIREWALL_V2:fwaas_db:neutron_fwaas.services.firewall.service_drivers.ovn.firewall_l3_driver.OVNFwaasDriver:default +{{ end }} diff --git a/test/functional/neutronapi_controller_test.go b/test/functional/neutronapi_controller_test.go index 4f337f26..251578e0 100644 --- a/test/functional/neutronapi_controller_test.go +++ b/test/functional/neutronapi_controller_test.go @@ -868,6 +868,31 @@ func getNeutronAPIControllerSuite(ml2MechanismDrivers []string) func() { }, timeout, interval).Should(Succeed()) }) } + It("should create a Secret for 01-neutron.conf without FWaaS configuration", func() { + if isOVNEnabled { + DeferCleanup(DeleteOVNDBClusters, CreateOVNDBClusters(namespace)) + } + + keystoneAPI := keystone.CreateKeystoneAPI(namespace) + DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPI) + + secret := types.NamespacedName{ + Namespace: neutronAPIName.Namespace, + Name: fmt.Sprintf("%s-%s", neutronAPIName.Name, "config"), + } + + Eventually(func() corev1.Secret { + return th.GetSecret(secret) + }, timeout, interval).ShouldNot(BeNil()) + + data := th.GetSecret(secret).Data["01-neutron.conf"] + conf := string(data) + + // service_plugins should include firewall_v2 + Expect(conf).ShouldNot(MatchRegexp("service_plugins = .*firewall_v2.*")) + Expect(conf).ShouldNot(ContainSubstring( + "service_provider = FIREWALL_V2:fwaas_db:neutron_fwaas.services.firewall.service_drivers.ovn.firewall_l3_driver.OVNFwaasDriver:default")) + }) }) When("DB is created", func() { @@ -1904,6 +1929,60 @@ func getNeutronAPIControllerSuite(ml2MechanismDrivers []string) func() { }, ).Spec.Template.Spec.Containers[0].Env, "CONFIG_HASH", "") }) + + When("Neutron API is created with FWaaS enabled", func() { + BeforeEach(func() { + spec["enableFwaas"] = true + DeferCleanup(th.DeleteInstance, CreateNeutronAPI(neutronAPIName.Namespace, neutronAPIName.Name, spec)) + DeferCleanup(k8sClient.Delete, ctx, CreateNeutronAPISecret(namespace, SecretName)) + DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, "memcached", memcachedSpec)) + infra.SimulateMemcachedReady(memcachedName) + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + namespace, + GetNeutronAPI(neutronAPIName).Spec.DatabaseInstance, + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) + SimulateTransportURLReady(apiTransportURLName) + mariadb.SimulateMariaDBAccountCompleted(types.NamespacedName{Namespace: namespace, Name: GetNeutronAPI(neutronAPIName).Spec.DatabaseAccount}) + mariadb.SimulateMariaDBDatabaseCompleted(types.NamespacedName{Namespace: namespace, Name: neutronapi.DatabaseCRName}) + keystoneAPI := keystone.CreateKeystoneAPI(namespace) + DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPI) + }) + + It("should create a Secret for 01-neutron.conf with expected FWaaS configuration", func() { + if isOVNEnabled { + DeferCleanup(DeleteOVNDBClusters, CreateOVNDBClusters(namespace)) + } + secret := types.NamespacedName{ + Namespace: neutronAPIName.Namespace, + Name: fmt.Sprintf("%s-%s", neutronAPIName.Name, "config"), + } + + Eventually(func() corev1.Secret { + return th.GetSecret(secret) + }, timeout, interval).ShouldNot(BeNil()) + + data := th.GetSecret(secret).Data["01-neutron.conf"] + conf := string(data) + + // service_plugins should include firewall_v2 + Expect(conf).Should(MatchRegexp("service_plugins = .*firewall_v2.*")) + + if isOVNEnabled { + Expect(conf).Should(ContainSubstring( + "service_provider = FIREWALL_V2:fwaas_db:neutron_fwaas.services.firewall.service_drivers.ovn.firewall_l3_driver.OVNFwaasDriver:default")) + } else { + Expect(conf).ShouldNot(ContainSubstring( + "service_provider = FIREWALL_V2:fwaas_db:neutron_fwaas.services.firewall.service_drivers.ovn.firewall_l3_driver.OVNFwaasDriver:default")) + } + }) + + }) } }