diff --git a/api/bases/neutron.openstack.org_neutronapis.yaml b/api/bases/neutron.openstack.org_neutronapis.yaml index c0f7760c..a735be69 100644 --- a/api/bases/neutron.openstack.org_neutronapis.yaml +++ b/api/bases/neutron.openstack.org_neutronapis.yaml @@ -2052,6 +2052,14 @@ spec: default: memcached description: Memcached instance name. type: string + ml2MechanismDrivers: + default: + - ovn + description: Ml2MechanismDrivers - list of ml2 drivers to enable. + Using {"ovn"} if not set. + items: + type: string + type: array networkAttachments: description: NetworkAttachments is a list of NetworkAttachment resource names to expose the services to the given network diff --git a/api/v1beta1/neutronapi_types.go b/api/v1beta1/neutronapi_types.go index 5a49f80a..99356609 100644 --- a/api/v1beta1/neutronapi_types.go +++ b/api/v1beta1/neutronapi_types.go @@ -102,6 +102,11 @@ type NeutronAPISpecCore struct { // PreserveJobs - do not delete jobs after they finished e.g. to check logs PreserveJobs bool `json:"preserveJobs"` + // +kubebuilder:validation:Optional + // Ml2MechanismDrivers - list of ml2 drivers to enable. Using {"ovn"} if not set. + // +kubebuilder:default={"ovn"} + Ml2MechanismDrivers []string `json:"ml2MechanismDrivers"` + // +kubebuilder:validation:Optional // CustomServiceConfig - customize the service config using this parameter to change service defaults, // or overwrite rendered information using raw OpenStack config format. The content gets added to @@ -266,6 +271,16 @@ func (instance NeutronAPI) RbacResourceName() string { return "neutron-" + instance.Name } +func (instance NeutronAPI) IsOVNEnabled() bool { + for _, driver := range instance.Spec.Ml2MechanismDrivers { + // TODO: use const + if driver == "ovn" { + return true + } + } + return false +} + // SetupDefaults - initializes any CRD field defaults based on environment variables (the defaulting mechanism itself is implemented via webhooks) func SetupDefaults() { // Acquire environmental defaults and initialize Neutron defaults with them diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 7ed36ae3..7765a3d0 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -156,6 +156,11 @@ func (in *NeutronAPISpecCore) DeepCopyInto(out *NeutronAPISpecCore) { (*out)[key] = val } } + if in.Ml2MechanismDrivers != nil { + in, out := &in.Ml2MechanismDrivers, &out.Ml2MechanismDrivers + *out = make([]string, len(*in)) + copy(*out, *in) + } if in.DefaultConfigOverwrite != nil { in, out := &in.DefaultConfigOverwrite, &out.DefaultConfigOverwrite *out = make(map[string]string, len(*in)) diff --git a/config/crd/bases/neutron.openstack.org_neutronapis.yaml b/config/crd/bases/neutron.openstack.org_neutronapis.yaml index c0f7760c..a735be69 100644 --- a/config/crd/bases/neutron.openstack.org_neutronapis.yaml +++ b/config/crd/bases/neutron.openstack.org_neutronapis.yaml @@ -2052,6 +2052,14 @@ spec: default: memcached description: Memcached instance name. type: string + ml2MechanismDrivers: + default: + - ovn + description: Ml2MechanismDrivers - list of ml2 drivers to enable. + Using {"ovn"} if not set. + items: + type: string + type: array networkAttachments: description: NetworkAttachments is a list of NetworkAttachment resource names to expose the services to the given network diff --git a/config/samples/neutron_v1beta1_neutronapi_openvswitch.yaml b/config/samples/neutron_v1beta1_neutronapi_openvswitch.yaml new file mode 100644 index 00000000..bd7caa36 --- /dev/null +++ b/config/samples/neutron_v1beta1_neutronapi_openvswitch.yaml @@ -0,0 +1,15 @@ +apiVersion: neutron.openstack.org/v1beta1 +kind: NeutronAPI +metadata: + name: neutron + namespace: openstack +spec: + serviceUser: neutron + databaseInstance: openstack + databaseAccount: neutron + rabbitMqClusterName: rabbitmq + memcachedInstance: memcached + preserveJobs: false + secret: neutron-secret + ml2MechanismDrivers: + - openvswitch diff --git a/controllers/neutronapi_controller.go b/controllers/neutronapi_controller.go index 0bfcb2f5..d026c3b5 100644 --- a/controllers/neutronapi_controller.go +++ b/controllers/neutronapi_controller.go @@ -19,6 +19,7 @@ package controllers import ( "context" "fmt" + "strings" "time" "github.com/go-logr/logr" @@ -921,6 +922,12 @@ func (r *NeutronAPIReconciler) reconcileNormal(ctx context.Context, instance *ne return ctrl.Result{}, err } + err = r.reconcileExternalOVNSecrets(ctx, helper, instance, &secretVars) + if err != nil { + Log.Error(err, "Failed to reconcile external OVN Secrets") + return ctrl.Result{}, err + } + // // TODO check when/if Init, Update, or Upgrade should/could be skipped // @@ -1111,12 +1118,19 @@ func getDhcpAgentSecretName(instance *neutronv1beta1.NeutronAPI) string { return getExternalSecretName(instance, "dhcp-agent") } -func (r *NeutronAPIReconciler) reconcileExternalMetadataAgentSecret( +func (r *NeutronAPIReconciler) reconcileExternalOVNMetadataAgentSecret( ctx context.Context, h *helper.Helper, instance *neutronv1beta1.NeutronAPI, envVars *map[string]env.Setter, ) error { + if !instance.IsOVNEnabled() { + err := r.deleteExternalSecret(ctx, h, instance, getMetadataAgentSecretName(instance)) + if err != nil { + return fmt.Errorf("failed to delete Neutron Metadata Agent external Secret: %w", err) + } + return nil + } sbCluster, err := ovnclient.GetDBClusterByType(ctx, h, instance.Namespace, map[string]string{}, ovnclient.SBDBType) if err != nil { err = r.deleteExternalSecret(ctx, h, instance, getMetadataAgentSecretName(instance)) @@ -1135,7 +1149,7 @@ func (r *NeutronAPIReconciler) reconcileExternalMetadataAgentSecret( return nil } - err = r.ensureExternalMetadataAgentSecret(ctx, h, instance, sbEndpoint, envVars) + err = r.ensureExternalOVNMetadataAgentSecret(ctx, h, instance, sbEndpoint, envVars) if err != nil { return fmt.Errorf("failed to ensure Neutron Metadata Agent external Secret: %w", err) } @@ -1148,6 +1162,13 @@ func (r *NeutronAPIReconciler) reconcileExternalOVNAgentSecret( instance *neutronv1beta1.NeutronAPI, envVars *map[string]env.Setter, ) error { + if !instance.IsOVNEnabled() { + err := r.deleteExternalSecret(ctx, h, instance, getOVNAgentSecretName(instance)) + if err != nil { + return fmt.Errorf("failed to delete Neutron OVN Agent external Secret: %w", err) + } + return nil + } nbCluster, err := ovnclient.GetDBClusterByType(ctx, h, instance.Namespace, map[string]string{}, ovnclient.NBDBType) if err != nil { err = r.deleteExternalSecret(ctx, h, instance, getOVNAgentSecretName(instance)) @@ -1258,6 +1279,7 @@ func (r *NeutronAPIReconciler) reconcileExternalDhcpAgentSecret( } // TODO(ihar) - is there any hashing mechanism for EDP config? do we trigger deploy somehow? +// NOTE(ihar): Add config reconciliation code for any other services here or below func (r *NeutronAPIReconciler) reconcileExternalSecrets( ctx context.Context, h *helper.Helper, @@ -1266,15 +1288,7 @@ func (r *NeutronAPIReconciler) reconcileExternalSecrets( ) error { Log := r.GetLogger(ctx) // Generate one Secret per external service - err := r.reconcileExternalMetadataAgentSecret(ctx, h, instance, envVars) - if err != nil { - return fmt.Errorf("failed to reconcile Neutron Metadata Agent external Secret: %w", err) - } - err = r.reconcileExternalOVNAgentSecret(ctx, h, instance, envVars) - if err != nil { - return fmt.Errorf("failed to reconcile Neutron OVN Agent external Secret: %w", err) - } - err = r.reconcileExternalSriovAgentSecret(ctx, h, instance, envVars) + err := r.reconcileExternalSriovAgentSecret(ctx, h, instance, envVars) if err != nil { return fmt.Errorf("failed to reconcile Neutron SR-IOV Agent external Secret: %w", err) } @@ -1282,11 +1296,30 @@ func (r *NeutronAPIReconciler) reconcileExternalSecrets( if err != nil { return fmt.Errorf("failed to reconcile Neutron DHCP Agent external Secret: %w", err) } - // NOTE(ihar): Add config reconciliation code for any other services here Log.Info(fmt.Sprintf("Reconciled external secrets for %s", instance.Name)) return nil } +func (r *NeutronAPIReconciler) reconcileExternalOVNSecrets( + ctx context.Context, + h *helper.Helper, + instance *neutronv1beta1.NeutronAPI, + envVars *map[string]env.Setter, +) error { + Log := r.GetLogger(ctx) + // Generate one Secret per external service + err := r.reconcileExternalOVNMetadataAgentSecret(ctx, h, instance, envVars) + if err != nil { + return fmt.Errorf("failed to reconcile Neutron Metadata Agent external Secret: %w", err) + } + err = r.reconcileExternalOVNAgentSecret(ctx, h, instance, envVars) + if err != nil { + return fmt.Errorf("failed to reconcile Neutron OVN Agent external Secret: %w", err) + } + Log.Info(fmt.Sprintf("Reconciled external OVN secrets for %s", instance.Name)) + return nil +} + // TODO(ihar) this function could live in lib-common func (r *NeutronAPIReconciler) deleteExternalSecret( ctx context.Context, @@ -1346,7 +1379,7 @@ func (r *NeutronAPIReconciler) ensureExternalSecret( return nil } -func (r *NeutronAPIReconciler) ensureExternalMetadataAgentSecret( +func (r *NeutronAPIReconciler) ensureExternalOVNMetadataAgentSecret( ctx context.Context, h *helper.Helper, instance *neutronv1beta1.NeutronAPI, @@ -1431,23 +1464,6 @@ func (r *NeutronAPIReconciler) generateServiceSecrets( // Create/update secrets from templates cmLabels := labels.GetLabels(instance, labels.GetGroupLabel(neutronapi.ServiceName), map[string]string{}) - nbCluster, err := ovnclient.GetDBClusterByType(ctx, h, instance.Namespace, map[string]string{}, ovnclient.NBDBType) - if err != nil { - return err - } - nbEndpoint, err := nbCluster.GetInternalEndpoint() - if err != nil { - return err - } - - sbCluster, err := ovnclient.GetDBClusterByType(ctx, h, instance.Namespace, map[string]string{}, ovnclient.SBDBType) - if err != nil { - return err - } - sbEndpoint, err := sbCluster.GetInternalEndpoint() - if err != nil { - return err - } var tlsCfg *tls.Service if instance.Spec.TLS.Ca.CaBundleSecretName != "" { tlsCfg = &tls.Service{} @@ -1513,11 +1529,33 @@ func (r *NeutronAPIReconciler) generateServiceSecrets( templateParameters["DbUser"] = databaseAccount.Spec.UserName templateParameters["DbPassword"] = string(dbSecret.Data[mariadbv1.DatabasePasswordSelector]) templateParameters["Db"] = neutronapi.Database + // TODO: add join func to template library? + templateParameters["Ml2MechanismDrivers"] = strings.Join(instance.Spec.Ml2MechanismDrivers, ",") // OVN - templateParameters["NBConnection"] = nbEndpoint - templateParameters["SBConnection"] = sbEndpoint - templateParameters["OVNDB_TLS"] = instance.Spec.TLS.Ovn.Enabled() + templateParameters["IsOVN"] = instance.IsOVNEnabled() + if instance.IsOVNEnabled() { + nbCluster, err := ovnclient.GetDBClusterByType(ctx, h, instance.Namespace, map[string]string{}, ovnclient.NBDBType) + if err != nil { + return err + } + nbEndpoint, err := nbCluster.GetInternalEndpoint() + if err != nil { + return err + } + + sbCluster, err := ovnclient.GetDBClusterByType(ctx, h, instance.Namespace, map[string]string{}, ovnclient.SBDBType) + if err != nil { + return err + } + sbEndpoint, err := sbCluster.GetInternalEndpoint() + if err != nil { + return err + } + templateParameters["NBConnection"] = nbEndpoint + templateParameters["SBConnection"] = sbEndpoint + templateParameters["OVNDB_TLS"] = instance.Spec.TLS.Ovn.Enabled() + } // create httpd vhost template parameters httpdVhostConfig := map[string]interface{}{} diff --git a/pkg/neutronapi/deployment.go b/pkg/neutronapi/deployment.go index c65d21c6..e5649fad 100644 --- a/pkg/neutronapi/deployment.go +++ b/pkg/neutronapi/deployment.go @@ -114,7 +114,7 @@ func Deployment( } } - if instance.Spec.TLS.Ovn.Enabled() { + if instance.IsOVNEnabled() && instance.Spec.TLS.Ovn.Enabled() { svc := tls.Service{ SecretName: *instance.Spec.TLS.Ovn.SecretName, CaMount: ptr.To("/var/lib/config-data/tls/certs/ovndbca.crt"), diff --git a/templates/neutronapi/config/01-neutron.conf b/templates/neutronapi/config/01-neutron.conf index 0154326a..23501985 100644 --- a/templates/neutronapi/config/01-neutron.conf +++ b/templates/neutronapi/config/01-neutron.conf @@ -3,7 +3,11 @@ bind_host = localhost bind_port = 9697 transport_url={{ .TransportURL }} core_plugin = neutron.plugins.ml2.plugin.Ml2Plugin +{{ if .IsOVN }} service_plugins = qos,ovn-router,trunk,segments,port_forwarding,log +{{ else }} +service_plugins = qos,trunk,segments,port_forwarding,log +{{ end }} dns_domain = openstackgate.local dhcp_agent_notification = false api_workers = 2 @@ -16,7 +20,7 @@ connection=mysql+pymysql://{{ .DbUser }}:{{ .DbPassword }}@{{ .DbHost }}/{{ .Db mysql_wsrep_sync_wait = 1 [ml2] -mechanism_drivers = ovn +mechanism_drivers = {{ .Ml2MechanismDrivers }} type_drivers = local,flat,vlan,geneve,vxlan tenant_network_types = geneve,vxlan,vlan,flat extension_drivers = qos,port_security,dns_domain_ports @@ -31,6 +35,7 @@ network_vlan_ranges = datacentre [ml2_type_vxlan] vni_ranges = 1:65536 +{{ if .IsOVN }} [ovn] ovn_nb_connection = {{ .NBConnection }} ovn_sb_connection = {{ .SBConnection }} @@ -44,6 +49,7 @@ ovn_sb_private_key = /etc/pki/tls/private/ovndb.key ovn_sb_certificate = /etc/pki/tls/certs/ovndb.crt ovn_sb_ca_cert = /etc/pki/tls/certs/ovndbca.crt {{- end }} +{{- end }} [keystone_authtoken] www_authenticate_uri = {{ .KeystonePublicURL }} diff --git a/test/functional/neutronapi_controller_test.go b/test/functional/neutronapi_controller_test.go index 39a7ace7..c26e4646 100644 --- a/test/functional/neutronapi_controller_test.go +++ b/test/functional/neutronapi_controller_test.go @@ -48,1350 +48,1409 @@ import ( "github.com/openstack-k8s-operators/neutron-operator/pkg/neutronapi" ) -var _ = Describe("NeutronAPI controller", func() { - - var secret *corev1.Secret - var apiTransportURLName types.NamespacedName - var neutronAPIName types.NamespacedName - var memcachedSpec memcachedv1.MemcachedSpec - var memcachedName types.NamespacedName - var name string - var spec map[string]interface{} - var caBundleSecretName types.NamespacedName - var internalCertSecretName types.NamespacedName - var publicCertSecretName types.NamespacedName - var ovnDbCertSecretName types.NamespacedName - - BeforeEach(func() { - name = fmt.Sprintf("neutron-%s", uuid.New().String()) - apiTransportURLName = types.NamespacedName{ - Namespace: namespace, - Name: name + "-neutron-transport", +func getNeutronAPIControllerSuite(ml2MechanismDrivers []string) func() { + isOVNEnabled := len(ml2MechanismDrivers) == 0 + for _, v := range ml2MechanismDrivers { + if v == "ovn" { + isOVNEnabled = true } + } + return func() { + var secret *corev1.Secret + var apiTransportURLName types.NamespacedName + var neutronAPIName types.NamespacedName + var memcachedSpec memcachedv1.MemcachedSpec + var memcachedName types.NamespacedName + var name string + var spec map[string]interface{} + var caBundleSecretName types.NamespacedName + var internalCertSecretName types.NamespacedName + var publicCertSecretName types.NamespacedName + var ovnDbCertSecretName types.NamespacedName - spec = GetDefaultNeutronAPISpec() + BeforeEach(func() { + name = fmt.Sprintf("neutron-%s", uuid.New().String()) + apiTransportURLName = types.NamespacedName{ + Namespace: namespace, + Name: name + "-neutron-transport", + } - neutronAPIName = types.NamespacedName{ - Namespace: namespace, - Name: name, - } - memcachedSpec = memcachedv1.MemcachedSpec{ - MemcachedSpecCore: memcachedv1.MemcachedSpecCore{ - Replicas: ptr.To(int32(3)), - }, - } - memcachedName = types.NamespacedName{ - Name: "memcached", - Namespace: namespace, - } - caBundleSecretName = types.NamespacedName{ - Name: CABundleSecretName, - Namespace: namespace, - } - internalCertSecretName = types.NamespacedName{ - Name: InternalCertSecretName, - Namespace: namespace, - } - publicCertSecretName = types.NamespacedName{ - Name: PublicCertSecretName, - Namespace: namespace, - } - ovnDbCertSecretName = types.NamespacedName{ - Name: OVNDbCertSecretName, - Namespace: namespace, - } - }) + spec = GetDefaultNeutronAPISpec() + if len(ml2MechanismDrivers) > 0 { + spec["ml2MechanismDrivers"] = ml2MechanismDrivers + } - When("A NeutronAPI instance is created", func() { - BeforeEach(func() { - DeferCleanup(th.DeleteInstance, CreateNeutronAPI(neutronAPIName.Namespace, neutronAPIName.Name, spec)) + neutronAPIName = types.NamespacedName{ + Namespace: namespace, + Name: name, + } + memcachedSpec = memcachedv1.MemcachedSpec{ + MemcachedSpecCore: memcachedv1.MemcachedSpecCore{ + Replicas: ptr.To(int32(3)), + }, + } + memcachedName = types.NamespacedName{ + Name: "memcached", + Namespace: namespace, + } + caBundleSecretName = types.NamespacedName{ + Name: CABundleSecretName, + Namespace: namespace, + } + internalCertSecretName = types.NamespacedName{ + Name: InternalCertSecretName, + Namespace: namespace, + } + publicCertSecretName = types.NamespacedName{ + Name: PublicCertSecretName, + Namespace: namespace, + } + ovnDbCertSecretName = types.NamespacedName{ + Name: OVNDbCertSecretName, + Namespace: namespace, + } }) - It("should have the Spec fields initialized", func() { - NeutronAPI := GetNeutronAPI(neutronAPIName) - Expect(NeutronAPI.Spec.DatabaseInstance).Should(Equal("test-neutron-db-instance")) - Expect(NeutronAPI.Spec.DatabaseAccount).Should(Equal("neutron")) - Expect(NeutronAPI.Spec.RabbitMqClusterName).Should(Equal("rabbitmq")) - Expect(NeutronAPI.Spec.MemcachedInstance).Should(Equal("memcached")) - Expect(*(NeutronAPI.Spec.Replicas)).Should(Equal(int32(1))) - Expect(NeutronAPI.Spec.ServiceUser).Should(Equal("neutron")) - }) + When("A NeutronAPI instance is created", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateNeutronAPI(neutronAPIName.Namespace, neutronAPIName.Name, spec)) + }) - It("should have the Status fields initialized", func() { - NeutronAPI := GetNeutronAPI(neutronAPIName) - Expect(NeutronAPI.Status.Hash).To(BeEmpty()) - Expect(NeutronAPI.Status.DatabaseHostname).To(Equal("")) - Expect(NeutronAPI.Status.TransportURLSecret).To(Equal("")) - Expect(NeutronAPI.Status.ReadyCount).To(Equal(int32(0))) + It("should have the Spec fields initialized", func() { + NeutronAPI := GetNeutronAPI(neutronAPIName) + Expect(NeutronAPI.Spec.DatabaseInstance).Should(Equal("test-neutron-db-instance")) + Expect(NeutronAPI.Spec.DatabaseAccount).Should(Equal("neutron")) + Expect(NeutronAPI.Spec.RabbitMqClusterName).Should(Equal("rabbitmq")) + Expect(NeutronAPI.Spec.MemcachedInstance).Should(Equal("memcached")) + Expect(*(NeutronAPI.Spec.Replicas)).Should(Equal(int32(1))) + Expect(NeutronAPI.Spec.ServiceUser).Should(Equal("neutron")) + }) + + It("should have the Status fields initialized", func() { + NeutronAPI := GetNeutronAPI(neutronAPIName) + Expect(NeutronAPI.Status.Hash).To(BeEmpty()) + Expect(NeutronAPI.Status.DatabaseHostname).To(Equal("")) + Expect(NeutronAPI.Status.TransportURLSecret).To(Equal("")) + Expect(NeutronAPI.Status.ReadyCount).To(Equal(int32(0))) + }) + + It("should have Unknown Conditions initialized as transporturl not created", func() { + for _, cond := range []condition.Type{ + condition.InputReadyCondition, + condition.MemcachedReadyCondition, + } { + th.ExpectCondition( + neutronAPIName, + ConditionGetterFunc(NeutronAPIConditionGetter), + cond, + corev1.ConditionUnknown, + ) + } + }) + + It("should have a finalizer", func() { + // the reconciler loop adds the finalizer so we have to wait for + // it to run + Eventually(func() []string { + return GetNeutronAPI(neutronAPIName).Finalizers + }, timeout, interval).Should(ContainElement("openstack.org/neutronapi")) + }) + + It("should not create a secret", func() { + secret := types.NamespacedName{ + Namespace: neutronAPIName.Namespace, + Name: fmt.Sprintf("%s-%s", neutronAPIName.Name, "config"), + } + th.AssertSecretDoesNotExist(secret) + }) }) - It("should have Unknown Conditions initialized as transporturl not created", func() { - for _, cond := range []condition.Type{ - condition.InputReadyCondition, - condition.MemcachedReadyCondition, - } { + When("an unrelated secret is provided", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateNeutronAPI(neutronAPIName.Namespace, neutronAPIName.Name, spec)) + }) + + It("should remain in a state of waiting for the proper secret", func() { + SimulateTransportURLReady(apiTransportURLName) + secret = &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "an-unrelated-secret", + Namespace: namespace, + }, + } + Expect(k8sClient.Create(ctx, secret)).Should(Succeed()) + DeferCleanup(k8sClient.Delete, ctx, secret) + + th.ExpectCondition( + neutronAPIName, + ConditionGetterFunc(NeutronAPIConditionGetter), + condition.InputReadyCondition, + corev1.ConditionFalse, + ) th.ExpectCondition( neutronAPIName, ConditionGetterFunc(NeutronAPIConditionGetter), - cond, + condition.RabbitMqTransportURLReadyCondition, corev1.ConditionUnknown, ) - } - }) - It("should have a finalizer", func() { - // the reconciler loop adds the finalizer so we have to wait for - // it to run - Eventually(func() []string { - return GetNeutronAPI(neutronAPIName).Finalizers - }, timeout, interval).Should(ContainElement("openstack.org/neutronapi")) + }) + It("should not create a secret", func() { + secret := types.NamespacedName{ + Namespace: neutronAPIName.Namespace, + Name: fmt.Sprintf("%s-%s", neutronAPIName.Name, "config"), + } + th.AssertSecretDoesNotExist(secret) + }) }) - It("should not create a secret", func() { - secret := types.NamespacedName{ - Namespace: neutronAPIName.Namespace, - Name: fmt.Sprintf("%s-%s", neutronAPIName.Name, "config"), - } - th.AssertSecretDoesNotExist(secret) - }) - }) + When("the proper secret is provided, TransportURL and Memcached are Created", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateNeutronAPI(neutronAPIName.Namespace, neutronAPIName.Name, spec)) + DeferCleanup(DeleteOVNDBClusters, CreateOVNDBClusters(namespace)) - When("an unrelated secret is provided", func() { - BeforeEach(func() { - DeferCleanup(th.DeleteInstance, CreateNeutronAPI(neutronAPIName.Namespace, neutronAPIName.Name, spec)) - }) + secret = &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: SecretName, + Namespace: namespace, + }, + Data: map[string][]byte{ + "NeutronPassword": []byte("12345678"), + "transport_url": []byte("rabbit://user@svc:1234"), + }, + } + Expect(k8sClient.Create(ctx, secret)).Should(Succeed()) + SimulateTransportURLReady(apiTransportURLName) + DeferCleanup(k8sClient.Delete, ctx, secret) + DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, "memcached", memcachedSpec)) + infra.SimulateMemcachedReady(memcachedName) - It("should remain in a state of waiting for the proper secret", func() { - SimulateTransportURLReady(apiTransportURLName) - secret = &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "an-unrelated-secret", - Namespace: namespace, - }, - } - Expect(k8sClient.Create(ctx, secret)).Should(Succeed()) - DeferCleanup(k8sClient.Delete, ctx, secret) + }) + It("should be in a state of having the input ready", func() { - th.ExpectCondition( - neutronAPIName, - ConditionGetterFunc(NeutronAPIConditionGetter), - condition.InputReadyCondition, - corev1.ConditionFalse, - ) - th.ExpectCondition( - neutronAPIName, - ConditionGetterFunc(NeutronAPIConditionGetter), - condition.RabbitMqTransportURLReadyCondition, - corev1.ConditionUnknown, - ) + th.ExpectCondition( + neutronAPIName, + ConditionGetterFunc(NeutronAPIConditionGetter), + condition.InputReadyCondition, + corev1.ConditionTrue, + ) + }) - }) - It("should not create a secret", func() { - secret := types.NamespacedName{ - Namespace: neutronAPIName.Namespace, - Name: fmt.Sprintf("%s-%s", neutronAPIName.Name, "config"), - } - th.AssertSecretDoesNotExist(secret) - }) - }) + It("should be in a state of having the TransportURL ready", func() { + th.ExpectCondition( + neutronAPIName, + ConditionGetterFunc(NeutronAPIConditionGetter), + condition.RabbitMqTransportURLReadyCondition, + corev1.ConditionTrue, + ) + }) + It("should be in a state of having the Memcached ready", func() { + th.ExpectCondition( + neutronAPIName, + ConditionGetterFunc(NeutronAPIConditionGetter), + condition.MemcachedReadyCondition, + corev1.ConditionTrue, + ) + }) + It("should not create a secret", func() { + secret := types.NamespacedName{ + Namespace: neutronAPIName.Namespace, + Name: fmt.Sprintf("%s-%s", neutronAPIName.Name, "config"), + } + th.AssertSecretDoesNotExist(secret) + }) - When("the proper secret is provided, TransportURL and Memcached are Created", func() { - BeforeEach(func() { - DeferCleanup(th.DeleteInstance, CreateNeutronAPI(neutronAPIName.Namespace, neutronAPIName.Name, spec)) - DeferCleanup(DeleteOVNDBClusters, CreateOVNDBClusters(namespace)) + It("should create an external SR-IOV Agent Secret with expected transport_url set", func() { + externalSriovAgentSecret := types.NamespacedName{ + Namespace: neutronAPIName.Namespace, + Name: fmt.Sprintf("%s-sriov-agent-neutron-config", neutronAPIName.Name), + } - secret = &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: SecretName, - Namespace: namespace, - }, - Data: map[string][]byte{ - "NeutronPassword": []byte("12345678"), - "transport_url": []byte("rabbit://user@svc:1234"), - }, - } - Expect(k8sClient.Create(ctx, secret)).Should(Succeed()) - SimulateTransportURLReady(apiTransportURLName) - DeferCleanup(k8sClient.Delete, ctx, secret) - DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, "memcached", memcachedSpec)) - infra.SimulateMemcachedReady(memcachedName) + Eventually(func() corev1.Secret { + return th.GetSecret(externalSriovAgentSecret) + }, timeout, interval).ShouldNot(BeNil()) - }) - It("should be in a state of having the input ready", func() { + transportURL := "rabbit://user@svc:1234" - th.ExpectCondition( - neutronAPIName, - ConditionGetterFunc(NeutronAPIConditionGetter), - condition.InputReadyCondition, - corev1.ConditionTrue, - ) - }) + Expect(string(th.GetSecret(externalSriovAgentSecret).Data[neutronapi.NeutronSriovAgentSecretKey])).Should( + ContainSubstring("transport_url = %s", transportURL)) - It("should be in a state of having the TransportURL ready", func() { - th.ExpectCondition( - neutronAPIName, - ConditionGetterFunc(NeutronAPIConditionGetter), - condition.RabbitMqTransportURLReadyCondition, - corev1.ConditionTrue, - ) - }) - It("should be in a state of having the Memcached ready", func() { - th.ExpectCondition( - neutronAPIName, - ConditionGetterFunc(NeutronAPIConditionGetter), - condition.MemcachedReadyCondition, - corev1.ConditionTrue, - ) - }) - It("should not create a secret", func() { - secret := types.NamespacedName{ - Namespace: neutronAPIName.Namespace, - Name: fmt.Sprintf("%s-%s", neutronAPIName.Name, "config"), - } - th.AssertSecretDoesNotExist(secret) - }) + Eventually(func(g Gomega) { + NeutronAPI := GetNeutronAPI(neutronAPIName) + g.Expect(NeutronAPI.Status.Hash[externalSriovAgentSecret.Name]).NotTo(BeEmpty()) + }, timeout, interval).Should(Succeed()) + }) - It("should create an external SR-IOV Agent Secret with expected transport_url set", func() { - externalSriovAgentSecret := types.NamespacedName{ - Namespace: neutronAPIName.Namespace, - Name: fmt.Sprintf("%s-sriov-agent-neutron-config", neutronAPIName.Name), - } + It("should create an external DHCP Agent Secret with expected transport_url set", func() { + externalDhcpAgentSecret := types.NamespacedName{ + Namespace: neutronAPIName.Namespace, + Name: fmt.Sprintf("%s-dhcp-agent-neutron-config", neutronAPIName.Name), + } - Eventually(func() corev1.Secret { - return th.GetSecret(externalSriovAgentSecret) - }, timeout, interval).ShouldNot(BeNil()) + Eventually(func() corev1.Secret { + return th.GetSecret(externalDhcpAgentSecret) + }, timeout, interval).ShouldNot(BeNil()) - transportURL := "rabbit://user@svc:1234" + transportURL := "rabbit://user@svc:1234" - Expect(string(th.GetSecret(externalSriovAgentSecret).Data[neutronapi.NeutronSriovAgentSecretKey])).Should( - ContainSubstring("transport_url = %s", transportURL)) + Expect(string(th.GetSecret(externalDhcpAgentSecret).Data[neutronapi.NeutronDhcpAgentSecretKey])).Should( + ContainSubstring("transport_url = %s", transportURL)) - Eventually(func(g Gomega) { - NeutronAPI := GetNeutronAPI(neutronAPIName) - g.Expect(NeutronAPI.Status.Hash[externalSriovAgentSecret.Name]).NotTo(BeEmpty()) - }, timeout, interval).Should(Succeed()) + Eventually(func(g Gomega) { + NeutronAPI := GetNeutronAPI(neutronAPIName) + g.Expect(NeutronAPI.Status.Hash[externalDhcpAgentSecret.Name]).NotTo(BeEmpty()) + }, timeout, interval).Should(Succeed()) + }) }) - It("should create an external DHCP Agent Secret with expected transport_url set", func() { - externalDhcpAgentSecret := types.NamespacedName{ - Namespace: neutronAPIName.Namespace, - Name: fmt.Sprintf("%s-dhcp-agent-neutron-config", neutronAPIName.Name), - } - - Eventually(func() corev1.Secret { - return th.GetSecret(externalDhcpAgentSecret) - }, timeout, interval).ShouldNot(BeNil()) - - transportURL := "rabbit://user@svc:1234" + When("Memcached is available", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateNeutronAPI(neutronAPIName.Namespace, neutronAPIName.Name, spec)) - Expect(string(th.GetSecret(externalDhcpAgentSecret).Data[neutronapi.NeutronDhcpAgentSecretKey])).Should( - ContainSubstring("transport_url = %s", transportURL)) - - Eventually(func(g Gomega) { - NeutronAPI := GetNeutronAPI(neutronAPIName) - g.Expect(NeutronAPI.Status.Hash[externalDhcpAgentSecret.Name]).NotTo(BeEmpty()) - }, timeout, interval).Should(Succeed()) + secret = &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: SecretName, + Namespace: namespace, + }, + Data: map[string][]byte{ + "NeutronPassword": []byte("12345678"), + "transport_url": []byte("rabbit://user@svc:1234"), + }, + } + Expect(k8sClient.Create(ctx, secret)).Should(Succeed()) + DeferCleanup(k8sClient.Delete, ctx, secret) + DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, "memcached", memcachedSpec)) + infra.SimulateMemcachedReady(memcachedName) + SimulateTransportURLReady(apiTransportURLName) + }) + + It("should have memcached ready", func() { + th.ExpectCondition( + neutronAPIName, + ConditionGetterFunc(NeutronAPIConditionGetter), + condition.MemcachedReadyCondition, + corev1.ConditionTrue, + ) + }) }) - }) - When("Memcached is available", func() { - BeforeEach(func() { - DeferCleanup(th.DeleteInstance, CreateNeutronAPI(neutronAPIName.Namespace, neutronAPIName.Name, spec)) - - secret = &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: SecretName, - Namespace: namespace, - }, - Data: map[string][]byte{ - "NeutronPassword": []byte("12345678"), - "transport_url": []byte("rabbit://user@svc:1234"), - }, - } - Expect(k8sClient.Create(ctx, secret)).Should(Succeed()) - DeferCleanup(k8sClient.Delete, ctx, secret) - DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, "memcached", memcachedSpec)) - infra.SimulateMemcachedReady(memcachedName) - SimulateTransportURLReady(apiTransportURLName) + When("OVNDBCluster instance is not available", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateNeutronAPI(neutronAPIName.Namespace, neutronAPIName.Name, spec)) + DeferCleanup(k8sClient.Delete, ctx, CreateNeutronAPISecret(namespace, SecretName)) + SimulateTransportURLReady(apiTransportURLName) + }) + It("should not create a secret", func() { + secret := types.NamespacedName{ + Namespace: neutronAPIName.Namespace, + Name: fmt.Sprintf("%s-%s", neutronAPIName.Name, "config"), + } + th.AssertSecretDoesNotExist(secret) + }) }) - It("should have memcached ready", func() { - th.ExpectCondition( - neutronAPIName, - ConditionGetterFunc(NeutronAPIConditionGetter), - condition.MemcachedReadyCondition, - corev1.ConditionTrue, - ) + When("keystoneAPI instance is not available", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateNeutronAPI(neutronAPIName.Namespace, neutronAPIName.Name, spec)) + DeferCleanup(k8sClient.Delete, ctx, CreateNeutronAPISecret(namespace, SecretName)) + SimulateTransportURLReady(apiTransportURLName) + DeferCleanup(DeleteOVNDBClusters, CreateOVNDBClusters(namespace)) + }) + It("should not create a secret", func() { + secret := types.NamespacedName{ + Namespace: neutronAPIName.Namespace, + Name: fmt.Sprintf("%s-%s", neutronAPIName.Name, "config"), + } + th.AssertSecretDoesNotExist(secret) + }) }) - }) - When("OVNDBCluster instance is not available", func() { - BeforeEach(func() { - DeferCleanup(th.DeleteInstance, CreateNeutronAPI(neutronAPIName.Namespace, neutronAPIName.Name, spec)) - DeferCleanup(k8sClient.Delete, ctx, CreateNeutronAPISecret(namespace, SecretName)) - SimulateTransportURLReady(apiTransportURLName) - }) - It("should not create a secret", func() { - secret := types.NamespacedName{ - Namespace: neutronAPIName.Namespace, - Name: fmt.Sprintf("%s-%s", neutronAPIName.Name, "config"), - } - th.AssertSecretDoesNotExist(secret) - }) - }) + When("required dependency services are available", func() { + BeforeEach(func() { + spec["customServiceConfig"] = "[DEFAULT]\ndebug=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}) + }) + + It("should create a Secret for 01-neutron.conf with the auth_url config option set based on the KeystoneAPI", 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"), + } - When("keystoneAPI instance is not available", func() { - BeforeEach(func() { - DeferCleanup(th.DeleteInstance, CreateNeutronAPI(neutronAPIName.Namespace, neutronAPIName.Name, spec)) - DeferCleanup(k8sClient.Delete, ctx, CreateNeutronAPISecret(namespace, SecretName)) - SimulateTransportURLReady(apiTransportURLName) - DeferCleanup(DeleteOVNDBClusters, CreateOVNDBClusters(namespace)) - }) - It("should not create a secret", func() { - secret := types.NamespacedName{ - Namespace: neutronAPIName.Namespace, - Name: fmt.Sprintf("%s-%s", neutronAPIName.Name, "config"), - } - th.AssertSecretDoesNotExist(secret) - }) - }) + Eventually(func() corev1.Secret { + return th.GetSecret(secret) + }, timeout, interval).ShouldNot(BeNil()) - When("keystoneAPI, OVNDBCluster and Memcached instances are available", func() { - BeforeEach(func() { - spec["customServiceConfig"] = "[DEFAULT]\ndebug=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}) - }) + keystone := keystone.GetKeystoneAPI(keystoneAPI) + Expect(th.GetSecret(secret).Data["01-neutron.conf"]).Should( + ContainSubstring("auth_url = %s", keystone.Status.APIEndpoints["internal"])) - It("should create a Secret for 01-neutron.conf with the auth_url config option set based on the KeystoneAPI", func() { - DeferCleanup(DeleteOVNDBClusters, CreateOVNDBClusters(namespace)) - keystoneAPI := keystone.CreateKeystoneAPI(namespace) - DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPI) + th.ExpectCondition( + neutronAPIName, + ConditionGetterFunc(NeutronAPIConditionGetter), + condition.ServiceConfigReadyCondition, + corev1.ConditionTrue, + ) + th.ExpectCondition( + neutronAPIName, + ConditionGetterFunc(NeutronAPIConditionGetter), + condition.DBReadyCondition, + corev1.ConditionTrue, + ) + }) - secret := types.NamespacedName{ - Namespace: neutronAPIName.Namespace, - Name: fmt.Sprintf("%s-%s", neutronAPIName.Name, "config"), - } + It("should create a Secret for 01-neutron.conf with api and rpc workers and my.cnf", func() { + if isOVNEnabled { + DeferCleanup(DeleteOVNDBClusters, CreateOVNDBClusters(namespace)) + } + keystoneAPI := keystone.CreateKeystoneAPI(namespace) + DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPI) - Eventually(func() corev1.Secret { - return th.GetSecret(secret) - }, timeout, interval).ShouldNot(BeNil()) + 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()) + + configData := th.GetSecret(secret) + Expect(configData).ShouldNot(BeNil()) + conf := string(configData.Data["01-neutron.conf"]) + Expect(conf).Should( + ContainSubstring("api_workers = 2")) + Expect(conf).Should( + ContainSubstring("rpc_workers = 1")) + Expect(conf).Should( + ContainSubstring("mysql_wsrep_sync_wait = 1")) + + databaseAccount := mariadb.GetMariaDBAccount(types.NamespacedName{Namespace: namespace, Name: GetNeutronAPI(neutronAPIName).Spec.DatabaseAccount}) + databaseSecret := th.GetSecret(types.NamespacedName{Namespace: namespace, Name: databaseAccount.Spec.Secret}) + + Expect(conf).Should( + ContainSubstring( + fmt.Sprintf( + "connection=mysql+pymysql://%s:%s@hostname-for-test-neutron-db-instance.%s.svc/neutron?read_default_file=/etc/my.cnf", + databaseAccount.Spec.UserName, + databaseSecret.Data[mariadbv1.DatabasePasswordSelector], + namespace, + ))) + + myCnf := configData.Data["my.cnf"] + Expect(myCnf).To( + ContainSubstring("[client]\nssl=0")) + + }) + + It("should create a Secret for 01-neutron.conf with expected ml2 backend settings", func() { + var dbs []types.NamespacedName + if isOVNEnabled { + dbs := CreateOVNDBClusters(namespace) + DeferCleanup(DeleteOVNDBClusters, dbs) + } + 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"] + Expect(data).Should( + ContainSubstring( + fmt.Sprintf("mechanism_drivers = %s", strings.Join(ml2MechanismDrivers, ",")))) + + if isOVNEnabled { + Expect(data).Should(ContainSubstring("ovn-router")) + for _, db := range dbs { + ovndb := GetOVNDBCluster(db) + Expect(data).Should(ContainSubstring("ovn_%s_connection = %s", strings.ToLower(string(ovndb.Spec.DBType)), ovndb.Status.InternalDBAddress)) + } + } else { + Expect(data).ShouldNot(ContainSubstring("ovn-router")) + } - keystone := keystone.GetKeystoneAPI(keystoneAPI) - Expect(th.GetSecret(secret).Data["01-neutron.conf"]).Should( - ContainSubstring("auth_url = %s", keystone.Status.APIEndpoints["internal"])) + th.ExpectCondition( + neutronAPIName, + ConditionGetterFunc(NeutronAPIConditionGetter), + condition.ServiceConfigReadyCondition, + corev1.ConditionTrue, + ) + th.ExpectCondition( + neutronAPIName, + ConditionGetterFunc(NeutronAPIConditionGetter), + condition.DBReadyCondition, + corev1.ConditionTrue, + ) + }) + + It("should create secret with OwnerReferences set", func() { + if isOVNEnabled { + dbs := CreateOVNDBClusters(namespace) + DeferCleanup(DeleteOVNDBClusters, dbs) + } + 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()) + + // Check OwnerReferences set correctly for the Config Map + Expect(th.GetSecret(secret).ObjectMeta.OwnerReferences[0].Name).To(Equal(neutronAPIName.Name)) + Expect(th.GetSecret(secret).ObjectMeta.OwnerReferences[0].Kind).To(Equal("NeutronAPI")) + }) + + It("should create a Secret for 02-neutron-custom.conf with customServiceConfig input", 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"), + } - th.ExpectCondition( - neutronAPIName, - ConditionGetterFunc(NeutronAPIConditionGetter), - condition.ServiceConfigReadyCondition, - corev1.ConditionTrue, - ) - th.ExpectCondition( - neutronAPIName, - ConditionGetterFunc(NeutronAPIConditionGetter), - condition.DBReadyCondition, - corev1.ConditionTrue, - ) - }) + Eventually(func() corev1.Secret { + return th.GetSecret(secret) + }, timeout, interval).ShouldNot(BeNil()) - It("should create a Secret for 01-neutron.conf with api and rpc workers and my.cnf", func() { - DeferCleanup(DeleteOVNDBClusters, CreateOVNDBClusters(namespace)) - keystoneAPI := keystone.CreateKeystoneAPI(namespace) - DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPI) + Expect(th.GetSecret(secret).Data["02-neutron-custom.conf"]).Should( + ContainSubstring("[DEFAULT]\ndebug=True")) - 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()) + if isOVNEnabled { + It("should create an external Metadata Agent Secret with expected ovn_sb_connection set", func() { + dbs := CreateOVNDBClusters(namespace) + DeferCleanup(DeleteOVNDBClusters, dbs) - configData := th.GetSecret(secret) - Expect(configData).ShouldNot(BeNil()) - conf := string(configData.Data["01-neutron.conf"]) - Expect(conf).Should( - ContainSubstring("api_workers = 2")) - Expect(conf).Should( - ContainSubstring("rpc_workers = 1")) - Expect(conf).Should( - ContainSubstring("mysql_wsrep_sync_wait = 1")) + externalSBEndpoint := "10.0.0.254" + SetExternalDBEndpoint(dbs[1], externalSBEndpoint) - databaseAccount := mariadb.GetMariaDBAccount(types.NamespacedName{Namespace: namespace, Name: GetNeutronAPI(neutronAPIName).Spec.DatabaseAccount}) - databaseSecret := th.GetSecret(types.NamespacedName{Namespace: namespace, Name: databaseAccount.Spec.Secret}) + externalMetadataAgentSecret := types.NamespacedName{ + Namespace: neutronAPIName.Namespace, + Name: fmt.Sprintf("%s-ovn-metadata-agent-neutron-config", neutronAPIName.Name), + } - Expect(conf).Should( - ContainSubstring( - fmt.Sprintf( - "connection=mysql+pymysql://%s:%s@hostname-for-test-neutron-db-instance.%s.svc/neutron?read_default_file=/etc/my.cnf", - databaseAccount.Spec.UserName, - databaseSecret.Data[mariadbv1.DatabasePasswordSelector], - namespace, - ))) + Eventually(func(g Gomega) { + g.Expect(th.GetSecret(externalMetadataAgentSecret).Data[neutronapi.NeutronOVNMetadataAgentSecretKey]).Should( + ContainSubstring("ovn_sb_connection = %s", externalSBEndpoint)) + }, timeout, interval).Should(Succeed()) - myCnf := configData.Data["my.cnf"] - Expect(myCnf).To( - ContainSubstring("[client]\nssl=0")) + Eventually(func(g Gomega) { + NeutronAPI := GetNeutronAPI(neutronAPIName) + g.Expect(NeutronAPI.Status.Hash[externalMetadataAgentSecret.Name]).NotTo(BeEmpty()) + }, timeout, interval).Should(Succeed()) + }) - }) + It("should delete Metadata Agent external Secret once SB DBCluster is deleted", func() { + dbs := CreateOVNDBClusters(namespace) + DeferCleanup(DeleteOVNDBClusters, dbs) - It("should create a Secret for 01-neutron.conf with the ovn connection config option set based on the OVNDBCluster", func() { - dbs := CreateOVNDBClusters(namespace) - DeferCleanup(DeleteOVNDBClusters, dbs) - keystoneAPI := keystone.CreateKeystoneAPI(namespace) - DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPI) - secret := types.NamespacedName{ - Namespace: neutronAPIName.Namespace, - Name: fmt.Sprintf("%s-%s", neutronAPIName.Name, "config"), - } + externalSBEndpoint := "10.0.0.254" + SetExternalDBEndpoint(dbs[1], externalSBEndpoint) - Eventually(func() corev1.Secret { - return th.GetSecret(secret) - }, timeout, interval).ShouldNot(BeNil()) - for _, db := range dbs { - ovndb := GetOVNDBCluster(db) - Expect(th.GetSecret(secret).Data["01-neutron.conf"]).Should( - ContainSubstring("ovn_%s_connection = %s", strings.ToLower(string(ovndb.Spec.DBType)), ovndb.Status.InternalDBAddress)) - } - th.ExpectCondition( - neutronAPIName, - ConditionGetterFunc(NeutronAPIConditionGetter), - condition.ServiceConfigReadyCondition, - corev1.ConditionTrue, - ) - th.ExpectCondition( - neutronAPIName, - ConditionGetterFunc(NeutronAPIConditionGetter), - condition.DBReadyCondition, - corev1.ConditionTrue, - ) - }) - - It("should create secret with OwnerReferences set", func() { - dbs := CreateOVNDBClusters(namespace) - DeferCleanup(DeleteOVNDBClusters, dbs) - 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()) - - // Check OwnerReferences set correctly for the Config Map - Expect(th.GetSecret(secret).ObjectMeta.OwnerReferences[0].Name).To(Equal(neutronAPIName.Name)) - Expect(th.GetSecret(secret).ObjectMeta.OwnerReferences[0].Kind).To(Equal("NeutronAPI")) - }) - - It("should create a Secret for 02-neutron-custom.conf with customServiceConfig input", func() { - 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()) - - Expect(th.GetSecret(secret).Data["02-neutron-custom.conf"]).Should( - ContainSubstring("[DEFAULT]\ndebug=True")) - - }) - It("should create an external Metadata Agent Secret with expected ovn_sb_connection set", func() { - dbs := CreateOVNDBClusters(namespace) - DeferCleanup(DeleteOVNDBClusters, dbs) - - externalSBEndpoint := "10.0.0.254" - SetExternalDBEndpoint(dbs[1], externalSBEndpoint) - - externalMetadataAgentSecret := types.NamespacedName{ - Namespace: neutronAPIName.Namespace, - Name: fmt.Sprintf("%s-ovn-metadata-agent-neutron-config", neutronAPIName.Name), - } - - Eventually(func(g Gomega) { - g.Expect(th.GetSecret(externalMetadataAgentSecret).Data[neutronapi.NeutronOVNMetadataAgentSecretKey]).Should( - ContainSubstring("ovn_sb_connection = %s", externalSBEndpoint)) - }, timeout, interval).Should(Succeed()) - - Eventually(func(g Gomega) { - NeutronAPI := GetNeutronAPI(neutronAPIName) - g.Expect(NeutronAPI.Status.Hash[externalMetadataAgentSecret.Name]).NotTo(BeEmpty()) - }, timeout, interval).Should(Succeed()) - }) - - It("should delete Metadata Agent external Secret once SB DBCluster is deleted", func() { - dbs := CreateOVNDBClusters(namespace) - DeferCleanup(DeleteOVNDBClusters, dbs) - - externalSBEndpoint := "10.0.0.254" - SetExternalDBEndpoint(dbs[1], externalSBEndpoint) - - externalMetadataAgentSecret := types.NamespacedName{ - Namespace: neutronAPIName.Namespace, - Name: fmt.Sprintf("%s-ovn-metadata-agent-neutron-config", neutronAPIName.Name), - } + externalMetadataAgentSecret := types.NamespacedName{ + Namespace: neutronAPIName.Namespace, + Name: fmt.Sprintf("%s-ovn-metadata-agent-neutron-config", neutronAPIName.Name), + } - Eventually(func() corev1.Secret { - return th.GetSecret(externalMetadataAgentSecret) - }, timeout, interval).ShouldNot(BeNil()) + Eventually(func() corev1.Secret { + return th.GetSecret(externalMetadataAgentSecret) + }, timeout, interval).ShouldNot(BeNil()) - Eventually(func(g Gomega) { - NeutronAPI := GetNeutronAPI(neutronAPIName) - g.Expect(NeutronAPI.Status.Hash[externalMetadataAgentSecret.Name]).NotTo(BeEmpty()) - }, timeout, interval).Should(Succeed()) + Eventually(func(g Gomega) { + NeutronAPI := GetNeutronAPI(neutronAPIName) + g.Expect(NeutronAPI.Status.Hash[externalMetadataAgentSecret.Name]).NotTo(BeEmpty()) + }, timeout, interval).Should(Succeed()) - DeleteOVNDBClusters([]types.NamespacedName{dbs[1]}) + DeleteOVNDBClusters([]types.NamespacedName{dbs[1]}) - Eventually(func(g Gomega) { - secret := &corev1.Secret{} - g.Expect(k8sClient.Get(ctx, externalMetadataAgentSecret, secret)).Should(HaveOccurred()) - }, timeout, interval).Should(Succeed()) + Eventually(func(g Gomega) { + secret := &corev1.Secret{} + g.Expect(k8sClient.Get(ctx, externalMetadataAgentSecret, secret)).Should(HaveOccurred()) + }, timeout, interval).Should(Succeed()) - Eventually(func(g Gomega) { - NeutronAPI := GetNeutronAPI(neutronAPIName) - g.Expect(NeutronAPI.Status.Hash[externalMetadataAgentSecret.Name]).To(BeEmpty()) - }, timeout, interval).Should(Succeed()) - }) + Eventually(func(g Gomega) { + NeutronAPI := GetNeutronAPI(neutronAPIName) + g.Expect(NeutronAPI.Status.Hash[externalMetadataAgentSecret.Name]).To(BeEmpty()) + }, timeout, interval).Should(Succeed()) + }) - It("should update Neutron Metadata Agent Secret once SB DBCluster is updated", func() { - dbs := CreateOVNDBClusters(namespace) - DeferCleanup(DeleteOVNDBClusters, dbs) + It("should update Neutron Metadata Agent Secret once SB DBCluster is updated", func() { + dbs := CreateOVNDBClusters(namespace) + DeferCleanup(DeleteOVNDBClusters, dbs) - externalSBEndpoint := "10.0.0.254" - SetExternalDBEndpoint(dbs[1], externalSBEndpoint) + externalSBEndpoint := "10.0.0.254" + SetExternalDBEndpoint(dbs[1], externalSBEndpoint) - externalMetadataAgentSecret := types.NamespacedName{ - Namespace: neutronAPIName.Namespace, - Name: fmt.Sprintf("%s-ovn-metadata-agent-neutron-config", neutronAPIName.Name), + externalMetadataAgentSecret := types.NamespacedName{ + Namespace: neutronAPIName.Namespace, + Name: fmt.Sprintf("%s-ovn-metadata-agent-neutron-config", neutronAPIName.Name), + } + + Eventually(func(g Gomega) { + g.Expect(th.GetSecret(externalMetadataAgentSecret).Data[neutronapi.NeutronOVNMetadataAgentSecretKey]).Should( + ContainSubstring("ovn_sb_connection = %s", externalSBEndpoint)) + }, timeout, interval).Should(Succeed()) + + Eventually(func(g Gomega) { + NeutronAPI := GetNeutronAPI(neutronAPIName) + initialHash := NeutronAPI.Status.Hash[externalMetadataAgentSecret.Name] + g.Expect(initialHash).NotTo(BeEmpty()) + }, timeout, interval).Should(Succeed()) + + NeutronAPI := GetNeutronAPI(neutronAPIName) + initialHash := NeutronAPI.Status.Hash[externalMetadataAgentSecret.Name] + + newExternalSBEndpoint := "10.0.0.250" + SetExternalDBEndpoint(dbs[1], newExternalSBEndpoint) + + Eventually(func(g Gomega) { + g.Expect(th.GetSecret(externalMetadataAgentSecret).Data[neutronapi.NeutronOVNMetadataAgentSecretKey]).Should( + ContainSubstring("ovn_sb_connection = %s", newExternalSBEndpoint)) + }, timeout, interval).Should(Succeed()) + + Eventually(func(g Gomega) { + NeutronAPI := GetNeutronAPI(neutronAPIName) + newHash := NeutronAPI.Status.Hash[externalMetadataAgentSecret.Name] + g.Expect(newHash).NotTo(Equal(initialHash)) + }, timeout, interval).Should(Succeed()) + }) } - Eventually(func(g Gomega) { - g.Expect(th.GetSecret(externalMetadataAgentSecret).Data[neutronapi.NeutronOVNMetadataAgentSecretKey]).Should( - ContainSubstring("ovn_sb_connection = %s", externalSBEndpoint)) - }, timeout, interval).Should(Succeed()) - - Eventually(func(g Gomega) { - NeutronAPI := GetNeutronAPI(neutronAPIName) - initialHash := NeutronAPI.Status.Hash[externalMetadataAgentSecret.Name] - g.Expect(initialHash).NotTo(BeEmpty()) - }, timeout, interval).Should(Succeed()) + It("should create a Secret for neutron.conf with memcached servers set", func() { + if isOVNEnabled { + DeferCleanup(DeleteOVNDBClusters, CreateOVNDBClusters(namespace)) + } + keystoneAPI := keystone.CreateKeystoneAPI(namespace) + DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPI) - NeutronAPI := GetNeutronAPI(neutronAPIName) - initialHash := NeutronAPI.Status.Hash[externalMetadataAgentSecret.Name] + 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()) + neutronCfg := string(th.GetSecret(secret).Data["01-neutron.conf"]) + Expect(neutronCfg).Should( + ContainSubstring(fmt.Sprintf("memcache_servers=memcached-0.memcached.%s.svc:11211,memcached-1.memcached.%s.svc:11211,memcached-2.memcached.%s.svc:11211", + neutronAPIName.Namespace, neutronAPIName.Namespace, neutronAPIName.Namespace))) + Expect(neutronCfg).Should( + ContainSubstring(fmt.Sprintf("memcached_servers=inet:[memcached-0.memcached.%s.svc]:11211,inet:[memcached-1.memcached.%s.svc]:11211,inet:[memcached-2.memcached.%s.svc]:11211", + neutronAPIName.Namespace, neutronAPIName.Namespace, neutronAPIName.Namespace))) + }) + + if isOVNEnabled { + It("should create an external OVN Agent Secret with expected ovn nb and sb connection set", func() { + dbs := CreateOVNDBClusters(namespace) + DeferCleanup(DeleteOVNDBClusters, dbs) + + externalNBEndpoint := "10.0.0.253" + SetExternalDBEndpoint(dbs[0], externalNBEndpoint) + externalSBEndpoint := "10.0.0.254" + SetExternalDBEndpoint(dbs[1], externalSBEndpoint) + + externalOVNAgentSecret := types.NamespacedName{ + Namespace: neutronAPIName.Namespace, + Name: fmt.Sprintf("%s-ovn-agent-neutron-config", neutronAPIName.Name), + } + + Eventually(func(g Gomega) { + g.Expect(th.GetSecret(externalOVNAgentSecret).Data[neutronapi.NeutronOVNAgentSecretKey]).Should( + ContainSubstring("ovn_nb_connection = %s", externalNBEndpoint)) + g.Expect(th.GetSecret(externalOVNAgentSecret).Data[neutronapi.NeutronOVNAgentSecretKey]).Should( + ContainSubstring("ovn_sb_connection = %s", externalSBEndpoint)) + }, timeout, interval).Should(Succeed()) + + Eventually(func(g Gomega) { + NeutronAPI := GetNeutronAPI(neutronAPIName) + g.Expect(NeutronAPI.Status.Hash[externalOVNAgentSecret.Name]).NotTo(BeEmpty()) + }, timeout, interval).Should(Succeed()) + }) + + It("should delete OVN Agent external Secret once NB or SB DBClusters are deleted", func() { + dbs := CreateOVNDBClusters(namespace) + DeferCleanup(DeleteOVNDBClusters, dbs) + externalNBEndpoint := "10.0.0.253" + SetExternalDBEndpoint(dbs[0], externalNBEndpoint) + + externalSBEndpoint := "10.0.0.254" + SetExternalDBEndpoint(dbs[1], externalSBEndpoint) + + externalOVNAgentSecret := types.NamespacedName{ + Namespace: neutronAPIName.Namespace, + Name: fmt.Sprintf("%s-ovn-agent-neutron-config", neutronAPIName.Name), + } - newExternalSBEndpoint := "10.0.0.250" - SetExternalDBEndpoint(dbs[1], newExternalSBEndpoint) + Eventually(func() corev1.Secret { + return th.GetSecret(externalOVNAgentSecret) + }, timeout, interval).ShouldNot(BeNil()) - Eventually(func(g Gomega) { - g.Expect(th.GetSecret(externalMetadataAgentSecret).Data[neutronapi.NeutronOVNMetadataAgentSecretKey]).Should( - ContainSubstring("ovn_sb_connection = %s", newExternalSBEndpoint)) - }, timeout, interval).Should(Succeed()) + Eventually(func(g Gomega) { + NeutronAPI := GetNeutronAPI(neutronAPIName) + g.Expect(NeutronAPI.Status.Hash[externalOVNAgentSecret.Name]).NotTo(BeEmpty()) + }, timeout, interval).Should(Succeed()) - Eventually(func(g Gomega) { - NeutronAPI := GetNeutronAPI(neutronAPIName) - newHash := NeutronAPI.Status.Hash[externalMetadataAgentSecret.Name] - g.Expect(newHash).NotTo(Equal(initialHash)) - }, timeout, interval).Should(Succeed()) - }) - It("should create a Secret for neutron.conf with memcached servers set", func() { - DeferCleanup(DeleteOVNDBClusters, CreateOVNDBClusters(namespace)) - keystoneAPI := keystone.CreateKeystoneAPI(namespace) - DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPI) + DeleteOVNDBClusters([]types.NamespacedName{dbs[1]}) - secret := types.NamespacedName{ - Namespace: neutronAPIName.Namespace, - Name: fmt.Sprintf("%s-%s", neutronAPIName.Name, "config"), - } + Eventually(func(g Gomega) { + secret := &corev1.Secret{} + g.Expect(k8sClient.Get(ctx, externalOVNAgentSecret, secret)).Should(HaveOccurred()) + }, timeout, interval).Should(Succeed()) - Eventually(func() corev1.Secret { - return th.GetSecret(secret) - }, timeout, interval).ShouldNot(BeNil()) - neutronCfg := string(th.GetSecret(secret).Data["01-neutron.conf"]) - Expect(neutronCfg).Should( - ContainSubstring(fmt.Sprintf("memcache_servers=memcached-0.memcached.%s.svc:11211,memcached-1.memcached.%s.svc:11211,memcached-2.memcached.%s.svc:11211", - neutronAPIName.Namespace, neutronAPIName.Namespace, neutronAPIName.Namespace))) - Expect(neutronCfg).Should( - ContainSubstring(fmt.Sprintf("memcached_servers=inet:[memcached-0.memcached.%s.svc]:11211,inet:[memcached-1.memcached.%s.svc]:11211,inet:[memcached-2.memcached.%s.svc]:11211", - neutronAPIName.Namespace, neutronAPIName.Namespace, neutronAPIName.Namespace))) - }) + Eventually(func(g Gomega) { + NeutronAPI := GetNeutronAPI(neutronAPIName) + g.Expect(NeutronAPI.Status.Hash[externalOVNAgentSecret.Name]).To(BeEmpty()) + }, timeout, interval).Should(Succeed()) + }) - It("should create an external OVN Agent Secret with expected ovn nb and sb connection set", func() { - dbs := CreateOVNDBClusters(namespace) - DeferCleanup(DeleteOVNDBClusters, dbs) + It("should update Neutron OVN Agent Secret once NB or SB DBCluster is updated", func() { + dbs := CreateOVNDBClusters(namespace) + DeferCleanup(DeleteOVNDBClusters, dbs) + externalNBEndpoint := "10.0.0.253" + SetExternalDBEndpoint(dbs[0], externalNBEndpoint) - externalNBEndpoint := "10.0.0.253" - SetExternalDBEndpoint(dbs[0], externalNBEndpoint) - externalSBEndpoint := "10.0.0.254" - SetExternalDBEndpoint(dbs[1], externalSBEndpoint) + externalSBEndpoint := "10.0.0.254" + SetExternalDBEndpoint(dbs[1], externalSBEndpoint) - externalOVNAgentSecret := types.NamespacedName{ - Namespace: neutronAPIName.Namespace, - Name: fmt.Sprintf("%s-ovn-agent-neutron-config", neutronAPIName.Name), + externalOVNAgentSecret := types.NamespacedName{ + Namespace: neutronAPIName.Namespace, + Name: fmt.Sprintf("%s-ovn-agent-neutron-config", neutronAPIName.Name), + } + + Eventually(func(g Gomega) { + g.Expect(th.GetSecret(externalOVNAgentSecret).Data[neutronapi.NeutronOVNAgentSecretKey]).Should( + ContainSubstring("ovn_sb_connection = %s", externalSBEndpoint)) + }, timeout, interval).Should(Succeed()) + + Eventually(func(g Gomega) { + NeutronAPI := GetNeutronAPI(neutronAPIName) + initialHash := NeutronAPI.Status.Hash[externalOVNAgentSecret.Name] + g.Expect(initialHash).NotTo(BeEmpty()) + }, timeout, interval).Should(Succeed()) + + NeutronAPI := GetNeutronAPI(neutronAPIName) + initialHash := NeutronAPI.Status.Hash[externalOVNAgentSecret.Name] + + newExternalNBEndpoint := "10.0.0.249" + SetExternalDBEndpoint(dbs[0], newExternalNBEndpoint) + newExternalSBEndpoint := "10.0.0.250" + SetExternalDBEndpoint(dbs[1], newExternalSBEndpoint) + + Eventually(func(g Gomega) { + g.Expect(th.GetSecret(externalOVNAgentSecret).Data[neutronapi.NeutronOVNAgentSecretKey]).Should( + ContainSubstring("ovn_nb_connection = %s", newExternalNBEndpoint)) + }, timeout, interval).Should(Succeed()) + Eventually(func(g Gomega) { + g.Expect(th.GetSecret(externalOVNAgentSecret).Data[neutronapi.NeutronOVNAgentSecretKey]).Should( + ContainSubstring("ovn_sb_connection = %s", newExternalSBEndpoint)) + }, timeout, interval).Should(Succeed()) + + Eventually(func(g Gomega) { + NeutronAPI := GetNeutronAPI(neutronAPIName) + newHash := NeutronAPI.Status.Hash[externalOVNAgentSecret.Name] + g.Expect(newHash).NotTo(Equal(initialHash)) + }, timeout, interval).Should(Succeed()) + }) } - - Eventually(func(g Gomega) { - g.Expect(th.GetSecret(externalOVNAgentSecret).Data[neutronapi.NeutronOVNAgentSecretKey]).Should( - ContainSubstring("ovn_nb_connection = %s", externalNBEndpoint)) - g.Expect(th.GetSecret(externalOVNAgentSecret).Data[neutronapi.NeutronOVNAgentSecretKey]).Should( - ContainSubstring("ovn_sb_connection = %s", externalSBEndpoint)) - }, timeout, interval).Should(Succeed()) - - Eventually(func(g Gomega) { - NeutronAPI := GetNeutronAPI(neutronAPIName) - g.Expect(NeutronAPI.Status.Hash[externalOVNAgentSecret.Name]).NotTo(BeEmpty()) - }, timeout, interval).Should(Succeed()) }) - It("should delete OVN Agent external Secret once NB or SB DBClusters are deleted", func() { - dbs := CreateOVNDBClusters(namespace) - DeferCleanup(DeleteOVNDBClusters, dbs) - externalNBEndpoint := "10.0.0.253" - SetExternalDBEndpoint(dbs[0], externalNBEndpoint) - - externalSBEndpoint := "10.0.0.254" - SetExternalDBEndpoint(dbs[1], externalSBEndpoint) - - externalOVNAgentSecret := types.NamespacedName{ - Namespace: neutronAPIName.Namespace, - Name: fmt.Sprintf("%s-ovn-agent-neutron-config", neutronAPIName.Name), - } - - Eventually(func() corev1.Secret { - return th.GetSecret(externalOVNAgentSecret) - }, timeout, interval).ShouldNot(BeNil()) - - Eventually(func(g Gomega) { - NeutronAPI := GetNeutronAPI(neutronAPIName) - g.Expect(NeutronAPI.Status.Hash[externalOVNAgentSecret.Name]).NotTo(BeEmpty()) - }, timeout, interval).Should(Succeed()) - - DeleteOVNDBClusters([]types.NamespacedName{dbs[1]}) - - Eventually(func(g Gomega) { - secret := &corev1.Secret{} - g.Expect(k8sClient.Get(ctx, externalOVNAgentSecret, secret)).Should(HaveOccurred()) - }, timeout, interval).Should(Succeed()) - - Eventually(func(g Gomega) { + When("DB is created", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateNeutronAPI(neutronAPIName.Namespace, neutronAPIName.Name, spec)) + DeferCleanup(k8sClient.Delete, ctx, CreateNeutronAPISecret(namespace, SecretName)) + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + namespace, + GetNeutronAPI(neutronAPIName).Spec.DatabaseInstance, + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) + SimulateTransportURLReady(apiTransportURLName) + DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, "memcached", memcachedSpec)) + infra.SimulateMemcachedReady(memcachedName) + DeferCleanup(DeleteOVNDBClusters, CreateOVNDBClusters(namespace)) + DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(namespace)) + }) + It("Should set DBReady Condition and set DatabaseHostname Status when DB is Created", func() { + mariadb.SimulateMariaDBAccountCompleted(types.NamespacedName{Namespace: namespace, Name: GetNeutronAPI(neutronAPIName).Spec.DatabaseAccount}) + mariadb.SimulateMariaDBDatabaseCompleted(types.NamespacedName{Namespace: namespace, Name: neutronapi.DatabaseCRName}) + th.SimulateJobSuccess(types.NamespacedName{Namespace: namespace, Name: neutronAPIName.Name + "-db-sync"}) NeutronAPI := GetNeutronAPI(neutronAPIName) - g.Expect(NeutronAPI.Status.Hash[externalOVNAgentSecret.Name]).To(BeEmpty()) - }, timeout, interval).Should(Succeed()) + hostname := "hostname-for-" + NeutronAPI.Spec.DatabaseInstance + "." + namespace + ".svc" + Expect(NeutronAPI.Status.DatabaseHostname).To(Equal(hostname)) + th.ExpectCondition( + neutronAPIName, + ConditionGetterFunc(NeutronAPIConditionGetter), + condition.DBReadyCondition, + corev1.ConditionTrue, + ) + th.ExpectCondition( + neutronAPIName, + ConditionGetterFunc(NeutronAPIConditionGetter), + condition.DBSyncReadyCondition, + corev1.ConditionFalse, + ) + }) }) - It("should update Neutron OVN Agent Secret once NB or SB DBCluster is updated", func() { - dbs := CreateOVNDBClusters(namespace) - DeferCleanup(DeleteOVNDBClusters, dbs) - externalNBEndpoint := "10.0.0.253" - SetExternalDBEndpoint(dbs[0], externalNBEndpoint) - - externalSBEndpoint := "10.0.0.254" - SetExternalDBEndpoint(dbs[1], externalSBEndpoint) - - externalOVNAgentSecret := types.NamespacedName{ - Namespace: neutronAPIName.Namespace, - Name: fmt.Sprintf("%s-ovn-agent-neutron-config", neutronAPIName.Name), - } - - Eventually(func(g Gomega) { - g.Expect(th.GetSecret(externalOVNAgentSecret).Data[neutronapi.NeutronOVNAgentSecretKey]).Should( - ContainSubstring("ovn_sb_connection = %s", externalSBEndpoint)) - }, timeout, interval).Should(Succeed()) - - Eventually(func(g Gomega) { - NeutronAPI := GetNeutronAPI(neutronAPIName) - initialHash := NeutronAPI.Status.Hash[externalOVNAgentSecret.Name] - g.Expect(initialHash).NotTo(BeEmpty()) - }, timeout, interval).Should(Succeed()) - - NeutronAPI := GetNeutronAPI(neutronAPIName) - initialHash := NeutronAPI.Status.Hash[externalOVNAgentSecret.Name] + When("Keystone Resources are created", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreateNeutronAPI(neutronAPIName.Namespace, neutronAPIName.Name, spec)) + DeferCleanup(k8sClient.Delete, ctx, CreateNeutronAPISecret(namespace, SecretName)) + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + namespace, + GetNeutronAPI(neutronAPIName).Spec.DatabaseInstance, + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) + SimulateTransportURLReady(apiTransportURLName) + DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, "memcached", memcachedSpec)) + infra.SimulateMemcachedReady(memcachedName) + DeferCleanup(DeleteOVNDBClusters, CreateOVNDBClusters(namespace)) + DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(namespace)) + mariadb.SimulateMariaDBAccountCompleted(types.NamespacedName{Namespace: namespace, Name: GetNeutronAPI(neutronAPIName).Spec.DatabaseAccount}) + mariadb.SimulateMariaDBDatabaseCompleted(types.NamespacedName{Namespace: namespace, Name: neutronapi.DatabaseCRName}) + th.SimulateJobSuccess(types.NamespacedName{Namespace: namespace, Name: neutronAPIName.Name + "-db-sync"}) + keystone.SimulateKeystoneServiceReady(types.NamespacedName{Namespace: namespace, Name: "neutron"}) + keystone.SimulateKeystoneEndpointReady(types.NamespacedName{Namespace: namespace, Name: "neutron"}) + }) + It("Should set ExposeServiceReadyCondition Condition", func() { - newExternalNBEndpoint := "10.0.0.249" - SetExternalDBEndpoint(dbs[0], newExternalNBEndpoint) - newExternalSBEndpoint := "10.0.0.250" - SetExternalDBEndpoint(dbs[1], newExternalSBEndpoint) + th.ExpectCondition( + neutronAPIName, + ConditionGetterFunc(NeutronAPIConditionGetter), + condition.ExposeServiceReadyCondition, + corev1.ConditionTrue, + ) + }) - Eventually(func(g Gomega) { - g.Expect(th.GetSecret(externalOVNAgentSecret).Data[neutronapi.NeutronOVNAgentSecretKey]).Should( - ContainSubstring("ovn_nb_connection = %s", newExternalNBEndpoint)) - }, timeout, interval).Should(Succeed()) - Eventually(func(g Gomega) { - g.Expect(th.GetSecret(externalOVNAgentSecret).Data[neutronapi.NeutronOVNAgentSecretKey]).Should( - ContainSubstring("ovn_sb_connection = %s", newExternalSBEndpoint)) - }, timeout, interval).Should(Succeed()) + It("Assert Services are created", func() { + th.AssertServiceExists(types.NamespacedName{Namespace: namespace, Name: "neutron-public"}) + th.AssertServiceExists(types.NamespacedName{Namespace: namespace, Name: "neutron-internal"}) + }) - Eventually(func(g Gomega) { - NeutronAPI := GetNeutronAPI(neutronAPIName) - newHash := NeutronAPI.Status.Hash[externalOVNAgentSecret.Name] - g.Expect(newHash).NotTo(Equal(initialHash)) - }, timeout, interval).Should(Succeed()) - }) - }) + It("Endpoints are created", func() { - When("DB is created", func() { - BeforeEach(func() { - DeferCleanup(th.DeleteInstance, CreateNeutronAPI(neutronAPIName.Namespace, neutronAPIName.Name, spec)) - DeferCleanup(k8sClient.Delete, ctx, CreateNeutronAPISecret(namespace, SecretName)) - DeferCleanup( - mariadb.DeleteDBService, - mariadb.CreateDBService( - namespace, - GetNeutronAPI(neutronAPIName).Spec.DatabaseInstance, - corev1.ServiceSpec{ - Ports: []corev1.ServicePort{{Port: 3306}}, - }, - ), - ) - SimulateTransportURLReady(apiTransportURLName) - DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, "memcached", memcachedSpec)) - infra.SimulateMemcachedReady(memcachedName) - DeferCleanup(DeleteOVNDBClusters, CreateOVNDBClusters(namespace)) - DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(namespace)) - }) - It("Should set DBReady Condition and set DatabaseHostname Status when DB is Created", func() { - mariadb.SimulateMariaDBAccountCompleted(types.NamespacedName{Namespace: namespace, Name: GetNeutronAPI(neutronAPIName).Spec.DatabaseAccount}) - mariadb.SimulateMariaDBDatabaseCompleted(types.NamespacedName{Namespace: namespace, Name: neutronapi.DatabaseCRName}) - th.SimulateJobSuccess(types.NamespacedName{Namespace: namespace, Name: neutronAPIName.Name + "-db-sync"}) - NeutronAPI := GetNeutronAPI(neutronAPIName) - hostname := "hostname-for-" + NeutronAPI.Spec.DatabaseInstance + "." + namespace + ".svc" - Expect(NeutronAPI.Status.DatabaseHostname).To(Equal(hostname)) - th.ExpectCondition( - neutronAPIName, - ConditionGetterFunc(NeutronAPIConditionGetter), - condition.DBReadyCondition, - corev1.ConditionTrue, - ) - th.ExpectCondition( - neutronAPIName, - ConditionGetterFunc(NeutronAPIConditionGetter), - condition.DBSyncReadyCondition, - corev1.ConditionFalse, - ) - }) - }) - - When("Keystone Resources are created", func() { - BeforeEach(func() { - DeferCleanup(th.DeleteInstance, CreateNeutronAPI(neutronAPIName.Namespace, neutronAPIName.Name, spec)) - DeferCleanup(k8sClient.Delete, ctx, CreateNeutronAPISecret(namespace, SecretName)) - DeferCleanup( - mariadb.DeleteDBService, - mariadb.CreateDBService( - namespace, - GetNeutronAPI(neutronAPIName).Spec.DatabaseInstance, - corev1.ServiceSpec{ - Ports: []corev1.ServicePort{{Port: 3306}}, + th.ExpectCondition( + neutronAPIName, + ConditionGetterFunc(NeutronAPIConditionGetter), + condition.KeystoneEndpointReadyCondition, + corev1.ConditionTrue, + ) + keystoneEndpoint := keystone.GetKeystoneEndpoint(types.NamespacedName{Namespace: namespace, Name: "neutron"}) + endpoints := keystoneEndpoint.Spec.Endpoints + regexp := `http:.*:?\d*$` + Expect(endpoints).To(HaveKeyWithValue("public", MatchRegexp(regexp))) + Expect(endpoints).To(HaveKeyWithValue("internal", MatchRegexp(regexp))) + }) + + It("Deployment is created as expected", func() { + deployment := th.GetDeployment( + types.NamespacedName{ + Namespace: neutronAPIName.Namespace, + Name: "neutron", }, - ), - ) - SimulateTransportURLReady(apiTransportURLName) - DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, "memcached", memcachedSpec)) - infra.SimulateMemcachedReady(memcachedName) - DeferCleanup(DeleteOVNDBClusters, CreateOVNDBClusters(namespace)) - DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(namespace)) - mariadb.SimulateMariaDBAccountCompleted(types.NamespacedName{Namespace: namespace, Name: GetNeutronAPI(neutronAPIName).Spec.DatabaseAccount}) - mariadb.SimulateMariaDBDatabaseCompleted(types.NamespacedName{Namespace: namespace, Name: neutronapi.DatabaseCRName}) - th.SimulateJobSuccess(types.NamespacedName{Namespace: namespace, Name: neutronAPIName.Name + "-db-sync"}) - keystone.SimulateKeystoneServiceReady(types.NamespacedName{Namespace: namespace, Name: "neutron"}) - keystone.SimulateKeystoneEndpointReady(types.NamespacedName{Namespace: namespace, Name: "neutron"}) - }) - It("Should set ExposeServiceReadyCondition Condition", func() { - - th.ExpectCondition( - neutronAPIName, - ConditionGetterFunc(NeutronAPIConditionGetter), - condition.ExposeServiceReadyCondition, - corev1.ConditionTrue, - ) - }) - - It("Assert Services are created", func() { - th.AssertServiceExists(types.NamespacedName{Namespace: namespace, Name: "neutron-public"}) - th.AssertServiceExists(types.NamespacedName{Namespace: namespace, Name: "neutron-internal"}) - }) - - It("Endpoints are created", func() { - - th.ExpectCondition( - neutronAPIName, - ConditionGetterFunc(NeutronAPIConditionGetter), - condition.KeystoneEndpointReadyCondition, - corev1.ConditionTrue, - ) - keystoneEndpoint := keystone.GetKeystoneEndpoint(types.NamespacedName{Namespace: namespace, Name: "neutron"}) - endpoints := keystoneEndpoint.Spec.Endpoints - regexp := `http:.*:?\d*$` - Expect(endpoints).To(HaveKeyWithValue("public", MatchRegexp(regexp))) - Expect(endpoints).To(HaveKeyWithValue("internal", MatchRegexp(regexp))) + ) + Expect(int(*deployment.Spec.Replicas)).To(Equal(1)) + Expect(deployment.Spec.Template.Spec.Volumes).To(HaveLen(2)) + Expect(deployment.Spec.Template.Spec.Containers).To(HaveLen(2)) + + nSvcContainer := deployment.Spec.Template.Spec.Containers[0] + Expect(nSvcContainer.LivenessProbe.HTTPGet.Port.IntVal).To(Equal(int32(9696))) + Expect(nSvcContainer.VolumeMounts).To(HaveLen(2)) + Expect(nSvcContainer.Image).To(Equal(util.GetEnvVar("RELATED_IMAGE_NEUTRON_API_IMAGE_URL_DEFAULT", neutronv1.NeutronAPIContainerImage))) + + nHttpdProxyContainer := deployment.Spec.Template.Spec.Containers[1] + Expect(nHttpdProxyContainer.LivenessProbe.HTTPGet.Port.IntVal).To(Equal(int32(9696))) + Expect(nHttpdProxyContainer.ReadinessProbe.HTTPGet.Port.IntVal).To(Equal(int32(9696))) + Expect(nHttpdProxyContainer.VolumeMounts).To(HaveLen(2)) + Expect(nHttpdProxyContainer.Image).To(Equal(util.GetEnvVar("RELATED_IMAGE_NEUTRON_API_IMAGE_URL_DEFAULT", neutronv1.NeutronAPIContainerImage))) + }) }) - It("Deployment is created as expected", func() { - deployment := th.GetDeployment( - types.NamespacedName{ - Namespace: neutronAPIName.Namespace, + When("NeutronAPI is created with networkAttachments", func() { + BeforeEach(func() { + spec["networkAttachments"] = []string{"internalapi"} + DeferCleanup(th.DeleteInstance, CreateNeutronAPI(neutronAPIName.Namespace, neutronAPIName.Name, spec)) + DeferCleanup(k8sClient.Delete, ctx, CreateNeutronAPISecret(namespace, SecretName)) + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + namespace, + GetNeutronAPI(neutronAPIName).Spec.DatabaseInstance, + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) + SimulateTransportURLReady(apiTransportURLName) + DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, "memcached", memcachedSpec)) + infra.SimulateMemcachedReady(memcachedName) + DeferCleanup(DeleteOVNDBClusters, CreateOVNDBClusters(namespace)) + DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(namespace)) + }) + + It("reports that the definition is missing", func() { + th.ExpectConditionWithDetails( + neutronAPIName, + ConditionGetterFunc(NeutronAPIConditionGetter), + condition.NetworkAttachmentsReadyCondition, + corev1.ConditionFalse, + condition.RequestedReason, + "NetworkAttachment resources missing: internalapi", + ) + }) + It("reports that network attachment is missing", func() { + + internalAPINADName := types.NamespacedName{Namespace: namespace, Name: "internalapi"} + nad := th.CreateNetworkAttachmentDefinition(internalAPINADName) + DeferCleanup(th.DeleteInstance, nad) + mariadb.SimulateMariaDBAccountCompleted(types.NamespacedName{Namespace: namespace, Name: GetNeutronAPI(neutronAPIName).Spec.DatabaseAccount}) + mariadb.SimulateMariaDBDatabaseCompleted(types.NamespacedName{Namespace: namespace, Name: neutronapi.DatabaseCRName}) + th.SimulateJobSuccess(types.NamespacedName{Namespace: namespace, Name: neutronAPIName.Name + "-db-sync"}) + keystone.SimulateKeystoneServiceReady(types.NamespacedName{Namespace: namespace, Name: "neutron"}) + keystone.SimulateKeystoneEndpointReady(types.NamespacedName{Namespace: namespace, Name: "neutron"}) + + deplName := types.NamespacedName{ + Namespace: namespace, Name: "neutron", - }, - ) - Expect(int(*deployment.Spec.Replicas)).To(Equal(1)) - Expect(deployment.Spec.Template.Spec.Volumes).To(HaveLen(2)) - Expect(deployment.Spec.Template.Spec.Containers).To(HaveLen(2)) - - nSvcContainer := deployment.Spec.Template.Spec.Containers[0] - Expect(nSvcContainer.LivenessProbe.HTTPGet.Port.IntVal).To(Equal(int32(9696))) - Expect(nSvcContainer.VolumeMounts).To(HaveLen(2)) - Expect(nSvcContainer.Image).To(Equal(util.GetEnvVar("RELATED_IMAGE_NEUTRON_API_IMAGE_URL_DEFAULT", neutronv1.NeutronAPIContainerImage))) - - nHttpdProxyContainer := deployment.Spec.Template.Spec.Containers[1] - Expect(nHttpdProxyContainer.LivenessProbe.HTTPGet.Port.IntVal).To(Equal(int32(9696))) - Expect(nHttpdProxyContainer.ReadinessProbe.HTTPGet.Port.IntVal).To(Equal(int32(9696))) - Expect(nHttpdProxyContainer.VolumeMounts).To(HaveLen(2)) - Expect(nHttpdProxyContainer.Image).To(Equal(util.GetEnvVar("RELATED_IMAGE_NEUTRON_API_IMAGE_URL_DEFAULT", neutronv1.NeutronAPIContainerImage))) - }) - }) - - When("NeutronAPI is created with networkAttachments", func() { - BeforeEach(func() { - spec["networkAttachments"] = []string{"internalapi"} - DeferCleanup(th.DeleteInstance, CreateNeutronAPI(neutronAPIName.Namespace, neutronAPIName.Name, spec)) - DeferCleanup(k8sClient.Delete, ctx, CreateNeutronAPISecret(namespace, SecretName)) - DeferCleanup( - mariadb.DeleteDBService, - mariadb.CreateDBService( - namespace, - GetNeutronAPI(neutronAPIName).Spec.DatabaseInstance, - corev1.ServiceSpec{ - Ports: []corev1.ServicePort{{Port: 3306}}, - }, - ), - ) - SimulateTransportURLReady(apiTransportURLName) - DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, "memcached", memcachedSpec)) - infra.SimulateMemcachedReady(memcachedName) - DeferCleanup(DeleteOVNDBClusters, CreateOVNDBClusters(namespace)) - DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(namespace)) - }) - - It("reports that the definition is missing", func() { - th.ExpectConditionWithDetails( - neutronAPIName, - ConditionGetterFunc(NeutronAPIConditionGetter), - condition.NetworkAttachmentsReadyCondition, - corev1.ConditionFalse, - condition.RequestedReason, - "NetworkAttachment resources missing: internalapi", - ) - }) - It("reports that network attachment is missing", func() { - - internalAPINADName := types.NamespacedName{Namespace: namespace, Name: "internalapi"} - nad := th.CreateNetworkAttachmentDefinition(internalAPINADName) - DeferCleanup(th.DeleteInstance, nad) - mariadb.SimulateMariaDBAccountCompleted(types.NamespacedName{Namespace: namespace, Name: GetNeutronAPI(neutronAPIName).Spec.DatabaseAccount}) - mariadb.SimulateMariaDBDatabaseCompleted(types.NamespacedName{Namespace: namespace, Name: neutronapi.DatabaseCRName}) - th.SimulateJobSuccess(types.NamespacedName{Namespace: namespace, Name: neutronAPIName.Name + "-db-sync"}) - keystone.SimulateKeystoneServiceReady(types.NamespacedName{Namespace: namespace, Name: "neutron"}) - keystone.SimulateKeystoneEndpointReady(types.NamespacedName{Namespace: namespace, Name: "neutron"}) - - deplName := types.NamespacedName{ - Namespace: namespace, - Name: "neutron", - } - depl := th.GetDeployment(deplName) - th.SimulateDeploymentReadyWithPods(deplName, map[string][]string{}) - - expectedAnnotation, err := json.Marshal( - []networkv1.NetworkSelectionElement{ - { - Name: "internalapi", - Namespace: namespace, - InterfaceRequest: "internalapi", - }}) - Expect(err).ShouldNot(HaveOccurred()) - Expect(depl.Spec.Template.ObjectMeta.Annotations).To( - HaveKeyWithValue("k8s.v1.cni.cncf.io/networks", string(expectedAnnotation)), - ) - - // We don't add network attachment status annotations to the Pods - // to simulate that the network attachments are missing. - //th.SimulateDeploymentReadyWithPods(deplName, map[string][]string{}) - - th.ExpectConditionWithDetails( - neutronAPIName, - ConditionGetterFunc(NeutronAPIConditionGetter), - condition.NetworkAttachmentsReadyCondition, - corev1.ConditionFalse, - condition.ErrorReason, - "NetworkAttachments error occurred "+ - "not all pods have interfaces with ips as configured in NetworkAttachments: [internalapi]", - ) - }) - It("reports that an IP is missing", func() { - - internalAPINADName := types.NamespacedName{Namespace: namespace, Name: "internalapi"} - nad := th.CreateNetworkAttachmentDefinition(internalAPINADName) - DeferCleanup(th.DeleteInstance, nad) - mariadb.SimulateMariaDBAccountCompleted(types.NamespacedName{Namespace: namespace, Name: GetNeutronAPI(neutronAPIName).Spec.DatabaseAccount}) - mariadb.SimulateMariaDBDatabaseCompleted(types.NamespacedName{Namespace: namespace, Name: neutronapi.DatabaseCRName}) - th.SimulateJobSuccess(types.NamespacedName{Namespace: namespace, Name: neutronAPIName.Name + "-db-sync"}) - keystone.SimulateKeystoneServiceReady(types.NamespacedName{Namespace: namespace, Name: "neutron"}) - keystone.SimulateKeystoneEndpointReady(types.NamespacedName{Namespace: namespace, Name: "neutron"}) - deplName := types.NamespacedName{ - Namespace: namespace, - Name: "neutron", - } - depl := th.GetDeployment(deplName) - - expectedAnnotation, err := json.Marshal( - []networkv1.NetworkSelectionElement{ - { - Name: "internalapi", - Namespace: namespace, - InterfaceRequest: "internalapi", - }}) - Expect(err).ShouldNot(HaveOccurred()) - Expect(depl.Spec.Template.ObjectMeta.Annotations).To( - HaveKeyWithValue("k8s.v1.cni.cncf.io/networks", string(expectedAnnotation)), - ) + } + depl := th.GetDeployment(deplName) + th.SimulateDeploymentReadyWithPods(deplName, map[string][]string{}) + + expectedAnnotation, err := json.Marshal( + []networkv1.NetworkSelectionElement{ + { + Name: "internalapi", + Namespace: namespace, + InterfaceRequest: "internalapi", + }}) + Expect(err).ShouldNot(HaveOccurred()) + Expect(depl.Spec.Template.ObjectMeta.Annotations).To( + HaveKeyWithValue("k8s.v1.cni.cncf.io/networks", string(expectedAnnotation)), + ) - // We simulate that there is no IP associated with the internalapi - // network attachment - th.SimulateDeploymentReadyWithPods( - deplName, - map[string][]string{namespace + "/internalapi": {}}, - ) + // We don't add network attachment status annotations to the Pods + // to simulate that the network attachments are missing. + //th.SimulateDeploymentReadyWithPods(deplName, map[string][]string{}) - th.ExpectConditionWithDetails( - neutronAPIName, - ConditionGetterFunc(NeutronAPIConditionGetter), - condition.NetworkAttachmentsReadyCondition, - corev1.ConditionFalse, - condition.ErrorReason, - "NetworkAttachments error occurred "+ - "not all pods have interfaces with ips as configured in NetworkAttachments: [internalapi]", - ) - }) - It("reports NetworkAttachmentsReady if the Pods got the proper annotations", func() { + th.ExpectConditionWithDetails( + neutronAPIName, + ConditionGetterFunc(NeutronAPIConditionGetter), + condition.NetworkAttachmentsReadyCondition, + corev1.ConditionFalse, + condition.ErrorReason, + "NetworkAttachments error occurred "+ + "not all pods have interfaces with ips as configured in NetworkAttachments: [internalapi]", + ) + }) + It("reports that an IP is missing", func() { + + internalAPINADName := types.NamespacedName{Namespace: namespace, Name: "internalapi"} + nad := th.CreateNetworkAttachmentDefinition(internalAPINADName) + DeferCleanup(th.DeleteInstance, nad) + mariadb.SimulateMariaDBAccountCompleted(types.NamespacedName{Namespace: namespace, Name: GetNeutronAPI(neutronAPIName).Spec.DatabaseAccount}) + mariadb.SimulateMariaDBDatabaseCompleted(types.NamespacedName{Namespace: namespace, Name: neutronapi.DatabaseCRName}) + th.SimulateJobSuccess(types.NamespacedName{Namespace: namespace, Name: neutronAPIName.Name + "-db-sync"}) + keystone.SimulateKeystoneServiceReady(types.NamespacedName{Namespace: namespace, Name: "neutron"}) + keystone.SimulateKeystoneEndpointReady(types.NamespacedName{Namespace: namespace, Name: "neutron"}) + deplName := types.NamespacedName{ + Namespace: namespace, + Name: "neutron", + } + depl := th.GetDeployment(deplName) + + expectedAnnotation, err := json.Marshal( + []networkv1.NetworkSelectionElement{ + { + Name: "internalapi", + Namespace: namespace, + InterfaceRequest: "internalapi", + }}) + Expect(err).ShouldNot(HaveOccurred()) + Expect(depl.Spec.Template.ObjectMeta.Annotations).To( + HaveKeyWithValue("k8s.v1.cni.cncf.io/networks", string(expectedAnnotation)), + ) - internalAPINADName := types.NamespacedName{Namespace: namespace, Name: "internalapi"} - nad := th.CreateNetworkAttachmentDefinition(internalAPINADName) - DeferCleanup(th.DeleteInstance, nad) - mariadb.SimulateMariaDBAccountCompleted(types.NamespacedName{Namespace: namespace, Name: GetNeutronAPI(neutronAPIName).Spec.DatabaseAccount}) - mariadb.SimulateMariaDBDatabaseCompleted(types.NamespacedName{Namespace: namespace, Name: neutronapi.DatabaseCRName}) - th.SimulateJobSuccess(types.NamespacedName{Namespace: namespace, Name: neutronAPIName.Name + "-db-sync"}) - keystone.SimulateKeystoneServiceReady(types.NamespacedName{Namespace: namespace, Name: "neutron"}) - keystone.SimulateKeystoneEndpointReady(types.NamespacedName{Namespace: namespace, Name: "neutron"}) + // We simulate that there is no IP associated with the internalapi + // network attachment + th.SimulateDeploymentReadyWithPods( + deplName, + map[string][]string{namespace + "/internalapi": {}}, + ) - deplName := types.NamespacedName{ - Namespace: namespace, - Name: "neutron", - } - th.SimulateDeploymentReadyWithPods( - deplName, - map[string][]string{namespace + "/internalapi": {"10.0.0.1"}}, - ) + th.ExpectConditionWithDetails( + neutronAPIName, + ConditionGetterFunc(NeutronAPIConditionGetter), + condition.NetworkAttachmentsReadyCondition, + corev1.ConditionFalse, + condition.ErrorReason, + "NetworkAttachments error occurred "+ + "not all pods have interfaces with ips as configured in NetworkAttachments: [internalapi]", + ) + }) + It("reports NetworkAttachmentsReady if the Pods got the proper annotations", func() { + + internalAPINADName := types.NamespacedName{Namespace: namespace, Name: "internalapi"} + nad := th.CreateNetworkAttachmentDefinition(internalAPINADName) + DeferCleanup(th.DeleteInstance, nad) + mariadb.SimulateMariaDBAccountCompleted(types.NamespacedName{Namespace: namespace, Name: GetNeutronAPI(neutronAPIName).Spec.DatabaseAccount}) + mariadb.SimulateMariaDBDatabaseCompleted(types.NamespacedName{Namespace: namespace, Name: neutronapi.DatabaseCRName}) + th.SimulateJobSuccess(types.NamespacedName{Namespace: namespace, Name: neutronAPIName.Name + "-db-sync"}) + keystone.SimulateKeystoneServiceReady(types.NamespacedName{Namespace: namespace, Name: "neutron"}) + keystone.SimulateKeystoneEndpointReady(types.NamespacedName{Namespace: namespace, Name: "neutron"}) + + deplName := types.NamespacedName{ + Namespace: namespace, + Name: "neutron", + } + th.SimulateDeploymentReadyWithPods( + deplName, + map[string][]string{namespace + "/internalapi": {"10.0.0.1"}}, + ) - th.ExpectCondition( - neutronAPIName, - ConditionGetterFunc(NeutronAPIConditionGetter), - condition.NetworkAttachmentsReadyCondition, - corev1.ConditionTrue, - ) + th.ExpectCondition( + neutronAPIName, + ConditionGetterFunc(NeutronAPIConditionGetter), + condition.NetworkAttachmentsReadyCondition, + corev1.ConditionTrue, + ) - Eventually(func(g Gomega) { - NeutronAPI := GetNeutronAPI(neutronAPIName) - g.Expect(NeutronAPI.Status.NetworkAttachments).To( - Equal(map[string][]string{namespace + "/internalapi": {"10.0.0.1"}})) + Eventually(func(g Gomega) { + NeutronAPI := GetNeutronAPI(neutronAPIName) + g.Expect(NeutronAPI.Status.NetworkAttachments).To( + Equal(map[string][]string{namespace + "/internalapi": {"10.0.0.1"}})) - }, timeout, interval).Should(Succeed()) + }, timeout, interval).Should(Succeed()) + }) }) - }) - When("A NeutronAPI is created with TLS", func() { - BeforeEach(func() { - spec["tls"] = map[string]interface{}{ - "api": map[string]interface{}{ - "internal": map[string]interface{}{ - "secretName": InternalCertSecretName, - }, - "public": map[string]interface{}{ - "secretName": PublicCertSecretName, + When("A NeutronAPI is created with TLS", func() { + BeforeEach(func() { + spec["tls"] = map[string]interface{}{ + "api": map[string]interface{}{ + "internal": map[string]interface{}{ + "secretName": InternalCertSecretName, + }, + "public": map[string]interface{}{ + "secretName": PublicCertSecretName, + }, }, - }, - "caBundleSecretName": CABundleSecretName, - "ovn": map[string]interface{}{ - "secretName": InternalCertSecretName, - }, - } - DeferCleanup(th.DeleteInstance, CreateNeutronAPI(neutronAPIName.Namespace, neutronAPIName.Name, spec)) - - DeferCleanup(k8sClient.Delete, ctx, th.CreateCABundleSecret(caBundleSecretName)) - DeferCleanup(k8sClient.Delete, ctx, th.CreateCertSecret(internalCertSecretName)) - DeferCleanup(k8sClient.Delete, ctx, th.CreateCertSecret(publicCertSecretName)) - DeferCleanup(k8sClient.Delete, ctx, CreateNeutronAPISecret(namespace, SecretName)) - DeferCleanup( - mariadb.DeleteDBService, - mariadb.CreateDBService( - namespace, - GetNeutronAPI(neutronAPIName).Spec.DatabaseInstance, - corev1.ServiceSpec{ - Ports: []corev1.ServicePort{{Port: 3306}}, + "caBundleSecretName": CABundleSecretName, + "ovn": map[string]interface{}{ + "secretName": InternalCertSecretName, }, - ), - ) - SimulateTransportURLReady(apiTransportURLName) - DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, "memcached", memcachedSpec)) - infra.SimulateMemcachedReady(memcachedName) - DeferCleanup(DeleteOVNDBClusters, CreateOVNDBClusters(namespace)) - DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(namespace)) - mariadb.SimulateMariaDBAccountCompleted(types.NamespacedName{Namespace: namespace, Name: GetNeutronAPI(neutronAPIName).Spec.DatabaseAccount}) - mariadb.SimulateMariaDBTLSDatabaseCompleted(types.NamespacedName{Namespace: namespace, Name: neutronapi.Database}) - th.SimulateJobSuccess(types.NamespacedName{Namespace: namespace, Name: neutronAPIName.Name + "-db-sync"}) - keystone.SimulateKeystoneServiceReady(types.NamespacedName{Namespace: namespace, Name: "neutron"}) - keystone.SimulateKeystoneEndpointReady(types.NamespacedName{Namespace: namespace, Name: "neutron"}) - }) - - It("it creates deployment with CA and service certs mounted", func() { - th.ExpectCondition( - neutronAPIName, - ConditionGetterFunc(NeutronAPIConditionGetter), - condition.TLSInputReadyCondition, - corev1.ConditionTrue, - ) - - deployment := th.GetDeployment( - types.NamespacedName{ - Namespace: neutronAPIName.Namespace, - Name: "neutron", - }, - ) - - // cert deployment volumes - th.AssertVolumeExists(caBundleSecretName.Name, deployment.Spec.Template.Spec.Volumes) - th.AssertVolumeExists(internalCertSecretName.Name, deployment.Spec.Template.Spec.Volumes) - th.AssertVolumeExists(publicCertSecretName.Name, deployment.Spec.Template.Spec.Volumes) - th.AssertVolumeExists(ovnDbCertSecretName.Name, deployment.Spec.Template.Spec.Volumes) - - // svc container ca cert - nSvcContainer := deployment.Spec.Template.Spec.Containers[0] - th.AssertVolumeMountExists(caBundleSecretName.Name, "tls-ca-bundle.pem", nSvcContainer.VolumeMounts) - th.AssertVolumeMountExists(ovnDbCertSecretName.Name, "tls.key", nSvcContainer.VolumeMounts) - th.AssertVolumeMountExists(ovnDbCertSecretName.Name, "tls.crt", nSvcContainer.VolumeMounts) - th.AssertVolumeMountExists(ovnDbCertSecretName.Name, "ca.crt", nSvcContainer.VolumeMounts) - - // httpd container certs - nHttpdProxyContainer := deployment.Spec.Template.Spec.Containers[1] - th.AssertVolumeMountExists(publicCertSecretName.Name, "tls.key", nHttpdProxyContainer.VolumeMounts) - th.AssertVolumeMountExists(publicCertSecretName.Name, "tls.crt", nHttpdProxyContainer.VolumeMounts) - th.AssertVolumeMountExists(internalCertSecretName.Name, "tls.key", nHttpdProxyContainer.VolumeMounts) - th.AssertVolumeMountExists(internalCertSecretName.Name, "tls.crt", nHttpdProxyContainer.VolumeMounts) - th.AssertVolumeMountExists(caBundleSecretName.Name, "tls-ca-bundle.pem", nHttpdProxyContainer.VolumeMounts) - - Expect(nHttpdProxyContainer.ReadinessProbe.HTTPGet.Scheme).To(Equal(corev1.URISchemeHTTPS)) - Expect(nHttpdProxyContainer.LivenessProbe.HTTPGet.Scheme).To(Equal(corev1.URISchemeHTTPS)) - - secret := types.NamespacedName{ - Namespace: neutronAPIName.Namespace, - Name: fmt.Sprintf("%s-%s", neutronAPIName.Name, "config"), - } - - configData := th.GetSecret(secret) - Expect(configData).ShouldNot(BeNil()) - conf := string(configData.Data["01-neutron.conf"]) - Expect(conf).Should( - ContainSubstring("api_workers = 2")) - Expect(conf).Should( - ContainSubstring("rpc_workers = 1")) - Expect(conf).Should( - ContainSubstring("mysql_wsrep_sync_wait = 1")) - - databaseAccount := mariadb.GetMariaDBAccount(types.NamespacedName{Namespace: namespace, Name: GetNeutronAPI(neutronAPIName).Spec.DatabaseAccount}) - databaseSecret := th.GetSecret(types.NamespacedName{Namespace: namespace, Name: databaseAccount.Spec.Secret}) - - Expect(conf).Should( - ContainSubstring( - fmt.Sprintf( - "connection=mysql+pymysql://%s:%s@hostname-for-test-neutron-db-instance.%s.svc/neutron?read_default_file=/etc/my.cnf", - databaseAccount.Spec.UserName, - databaseSecret.Data[mariadbv1.DatabasePasswordSelector], - namespace))) - - Expect(conf).Should(And( - ContainSubstring("ovn_nb_private_key = /etc/pki/tls/private/ovndb.key"), - ContainSubstring("ovn_nb_certificate = /etc/pki/tls/certs/ovndb.crt"), - ContainSubstring("ovn_nb_ca_cert = /etc/pki/tls/certs/ovndbca.crt"), - ContainSubstring("ovn_sb_private_key = /etc/pki/tls/private/ovndb.key"), - ContainSubstring("ovn_sb_certificate = /etc/pki/tls/certs/ovndb.crt"), - ContainSubstring("ovn_sb_ca_cert = /etc/pki/tls/certs/ovndbca.crt"), - )) - - conf = string(configData.Data["my.cnf"]) - Expect(conf).To( - ContainSubstring("[client]\nssl-ca=/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem\nssl=1")) - }) - - It("TLS Endpoints are created", func() { - - th.ExpectCondition( - neutronAPIName, - ConditionGetterFunc(NeutronAPIConditionGetter), - condition.KeystoneEndpointReadyCondition, - corev1.ConditionTrue, - ) - keystoneEndpoint := keystone.GetKeystoneEndpoint(types.NamespacedName{Namespace: namespace, Name: "neutron"}) - endpoints := keystoneEndpoint.Spec.Endpoints - Expect(endpoints).To(HaveKeyWithValue("public", "https://neutron-public."+neutronAPIName.Namespace+".svc:9696")) - Expect(endpoints).To(HaveKeyWithValue("internal", "https://neutron-internal."+neutronAPIName.Namespace+".svc:9696")) - }) + } + DeferCleanup(th.DeleteInstance, CreateNeutronAPI(neutronAPIName.Namespace, neutronAPIName.Name, spec)) + + DeferCleanup(k8sClient.Delete, ctx, th.CreateCABundleSecret(caBundleSecretName)) + DeferCleanup(k8sClient.Delete, ctx, th.CreateCertSecret(internalCertSecretName)) + DeferCleanup(k8sClient.Delete, ctx, th.CreateCertSecret(publicCertSecretName)) + DeferCleanup(k8sClient.Delete, ctx, CreateNeutronAPISecret(namespace, SecretName)) + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + namespace, + GetNeutronAPI(neutronAPIName).Spec.DatabaseInstance, + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) + SimulateTransportURLReady(apiTransportURLName) + DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, "memcached", memcachedSpec)) + infra.SimulateMemcachedReady(memcachedName) + DeferCleanup(DeleteOVNDBClusters, CreateOVNDBClusters(namespace)) + DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(namespace)) + mariadb.SimulateMariaDBAccountCompleted(types.NamespacedName{Namespace: namespace, Name: GetNeutronAPI(neutronAPIName).Spec.DatabaseAccount}) + mariadb.SimulateMariaDBTLSDatabaseCompleted(types.NamespacedName{Namespace: namespace, Name: neutronapi.Database}) + th.SimulateJobSuccess(types.NamespacedName{Namespace: namespace, Name: neutronAPIName.Name + "-db-sync"}) + keystone.SimulateKeystoneServiceReady(types.NamespacedName{Namespace: namespace, Name: "neutron"}) + keystone.SimulateKeystoneEndpointReady(types.NamespacedName{Namespace: namespace, Name: "neutron"}) + }) + + It("it creates deployment with certs mounted", func() { + th.ExpectCondition( + neutronAPIName, + ConditionGetterFunc(NeutronAPIConditionGetter), + condition.TLSInputReadyCondition, + corev1.ConditionTrue, + ) - It("reconfigures the neutron pod when CA changes", func() { - // Grab the current config hash - originalHash := GetEnvVarValue( - th.GetDeployment( + deployment := th.GetDeployment( types.NamespacedName{ Namespace: neutronAPIName.Namespace, Name: "neutron", }, - ).Spec.Template.Spec.Containers[0].Env, "CONFIG_HASH", "") - Expect(originalHash).NotTo(BeEmpty()) + ) - // Change the content of the CA secret - th.UpdateSecret(caBundleSecretName, "tls-ca-bundle.pem", []byte("DifferentCAData")) + // cert deployment volumes + th.AssertVolumeExists(caBundleSecretName.Name, deployment.Spec.Template.Spec.Volumes) + th.AssertVolumeExists(internalCertSecretName.Name, deployment.Spec.Template.Spec.Volumes) + th.AssertVolumeExists(publicCertSecretName.Name, deployment.Spec.Template.Spec.Volumes) + if isOVNEnabled { + th.AssertVolumeExists(ovnDbCertSecretName.Name, deployment.Spec.Template.Spec.Volumes) + } + + // svc container ca cert + nSvcContainer := deployment.Spec.Template.Spec.Containers[0] + th.AssertVolumeMountExists(caBundleSecretName.Name, "tls-ca-bundle.pem", nSvcContainer.VolumeMounts) + if isOVNEnabled { + th.AssertVolumeMountExists(ovnDbCertSecretName.Name, "tls.key", nSvcContainer.VolumeMounts) + th.AssertVolumeMountExists(ovnDbCertSecretName.Name, "tls.crt", nSvcContainer.VolumeMounts) + th.AssertVolumeMountExists(ovnDbCertSecretName.Name, "ca.crt", nSvcContainer.VolumeMounts) + } + + // httpd container certs + nHttpdProxyContainer := deployment.Spec.Template.Spec.Containers[1] + th.AssertVolumeMountExists(publicCertSecretName.Name, "tls.key", nHttpdProxyContainer.VolumeMounts) + th.AssertVolumeMountExists(publicCertSecretName.Name, "tls.crt", nHttpdProxyContainer.VolumeMounts) + th.AssertVolumeMountExists(internalCertSecretName.Name, "tls.key", nHttpdProxyContainer.VolumeMounts) + th.AssertVolumeMountExists(internalCertSecretName.Name, "tls.crt", nHttpdProxyContainer.VolumeMounts) + th.AssertVolumeMountExists(caBundleSecretName.Name, "tls-ca-bundle.pem", nHttpdProxyContainer.VolumeMounts) + + Expect(nHttpdProxyContainer.ReadinessProbe.HTTPGet.Scheme).To(Equal(corev1.URISchemeHTTPS)) + Expect(nHttpdProxyContainer.LivenessProbe.HTTPGet.Scheme).To(Equal(corev1.URISchemeHTTPS)) + + secret := types.NamespacedName{ + Namespace: neutronAPIName.Namespace, + Name: fmt.Sprintf("%s-%s", neutronAPIName.Name, "config"), + } + + configData := th.GetSecret(secret) + Expect(configData).ShouldNot(BeNil()) + conf := string(configData.Data["01-neutron.conf"]) + Expect(conf).Should( + ContainSubstring("api_workers = 2")) + Expect(conf).Should( + ContainSubstring("rpc_workers = 1")) + Expect(conf).Should( + ContainSubstring("mysql_wsrep_sync_wait = 1")) + + databaseAccount := mariadb.GetMariaDBAccount(types.NamespacedName{Namespace: namespace, Name: GetNeutronAPI(neutronAPIName).Spec.DatabaseAccount}) + databaseSecret := th.GetSecret(types.NamespacedName{Namespace: namespace, Name: databaseAccount.Spec.Secret}) + + Expect(conf).Should( + ContainSubstring( + fmt.Sprintf( + "connection=mysql+pymysql://%s:%s@hostname-for-test-neutron-db-instance.%s.svc/neutron?read_default_file=/etc/my.cnf", + databaseAccount.Spec.UserName, + databaseSecret.Data[mariadbv1.DatabasePasswordSelector], + namespace))) + + if isOVNEnabled { + Expect(conf).Should(And( + ContainSubstring("ovn_nb_private_key = /etc/pki/tls/private/ovndb.key"), + ContainSubstring("ovn_nb_certificate = /etc/pki/tls/certs/ovndb.crt"), + ContainSubstring("ovn_nb_ca_cert = /etc/pki/tls/certs/ovndbca.crt"), + ContainSubstring("ovn_sb_private_key = /etc/pki/tls/private/ovndb.key"), + ContainSubstring("ovn_sb_certificate = /etc/pki/tls/certs/ovndb.crt"), + ContainSubstring("ovn_sb_ca_cert = /etc/pki/tls/certs/ovndbca.crt"), + )) + } else { + Expect(conf).ShouldNot(Or( + ContainSubstring("ovn_nb_private_key = /etc/pki/tls/private/ovndb.key"), + ContainSubstring("ovn_nb_certificate = /etc/pki/tls/certs/ovndb.crt"), + ContainSubstring("ovn_nb_ca_cert = /etc/pki/tls/certs/ovndbca.crt"), + ContainSubstring("ovn_sb_private_key = /etc/pki/tls/private/ovndb.key"), + ContainSubstring("ovn_sb_certificate = /etc/pki/tls/certs/ovndb.crt"), + ContainSubstring("ovn_sb_ca_cert = /etc/pki/tls/certs/ovndbca.crt"), + )) + } + + conf = string(configData.Data["my.cnf"]) + Expect(conf).To( + ContainSubstring("[client]\nssl-ca=/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem\nssl=1")) + }) + + It("TLS Endpoints are created", func() { - // Assert that the deployment is updated - Eventually(func(g Gomega) { - newHash := GetEnvVarValue( + th.ExpectCondition( + neutronAPIName, + ConditionGetterFunc(NeutronAPIConditionGetter), + condition.KeystoneEndpointReadyCondition, + corev1.ConditionTrue, + ) + keystoneEndpoint := keystone.GetKeystoneEndpoint(types.NamespacedName{Namespace: namespace, Name: "neutron"}) + endpoints := keystoneEndpoint.Spec.Endpoints + Expect(endpoints).To(HaveKeyWithValue("public", "https://neutron-public."+neutronAPIName.Namespace+".svc:9696")) + Expect(endpoints).To(HaveKeyWithValue("internal", "https://neutron-internal."+neutronAPIName.Namespace+".svc:9696")) + }) + + It("reconfigures the neutron pod when CA changes", func() { + // Grab the current config hash + originalHash := GetEnvVarValue( th.GetDeployment( types.NamespacedName{ Namespace: neutronAPIName.Namespace, Name: "neutron", }, ).Spec.Template.Spec.Containers[0].Env, "CONFIG_HASH", "") - g.Expect(newHash).NotTo(BeEmpty()) - g.Expect(newHash).NotTo(Equal(originalHash)) - }, timeout, interval).Should(Succeed()) + Expect(originalHash).NotTo(BeEmpty()) + + // Change the content of the CA secret + th.UpdateSecret(caBundleSecretName, "tls-ca-bundle.pem", []byte("DifferentCAData")) + + // Assert that the deployment is updated + Eventually(func(g Gomega) { + newHash := GetEnvVarValue( + th.GetDeployment( + types.NamespacedName{ + Namespace: neutronAPIName.Namespace, + Name: "neutron", + }, + ).Spec.Template.Spec.Containers[0].Env, "CONFIG_HASH", "") + g.Expect(newHash).NotTo(BeEmpty()) + g.Expect(newHash).NotTo(Equal(originalHash)) + }, timeout, interval).Should(Succeed()) + }) }) - }) - - When("A NeutronAPI is created with TLS and service override endpointURL set", func() { - BeforeEach(func() { - spec["tls"] = map[string]interface{}{ - "api": map[string]interface{}{ - "internal": map[string]interface{}{ - "secretName": InternalCertSecretName, - }, - "public": map[string]interface{}{ - "secretName": PublicCertSecretName, - }, - }, - "caBundleSecretName": CABundleSecretName, - } - - serviceOverride := map[string]interface{}{} - serviceOverride["public"] = map[string]interface{}{ - "endpointURL": "https://neutron-openstack.apps-crc.testing", - } - spec["override"] = map[string]interface{}{ - "service": serviceOverride, - } - - DeferCleanup(th.DeleteInstance, CreateNeutronAPI(neutronAPIName.Namespace, neutronAPIName.Name, spec)) - - DeferCleanup(k8sClient.Delete, ctx, th.CreateCABundleSecret(caBundleSecretName)) - DeferCleanup(k8sClient.Delete, ctx, th.CreateCertSecret(internalCertSecretName)) - DeferCleanup(k8sClient.Delete, ctx, th.CreateCertSecret(publicCertSecretName)) - DeferCleanup(k8sClient.Delete, ctx, CreateNeutronAPISecret(namespace, SecretName)) - DeferCleanup( - mariadb.DeleteDBService, - mariadb.CreateDBService( - namespace, - GetNeutronAPI(neutronAPIName).Spec.DatabaseInstance, - corev1.ServiceSpec{ - Ports: []corev1.ServicePort{{Port: 3306}}, + When("A NeutronAPI is created with TLS and service override endpointURL set", func() { + BeforeEach(func() { + spec["tls"] = map[string]interface{}{ + "api": map[string]interface{}{ + "internal": map[string]interface{}{ + "secretName": InternalCertSecretName, + }, + "public": map[string]interface{}{ + "secretName": PublicCertSecretName, + }, }, - ), - ) - SimulateTransportURLReady(apiTransportURLName) - DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, "memcached", memcachedSpec)) - infra.SimulateMemcachedReady(memcachedName) - DeferCleanup(DeleteOVNDBClusters, CreateOVNDBClusters(namespace)) - DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(namespace)) - mariadb.SimulateMariaDBAccountCompleted(types.NamespacedName{Namespace: namespace, Name: GetNeutronAPI(neutronAPIName).Spec.DatabaseAccount}) - mariadb.SimulateMariaDBDatabaseCompleted(types.NamespacedName{Namespace: namespace, Name: neutronapi.DatabaseCRName}) - th.SimulateJobSuccess(types.NamespacedName{Namespace: namespace, Name: neutronAPIName.Name + "-db-sync"}) - keystone.SimulateKeystoneServiceReady(types.NamespacedName{Namespace: namespace, Name: "neutron"}) - keystone.SimulateKeystoneEndpointReady(types.NamespacedName{Namespace: namespace, Name: "neutron"}) - }) - - It("registers endpointURL as public neutron endpoint", func() { + "caBundleSecretName": CABundleSecretName, + } + + serviceOverride := map[string]interface{}{} + serviceOverride["public"] = map[string]interface{}{ + "endpointURL": "https://neutron-openstack.apps-crc.testing", + } + + spec["override"] = map[string]interface{}{ + "service": serviceOverride, + } + + DeferCleanup(th.DeleteInstance, CreateNeutronAPI(neutronAPIName.Namespace, neutronAPIName.Name, spec)) + + DeferCleanup(k8sClient.Delete, ctx, th.CreateCABundleSecret(caBundleSecretName)) + DeferCleanup(k8sClient.Delete, ctx, th.CreateCertSecret(internalCertSecretName)) + DeferCleanup(k8sClient.Delete, ctx, th.CreateCertSecret(publicCertSecretName)) + DeferCleanup(k8sClient.Delete, ctx, CreateNeutronAPISecret(namespace, SecretName)) + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + namespace, + GetNeutronAPI(neutronAPIName).Spec.DatabaseInstance, + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) + SimulateTransportURLReady(apiTransportURLName) + DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, "memcached", memcachedSpec)) + infra.SimulateMemcachedReady(memcachedName) + DeferCleanup(DeleteOVNDBClusters, CreateOVNDBClusters(namespace)) + DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(namespace)) + mariadb.SimulateMariaDBAccountCompleted(types.NamespacedName{Namespace: namespace, Name: GetNeutronAPI(neutronAPIName).Spec.DatabaseAccount}) + mariadb.SimulateMariaDBDatabaseCompleted(types.NamespacedName{Namespace: namespace, Name: neutronapi.DatabaseCRName}) + th.SimulateJobSuccess(types.NamespacedName{Namespace: namespace, Name: neutronAPIName.Name + "-db-sync"}) + keystone.SimulateKeystoneServiceReady(types.NamespacedName{Namespace: namespace, Name: "neutron"}) + keystone.SimulateKeystoneEndpointReady(types.NamespacedName{Namespace: namespace, Name: "neutron"}) + }) + + It("registers endpointURL as public neutron endpoint", func() { - th.ExpectCondition( - neutronAPIName, - ConditionGetterFunc(NeutronAPIConditionGetter), - condition.KeystoneEndpointReadyCondition, - corev1.ConditionTrue, - ) - keystoneEndpoint := keystone.GetKeystoneEndpoint(types.NamespacedName{Namespace: namespace, Name: "neutron"}) - endpoints := keystoneEndpoint.Spec.Endpoints - Expect(endpoints).To(HaveKeyWithValue("public", "https://neutron-openstack.apps-crc.testing")) - Expect(endpoints).To(HaveKeyWithValue("internal", "https://neutron-internal."+neutronAPIName.Namespace+".svc:9696")) + th.ExpectCondition( + neutronAPIName, + ConditionGetterFunc(NeutronAPIConditionGetter), + condition.KeystoneEndpointReadyCondition, + corev1.ConditionTrue, + ) + keystoneEndpoint := keystone.GetKeystoneEndpoint(types.NamespacedName{Namespace: namespace, Name: "neutron"}) + endpoints := keystoneEndpoint.Spec.Endpoints + Expect(endpoints).To(HaveKeyWithValue("public", "https://neutron-openstack.apps-crc.testing")) + Expect(endpoints).To(HaveKeyWithValue("internal", "https://neutron-internal."+neutronAPIName.Namespace+".svc:9696")) + }) }) - }) - - // Run MariaDBAccount suite tests. these are pre-packaged ginkgo tests - // that exercise standard account create / update patterns that should be - // common to all controllers that ensure MariaDBAccount CRs. - mariadbSuite := &mariadb_test.MariaDBTestHarness{ - PopulateHarness: func(harness *mariadb_test.MariaDBTestHarness) { - harness.Setup( - "Neutron API", - neutronAPIName.Namespace, - neutronapi.Database, - "openstack.org/neutronapi", - mariadb, - timeout, - interval, - ) - }, - // Generate a fully running Neutron service given an accountName - // needs to make it all the way to the end where the mariadb finalizers - // are removed from unused accounts since that's part of what we are testing - SetupCR: func(accountName types.NamespacedName) { - - spec["databaseAccount"] = accountName.Name - DeferCleanup(th.DeleteInstance, CreateNeutronAPI(neutronAPIName.Namespace, neutronAPIName.Name, spec)) - DeferCleanup(DeleteOVNDBClusters, CreateOVNDBClusters(namespace)) + // Run MariaDBAccount suite tests. these are pre-packaged ginkgo tests + // that exercise standard account create / update patterns that should be + // common to all controllers that ensure MariaDBAccount CRs. + mariadbSuite := &mariadb_test.MariaDBTestHarness{ + PopulateHarness: func(harness *mariadb_test.MariaDBTestHarness) { + harness.Setup( + "Neutron API", + neutronAPIName.Namespace, + neutronapi.Database, + "openstack.org/neutronapi", + mariadb, + timeout, + interval, + ) + }, + // Generate a fully running Neutron service given an accountName + // needs to make it all the way to the end where the mariadb finalizers + // are removed from unused accounts since that's part of what we are testing + SetupCR: func(accountName types.NamespacedName) { - secret = &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: SecretName, - Namespace: GetNeutronAPI(neutronAPIName).Namespace, - }, - Data: map[string][]byte{ - "NeutronPassword": []byte("12345678"), - "transport_url": []byte("rabbit://user@svc:1234"), - }, - } - Expect(k8sClient.Create(ctx, secret)).Should(Succeed()) - SimulateTransportURLReady(apiTransportURLName) - DeferCleanup(k8sClient.Delete, ctx, secret) - DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, "memcached", memcachedSpec)) - infra.SimulateMemcachedReady(memcachedName) + spec["databaseAccount"] = accountName.Name - keystoneAPI := keystone.CreateKeystoneAPI(namespace) - DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPI) + DeferCleanup(th.DeleteInstance, CreateNeutronAPI(neutronAPIName.Namespace, neutronAPIName.Name, spec)) + DeferCleanup(DeleteOVNDBClusters, CreateOVNDBClusters(namespace)) - DeferCleanup( - mariadb.DeleteDBService, - mariadb.CreateDBService( - namespace, - GetNeutronAPI(neutronAPIName).Spec.DatabaseInstance, - corev1.ServiceSpec{ - Ports: []corev1.ServicePort{{Port: 3306}}, + secret = &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: SecretName, + Namespace: GetNeutronAPI(neutronAPIName).Namespace, }, - ), - ) - - mariadb.SimulateMariaDBAccountCompleted(accountName) - mariadb.SimulateMariaDBDatabaseCompleted(types.NamespacedName{Namespace: namespace, Name: neutronapi.DatabaseCRName}) - th.SimulateJobSuccess(types.NamespacedName{Namespace: namespace, Name: neutronAPIName.Name + "-db-sync"}) - keystone.SimulateKeystoneServiceReady(types.NamespacedName{Namespace: namespace, Name: "neutron"}) - keystone.SimulateKeystoneEndpointReady(types.NamespacedName{Namespace: namespace, Name: "neutron"}) - deplName := types.NamespacedName{ - Namespace: namespace, - Name: "neutron", - } - - th.SimulateDeploymentReadyWithPods( - deplName, - map[string][]string{namespace + "/internalapi": {}}, - ) + Data: map[string][]byte{ + "NeutronPassword": []byte("12345678"), + "transport_url": []byte("rabbit://user@svc:1234"), + }, + } + Expect(k8sClient.Create(ctx, secret)).Should(Succeed()) + SimulateTransportURLReady(apiTransportURLName) + DeferCleanup(k8sClient.Delete, ctx, secret) + DeferCleanup(infra.DeleteMemcached, infra.CreateMemcached(namespace, "memcached", memcachedSpec)) + infra.SimulateMemcachedReady(memcachedName) + + keystoneAPI := keystone.CreateKeystoneAPI(namespace) + DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPI) + + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + namespace, + GetNeutronAPI(neutronAPIName).Spec.DatabaseInstance, + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) - // ensure deployment is fully ready; old account finalizers aren't - // removed until we get here - th.ExpectCondition( - neutronAPIName, - ConditionGetterFunc(NeutronAPIConditionGetter), - condition.DeploymentReadyCondition, - corev1.ConditionTrue, - ) + mariadb.SimulateMariaDBAccountCompleted(accountName) + mariadb.SimulateMariaDBDatabaseCompleted(types.NamespacedName{Namespace: namespace, Name: neutronapi.DatabaseCRName}) + th.SimulateJobSuccess(types.NamespacedName{Namespace: namespace, Name: neutronAPIName.Name + "-db-sync"}) + keystone.SimulateKeystoneServiceReady(types.NamespacedName{Namespace: namespace, Name: "neutron"}) + keystone.SimulateKeystoneEndpointReady(types.NamespacedName{Namespace: namespace, Name: "neutron"}) + deplName := types.NamespacedName{ + Namespace: namespace, + Name: "neutron", + } - }, - // update to a new account name - UpdateAccount: func(newAccountName types.NamespacedName) { - Eventually(func(g Gomega) { - NeutronAPI := GetNeutronAPI(neutronAPIName) - NeutronAPI.Spec.DatabaseAccount = newAccountName.Name - g.Expect(th.K8sClient.Update(ctx, NeutronAPI)).Should(Succeed()) - }, timeout, interval).Should(Succeed()) - - }, - // delete NeutronAPI to exercise finalizer removal - DeleteCR: func() { - th.DeleteInstance(GetNeutronAPI(neutronAPIName)) - }, - } + th.SimulateDeploymentReadyWithPods( + deplName, + map[string][]string{namespace + "/internalapi": {}}, + ) - mariadbSuite.RunBasicSuite() + // ensure deployment is fully ready; old account finalizers aren't + // removed until we get here + th.ExpectCondition( + neutronAPIName, + ConditionGetterFunc(NeutronAPIConditionGetter), + condition.DeploymentReadyCondition, + corev1.ConditionTrue, + ) - mariadbSuite.RunURLAssertSuite(func(_ types.NamespacedName, username string, password string) { - Eventually(func(g Gomega) { - secret := types.NamespacedName{ - Namespace: neutronAPIName.Namespace, - Name: fmt.Sprintf("%s-%s", neutronAPIName.Name, "config"), - } + }, + // update to a new account name + UpdateAccount: func(newAccountName types.NamespacedName) { + Eventually(func(g Gomega) { + NeutronAPI := GetNeutronAPI(neutronAPIName) + NeutronAPI.Spec.DatabaseAccount = newAccountName.Name + g.Expect(th.K8sClient.Update(ctx, NeutronAPI)).Should(Succeed()) + }, timeout, interval).Should(Succeed()) - configData := th.GetSecret(secret) - conf := string(configData.Data["01-neutron.conf"]) + }, + // delete NeutronAPI to exercise finalizer removal + DeleteCR: func() { + th.DeleteInstance(GetNeutronAPI(neutronAPIName)) + }, + } - g.Expect(conf).Should( - ContainSubstring( - fmt.Sprintf( - "connection=mysql+pymysql://%s:%s@hostname-for-test-neutron-db-instance.%s.svc/neutron?read_default_file=/etc/my.cnf", - username, - password, - namespace, - ))) - }).Should(Succeed()) - }) + mariadbSuite.RunBasicSuite() - mariadbSuite.RunConfigHashSuite(func() string { - return GetEnvVarValue( - th.GetDeployment( - types.NamespacedName{ + mariadbSuite.RunURLAssertSuite(func(_ types.NamespacedName, username string, password string) { + Eventually(func(g Gomega) { + secret := types.NamespacedName{ Namespace: neutronAPIName.Namespace, - Name: "neutron", - }, - ).Spec.Template.Spec.Containers[0].Env, "CONFIG_HASH", "") - }) + Name: fmt.Sprintf("%s-%s", neutronAPIName.Name, "config"), + } + + configData := th.GetSecret(secret) + conf := string(configData.Data["01-neutron.conf"]) + + g.Expect(conf).Should( + ContainSubstring( + fmt.Sprintf( + "connection=mysql+pymysql://%s:%s@hostname-for-test-neutron-db-instance.%s.svc/neutron?read_default_file=/etc/my.cnf", + username, + password, + namespace, + ))) + }).Should(Succeed()) + }) -}) + mariadbSuite.RunConfigHashSuite(func() string { + return GetEnvVarValue( + th.GetDeployment( + types.NamespacedName{ + Namespace: neutronAPIName.Namespace, + Name: "neutron", + }, + ).Spec.Template.Spec.Containers[0].Env, "CONFIG_HASH", "") + }) + } +} + +var _ = Describe("NeutronAPI controller (ovn, implicit)", getNeutronAPIControllerSuite([]string{})) +var _ = Describe("NeutronAPI controller (ovn, explicit)", getNeutronAPIControllerSuite([]string{"ovn"})) +var _ = Describe("NeutronAPI controller (ovn, openvswitch)", getNeutronAPIControllerSuite([]string{"ovn", "openvswitch"})) +var _ = Describe("NeutronAPI controller (openvswitch)", getNeutronAPIControllerSuite([]string{"openvswitch"})) var _ = Describe("NeutronAPI Webhook", func() { diff --git a/test/kuttl/tests/neutron_no_ovn/01-assert.yaml b/test/kuttl/tests/neutron_no_ovn/01-assert.yaml new file mode 100644 index 00000000..daab3252 --- /dev/null +++ b/test/kuttl/tests/neutron_no_ovn/01-assert.yaml @@ -0,0 +1,30 @@ +apiVersion: neutron.openstack.org/v1beta1 +kind: NeutronAPI +metadata: + finalizers: + - openstack.org/neutronapi + name: neutron +spec: + replicas: 1 + ml2MechanismDrivers: + - openvswitch +status: + readyCount: 1 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: neutron +spec: + replicas: 1 +status: + availableReplicas: 1 + readyReplicas: 1 + replicas: 1 + updatedReplicas: 1 +--- +apiVersion: v1 +kind: Pod +metadata: + labels: + service: neutron diff --git a/test/kuttl/tests/neutron_no_ovn/01-deploy-neutron.yaml b/test/kuttl/tests/neutron_no_ovn/01-deploy-neutron.yaml new file mode 100644 index 00000000..75f76ef1 --- /dev/null +++ b/test/kuttl/tests/neutron_no_ovn/01-deploy-neutron.yaml @@ -0,0 +1,6 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: | + cp ../../../../config/samples/neutron_v1beta1_neutronapi_openvswitch.yaml deploy + oc kustomize deploy | oc apply -n $NAMESPACE -f - diff --git a/test/kuttl/tests/neutron_no_ovn/02-cleanup-neutron.yaml b/test/kuttl/tests/neutron_no_ovn/02-cleanup-neutron.yaml new file mode 120000 index 00000000..553f02bd --- /dev/null +++ b/test/kuttl/tests/neutron_no_ovn/02-cleanup-neutron.yaml @@ -0,0 +1 @@ +../../common/cleanup-neutron.yaml \ No newline at end of file diff --git a/test/kuttl/tests/neutron_no_ovn/02-errors.yaml b/test/kuttl/tests/neutron_no_ovn/02-errors.yaml new file mode 120000 index 00000000..ad072677 --- /dev/null +++ b/test/kuttl/tests/neutron_no_ovn/02-errors.yaml @@ -0,0 +1 @@ +../../common/errors_cleanup_neutron.yaml \ No newline at end of file diff --git a/test/kuttl/tests/neutron_no_ovn/deploy/kustomization.yaml b/test/kuttl/tests/neutron_no_ovn/deploy/kustomization.yaml new file mode 100644 index 00000000..b0b03583 --- /dev/null +++ b/test/kuttl/tests/neutron_no_ovn/deploy/kustomization.yaml @@ -0,0 +1,13 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: +- ./neutron_v1beta1_neutronapi_openvswitch.yaml +patches: +- patch: |- + - op: replace + path: /spec/secret + value: osp-secret + - op: remove + path: /metadata/namespace + target: + kind: NeutronAPI