From 8766f8f4dc64b02cdf53fe91966d668f77a0928b Mon Sep 17 00:00:00 2001 From: Arnau Verdaguer Date: Wed, 25 Sep 2024 23:40:44 +0200 Subject: [PATCH] Generate External config map on OVNDBCluster Due to openstack-operator PR#1104 deployment can run without OVNController, currently it is in charge of create the External ConfigMap used by EDPM deployment. Moving the creation of the map on OVNDBCluster to ensure the map exist regardless of the setup of the enviroment. The map's name is hardcoded (ovncontroller-config) this is done on purpose, as this name is hardcoded on the openstack-operators config files. This should be addressed on the future, but for now we'll handle it this way. With this commit OVNDBCluster will start watching OVNController CR to ensure that any change made in OVNController.Spec.ExternalIDs.OvnEncapType is monitored and can update the configMap accordingly. Resolves: OSPRH-10372 --- api/v1beta1/client.go | 24 ++- config/rbac/role.yaml | 6 + controllers/ovncontroller_controller.go | 85 +------- controllers/ovndbcluster_controller.go | 85 ++++++++ controllers/ovnnorthd_controller.go | 2 +- .../config/ovsdb-config | 2 + tests/functional/base_test.go | 4 + .../ovncontroller_controller_test.go | 147 ------------- .../ovndbcluster_controller_test.go | 204 ++++++++++++++++++ 9 files changed, 326 insertions(+), 233 deletions(-) rename templates/{ovncontroller => ovndbcluster}/config/ovsdb-config (61%) diff --git a/api/v1beta1/client.go b/api/v1beta1/client.go index ecaca39c..b42f2c64 100644 --- a/api/v1beta1/client.go +++ b/api/v1beta1/client.go @@ -52,6 +52,26 @@ func getDBClusters( return ovnDBList, nil } +func GetOVNController( + ctx context.Context, + h *helper.Helper, + namespace string, +) (*OVNController, error) { + ovnControllerList := &OVNControllerList{} + listOpts := []client.ListOption{ + client.InNamespace(namespace), + } + err := h.GetClient().List(ctx, ovnControllerList, listOpts...) + if err != nil { + return nil, err + } + if len(ovnControllerList.Items) > 0 { + return &ovnControllerList.Items[0], nil + } + + return nil, nil +} + // GetDBClusterByType - return OVNDBCluster for the given dbType func GetDBClusterByType( ctx context.Context, @@ -87,8 +107,8 @@ func getItems(list client.ObjectList) []client.Object { return items } -// OVNDBClusterNamespaceMapFunc - DBCluster Watch Function -func OVNDBClusterNamespaceMapFunc(crs client.ObjectList, reader client.Reader) handler.MapFunc { +// OVNCRNamespaceMapFunc // Generic function to watch any OVN CR +func OVNCRNamespaceMapFunc(crs client.ObjectList, reader client.Reader) handler.MapFunc { return func(ctx context.Context, obj client.Object) []reconcile.Request { log := log.FromContext(ctx) result := []reconcile.Request{} diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index c2b3e8ed..7947cc3d 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -143,6 +143,12 @@ rules: - patch - update - watch +- apiGroups: + - ovn.openstack.org + resources: + - ovncontroller + verbs: + - watch - apiGroups: - ovn.openstack.org resources: diff --git a/controllers/ovncontroller_controller.go b/controllers/ovncontroller_controller.go index dffe5b11..e865a78d 100644 --- a/controllers/ovncontroller_controller.go +++ b/controllers/ovncontroller_controller.go @@ -25,7 +25,6 @@ import ( "github.com/go-logr/logr" netattdefv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" k8s_errors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" @@ -227,7 +226,7 @@ func (r *OVNControllerReconciler) SetupWithManager(mgr ctrl.Manager) error { Owns(&corev1.ServiceAccount{}). Owns(&rbacv1.Role{}). Owns(&rbacv1.RoleBinding{}). - Watches(&ovnv1.OVNDBCluster{}, handler.EnqueueRequestsFromMapFunc(ovnv1.OVNDBClusterNamespaceMapFunc(crs, mgr.GetClient()))). + Watches(&ovnv1.OVNDBCluster{}, handler.EnqueueRequestsFromMapFunc(ovnv1.OVNCRNamespaceMapFunc(crs, mgr.GetClient()))). Watches( &corev1.Secret{}, handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc), @@ -601,35 +600,10 @@ func (r *OVNControllerReconciler) reconcileNormal(ctx context.Context, instance sbCluster, err := ovnv1.GetDBClusterByType(ctx, helper, instance.Namespace, map[string]string{}, ovnv1.SBDBType) if err != nil { - Log.Info("No SB OVNDBCluster defined, deleting external ConfigMap") - cleanupConfigMapErr := r.deleteExternalConfigMaps(ctx, helper, instance) - if cleanupConfigMapErr != nil { - Log.Error(cleanupConfigMapErr, "Failed to delete external ConfigMap") - return ctrl.Result{}, cleanupConfigMapErr - } + Log.Info("No SB OVNDBCluster defined. Exiting reconcile.") return ctrl.Result{}, nil } - ep, err := sbCluster.GetExternalEndpoint() - if err != nil || ep == "" { - Log.Info("No external endpoint defined for SB OVNDBCluster, deleting external ConfigMap") - cleanupConfigMapErr := r.deleteExternalConfigMaps(ctx, helper, instance) - if cleanupConfigMapErr != nil { - Log.Error(cleanupConfigMapErr, "Failed to delete external ConfigMap") - return ctrl.Result{}, cleanupConfigMapErr - } - } - - if sbCluster.Spec.NetworkAttachment != "" { - // Create ConfigMap for external dataplane consumption - // TODO(ihar) - is there any hashing mechanism for EDP config? do we trigger deploy somehow? - err = r.generateExternalConfigMaps(ctx, helper, instance, sbCluster, &configMapVars) - if err != nil { - Log.Error(err, "Failed to generate external ConfigMap") - return ctrl.Result{}, err - } - } - // create OVN Config Job - start // Waits for OVS pods to run the configJob which basically will set config into OVS database if instance.Status.OVSNumberReady != instance.Status.DesiredNumberScheduled { @@ -719,61 +693,6 @@ func (r *OVNControllerReconciler) generateServiceConfigMaps( return configmap.EnsureConfigMaps(ctx, h, instance, cms, envVars) } -// generateExternalConfigMaps - create configmaps for external dataplane consumption -func (r *OVNControllerReconciler) generateExternalConfigMaps( - ctx context.Context, - h *helper.Helper, - instance *ovnv1.OVNController, - sbCluster *ovnv1.OVNDBCluster, - envVars *map[string]env.Setter, -) error { - // Create/update configmaps from templates - cmLabels := labels.GetLabels(instance, labels.GetGroupLabel(ovnv1.ServiceNameOVNController), map[string]string{}) - - externalEndpoint, err := sbCluster.GetExternalEndpoint() - if err != nil { - return err - } - - externalTemplateParameters := make(map[string]interface{}) - externalTemplateParameters["OVNRemote"] = externalEndpoint - externalTemplateParameters["OVNEncapType"] = instance.Spec.ExternalIDS.OvnEncapType - - cms := []util.Template{ - // EDP ConfigMap - { - Name: fmt.Sprintf("%s-config", instance.Name), - Namespace: instance.Namespace, - Type: util.TemplateTypeConfig, - InstanceType: instance.Kind, - Labels: cmLabels, - ConfigOptions: externalTemplateParameters, - }, - } - return configmap.EnsureConfigMaps(ctx, h, instance, cms, envVars) -} - -// TODO(ihar) this function could live in lib-common -// deleteExternalConfigMaps - delete obsolete configmaps for external dataplane consumption -func (r *OVNControllerReconciler) deleteExternalConfigMaps( - ctx context.Context, - h *helper.Helper, - instance *ovnv1.OVNController, -) error { - cm := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("%s-config", instance.Name), - Namespace: instance.Namespace, - }, - } - - err := h.GetClient().Delete(ctx, cm) - if err != nil && !k8s_errors.IsNotFound(err) { - return fmt.Errorf("error deleting external config map %s: %w", cm.Name, err) - } - return nil -} - // createHashOfInputHashes - creates a hash of hashes which gets added to the resources which requires a restart // if any of the input resources change, like configs, passwords, ... // diff --git a/controllers/ovndbcluster_controller.go b/controllers/ovndbcluster_controller.go index 2a0296ee..5e1d29eb 100644 --- a/controllers/ovndbcluster_controller.go +++ b/controllers/ovndbcluster_controller.go @@ -23,6 +23,7 @@ import ( "time" "github.com/go-logr/logr" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" @@ -86,6 +87,7 @@ func (r *OVNDBClusterReconciler) GetLogger(ctx context.Context) logr.Logger { } //+kubebuilder:rbac:groups=ovn.openstack.org,resources=ovndbclusters,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=ovn.openstack.org,resources=ovncontroller,verbs=watch; //+kubebuilder:rbac:groups=ovn.openstack.org,resources=ovndbclusters/status,verbs=get;update;patch //+kubebuilder:rbac:groups=ovn.openstack.org,resources=ovndbclusters/finalizers,verbs=update;patch //+kubebuilder:rbac:groups=core,resources=configmaps,verbs=get;list;watch;create;update;patch;delete; @@ -205,6 +207,7 @@ func (r *OVNDBClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request // SetupWithManager sets up the controller with the Manager. func (r *OVNDBClusterReconciler) SetupWithManager(mgr ctrl.Manager) error { + crs := &ovnv1.OVNDBClusterList{} // index caBundleSecretNameField if err := mgr.GetFieldIndexer().IndexField(context.Background(), &ovnv1.OVNDBCluster{}, caBundleSecretNameField, func(rawObj client.Object) []string { // Extract the secret name from the spec, if one is provided @@ -238,6 +241,7 @@ func (r *OVNDBClusterReconciler) SetupWithManager(mgr ctrl.Manager) error { Owns(&rbacv1.Role{}). Owns(&rbacv1.RoleBinding{}). Owns(&infranetworkv1.DNSData{}). + Watches(&ovnv1.OVNController{}, handler.EnqueueRequestsFromMapFunc(ovnv1.OVNCRNamespaceMapFunc(crs, mgr.GetClient()))). Watches( &corev1.Secret{}, handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc), @@ -374,6 +378,15 @@ func (r *OVNDBClusterReconciler) reconcileNormal(ctx context.Context, instance * err.Error())) return ctrl.Result{}, err } + } else if instance.Spec.DBType == ovnv1.SBDBType { + // This config map was created by the SB and it only needs to be deleted once + // since this reconcile loop can be done by the SB and the NB, filtering so only + // one deletes it. + Log.Info("NetworkAttachment is empty, deleting external config map") + err = r.deleteExternalConfigMaps(ctx, helper, instance.Namespace) + if err != nil { + return ctrl.Result{}, err + } } serviceAnnotations, err := nad.CreateNetworksAnnotation(instance.Namespace, networkAttachments) @@ -612,6 +625,16 @@ func (r *OVNDBClusterReconciler) reconcileNormal(ctx context.Context, instance * // Set DB Address instance.Status.InternalDBAddress = strings.Join(internalDbAddress, ",") + if instance.Spec.DBType == ovnv1.SBDBType && instance.Spec.NetworkAttachment != "" { + // This config map will populate the sb db address to edpm, can't use the nb + // If there's no networkAttachments the configMap is not needed + configMapVars := make(map[string]env.Setter) + err = r.generateExternalConfigMaps(ctx, helper, instance, serviceName, &configMapVars) + if err != nil { + Log.Info(fmt.Sprintf("Error while generating external config map: %v", err)) + } + } + } Log.Info("Reconciled Service successfully") return ctrl.Result{}, nil @@ -786,6 +809,68 @@ func (r *OVNDBClusterReconciler) reconcileServices( return ctrl.Result{}, nil } +// generateServiceConfigMaps - create create configmaps which hold service configuration +func (r *OVNDBClusterReconciler) generateExternalConfigMaps( + ctx context.Context, + h *helper.Helper, + instance *ovnv1.OVNDBCluster, + serviceName string, + envVars *map[string]env.Setter, +) error { + // Create/update configmaps from templates + cmLabels := labels.GetLabels(instance, labels.GetGroupLabel(serviceName), map[string]string{}) + log := r.GetLogger(ctx) + + externalEndpoint, err := instance.GetExternalEndpoint() + if err != nil { + return err + } + + externalTemplateParameters := make(map[string]interface{}) + externalTemplateParameters["OVNRemote"] = externalEndpoint + + ovnController, err := ovnv1.GetOVNController(ctx, h, instance.Namespace) + if err != nil { + log.Info(fmt.Sprintf("Error on getting OVNController: %v", err)) + return err + } + if ovnController != nil { + externalTemplateParameters["OVNEncapType"] = ovnController.Spec.ExternalIDS.OvnEncapType + } + + cms := []util.Template{ + // EDP ConfigMap + { + Name: "ovncontroller-config", + Namespace: instance.Namespace, + Type: util.TemplateTypeConfig, + InstanceType: instance.Kind, + Labels: cmLabels, + ConfigOptions: externalTemplateParameters, + }, + } + return configmap.EnsureConfigMaps(ctx, h, instance, cms, envVars) +} + +func (r *OVNDBClusterReconciler) deleteExternalConfigMaps( + ctx context.Context, + h *helper.Helper, + namespace string, +) error { + cm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ovncontroller-config", + Namespace: namespace, + }, + } + + err := h.GetClient().Delete(ctx, cm) + if err != nil && !k8s_errors.IsNotFound(err) { + return fmt.Errorf("error deleting external config map %s: %w", cm.Name, err) + } + return nil +} + // generateServiceConfigMaps - create create configmaps which hold service configuration func (r *OVNDBClusterReconciler) generateServiceConfigMaps( ctx context.Context, diff --git a/controllers/ovnnorthd_controller.go b/controllers/ovnnorthd_controller.go index 726a665b..baf48cf0 100644 --- a/controllers/ovnnorthd_controller.go +++ b/controllers/ovnnorthd_controller.go @@ -212,7 +212,7 @@ func (r *OVNNorthdReconciler) SetupWithManager(mgr ctrl.Manager) error { Owns(&corev1.ServiceAccount{}). Owns(&rbacv1.Role{}). Owns(&rbacv1.RoleBinding{}). - Watches(&ovnv1.OVNDBCluster{}, handler.EnqueueRequestsFromMapFunc(ovnv1.OVNDBClusterNamespaceMapFunc(crs, mgr.GetClient()))). + Watches(&ovnv1.OVNDBCluster{}, handler.EnqueueRequestsFromMapFunc(ovnv1.OVNCRNamespaceMapFunc(crs, mgr.GetClient()))). Watches( &corev1.Secret{}, handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc), diff --git a/templates/ovncontroller/config/ovsdb-config b/templates/ovndbcluster/config/ovsdb-config similarity index 61% rename from templates/ovncontroller/config/ovsdb-config rename to templates/ovndbcluster/config/ovsdb-config index 6b3b43bc..c0707d7b 100644 --- a/templates/ovncontroller/config/ovsdb-config +++ b/templates/ovndbcluster/config/ovsdb-config @@ -1,2 +1,4 @@ ovn-remote: {{ .OVNRemote }} +{{if (index . "OVNEncapType")}} ovn-encap-type: {{ .OVNEncapType }} +{{end}} diff --git a/tests/functional/base_test.go b/tests/functional/base_test.go index 0958e7d8..19aff5be 100644 --- a/tests/functional/base_test.go +++ b/tests/functional/base_test.go @@ -235,6 +235,10 @@ func CreateOVNController(namespace string, spec ovnv1.OVNControllerSpec) client. return ovn.GetOVNController(name) } +func DeleteOVNController(instance types.NamespacedName) { + th.DeleteInstance(ovn.GetOVNController(instance)) +} + func GetOVNController(name types.NamespacedName) *ovnv1.OVNController { return ovn.GetOVNController(name) } diff --git a/tests/functional/ovncontroller_controller_test.go b/tests/functional/ovncontroller_controller_test.go index e67ac9b8..abf38be8 100644 --- a/tests/functional/ovncontroller_controller_test.go +++ b/tests/functional/ovncontroller_controller_test.go @@ -85,14 +85,6 @@ var _ = Describe("OVNController controller", func() { ) }) - It("should not create an external config map", func() { - externalCM := types.NamespacedName{ - Namespace: OVNControllerName.Namespace, - Name: fmt.Sprintf("%s-%s", OVNControllerName.Name, "config"), - } - th.AssertConfigMapDoesNotExist(externalCM) - }) - It("should not create a config job", func() { daemonSetName := types.NamespacedName{ Namespace: namespace, @@ -195,17 +187,9 @@ var _ = Describe("OVNController controller", func() { ) }) - It("should not create an external config map", func() { - externalCM := types.NamespacedName{ - Namespace: OVNControllerName.Namespace, - Name: fmt.Sprintf("%s-%s", OVNControllerName.Name, "config"), - } - th.AssertConfigMapDoesNotExist(externalCM) - }) }) When("OVNDBCluster instances with networkAttachments are available", func() { - var configCM types.NamespacedName var daemonSetName types.NamespacedName var daemonSetNameOVS types.NamespacedName var dbs []types.NamespacedName @@ -231,10 +215,6 @@ var _ = Describe("OVNController controller", func() { daemonSetNameOVS, map[string][]string{}, ) - configCM = types.NamespacedName{ - Namespace: OVNControllerName.Namespace, - Name: fmt.Sprintf("%s-%s", OVNControllerName.Name, "config"), - } }) It("should create a config job", func() { @@ -254,23 +234,7 @@ var _ = Describe("OVNController controller", func() { } th.AssertJobDoesNotExist(configJob) }) - It("should create an external config map", func() { - Eventually(func() corev1.ConfigMap { - return *th.GetConfigMap(configCM) - }, timeout, interval).ShouldNot(BeNil()) - }) - It("should delete the external config map when networkAttachment is detached from SB DB", func() { - Eventually(func() corev1.ConfigMap { - return *th.GetConfigMap(configCM) - }, timeout, interval).ShouldNot(BeNil()) - Eventually(func(g Gomega) { - ovndbcluster := GetOVNDBCluster(dbs[1]) - ovndbcluster.Spec.NetworkAttachment = "" - g.Expect(k8sClient.Update(ctx, ovndbcluster)).Should(Succeed()) - }, timeout, interval).Should(Succeed()) - th.AssertConfigMapDoesNotExist(configCM) - }) }) }) @@ -464,118 +428,7 @@ var _ = Describe("OVNController controller", func() { Expect(th.GetConfigMap(scriptsCM).Data["start-vswitchd.sh"]).Should( ContainSubstring("addr show dev %s", ovncontroller.Spec.NetworkAttachment)) }) - It("should create an external ConfigMap with expected key-value pairs and OwnerReferences set", func() { - - externalCM := types.NamespacedName{ - Namespace: OVNControllerName.Namespace, - Name: fmt.Sprintf("%s-%s", OVNControllerName.Name, "config"), - } - - daemonSetName := types.NamespacedName{ - Namespace: namespace, - Name: "ovn-controller", - } - SimulateDaemonsetNumberReadyWithPods( - daemonSetName, - make(map[string][]string), - ) - daemonSetName = types.NamespacedName{ - Namespace: namespace, - Name: "ovn-controller-ovs", - } - SimulateDaemonsetNumberReadyWithPods( - daemonSetName, - map[string][]string{namespace + "/internalapi": {"10.0.0.1"}}, - ) - ExpectedExternalSBEndpoint := "tcp:ovsdbserver-sb." + namespace + ".svc:6642" - - Eventually(func() corev1.ConfigMap { - return *th.GetConfigMap(externalCM) - }, timeout, interval).ShouldNot(BeNil()) - - // Check OwnerReferences set correctly for the Config Map - Expect(th.GetConfigMap(externalCM).ObjectMeta.OwnerReferences[0].Name).To(Equal(OVNControllerName.Name)) - Expect(th.GetConfigMap(externalCM).ObjectMeta.OwnerReferences[0].Kind).To(Equal("OVNController")) - - Eventually(func(g Gomega) { - g.Expect(th.GetConfigMap(externalCM).Data["ovsdb-config"]).Should( - ContainSubstring("ovn-remote: %s", ExpectedExternalSBEndpoint)) - }, timeout, interval).Should(Succeed()) - Eventually(func(g Gomega) { - g.Expect(th.GetConfigMap(externalCM).Data["ovsdb-config"]).Should( - ContainSubstring("ovn-encap-type: %s", "geneve")) - }, timeout, interval).Should(Succeed()) - }) - - It("should delete an external ConfigMap once SB DBCluster is deleted", func() { - - externalCM := types.NamespacedName{ - Namespace: OVNControllerName.Namespace, - Name: fmt.Sprintf("%s-%s", OVNControllerName.Name, "config"), - } - - daemonSetName := types.NamespacedName{ - Namespace: namespace, - Name: "ovn-controller", - } - SimulateDaemonsetNumberReadyWithPods( - daemonSetName, - make(map[string][]string), - ) - - daemonSetNameOVS := types.NamespacedName{ - Namespace: namespace, - Name: "ovn-controller-ovs", - } - SimulateDaemonsetNumberReadyWithPods( - daemonSetNameOVS, - map[string][]string{namespace + "/internalapi": {"10.0.0.1"}}, - ) - - Eventually(func() corev1.ConfigMap { - return *th.GetConfigMap(externalCM) - }, timeout, interval).ShouldNot(BeNil()) - - DeleteOVNDBClusters(dbs) - th.AssertConfigMapDoesNotExist(externalCM) - }) - - It("should delete an external ConfigMap once SB DBCluster is detached from NAD", func() { - - externalCM := types.NamespacedName{ - Namespace: OVNControllerName.Namespace, - Name: fmt.Sprintf("%s-%s", OVNControllerName.Name, "config"), - } - daemonSetName := types.NamespacedName{ - Namespace: namespace, - Name: "ovn-controller", - } - SimulateDaemonsetNumberReadyWithPods( - daemonSetName, - make(map[string][]string), - ) - daemonSetNameOVS := types.NamespacedName{ - Namespace: namespace, - Name: "ovn-controller-ovs", - } - SimulateDaemonsetNumberReadyWithPods( - daemonSetNameOVS, - map[string][]string{namespace + "/internalapi": {"10.0.0.1"}}, - ) - - Eventually(func() corev1.ConfigMap { - return *th.GetConfigMap(externalCM) - }, timeout, interval).ShouldNot(BeNil()) - - // Detach SBCluster from NAD - Eventually(func(g Gomega) { - ovndbcluster := GetOVNDBCluster(dbs[1]) - ovndbcluster.Spec.NetworkAttachment = "" - g.Expect(k8sClient.Update(ctx, ovndbcluster)).Should(Succeed()) - }, timeout, interval).Should(Succeed()) - th.AssertConfigMapDoesNotExist(externalCM) - }) }) When("OVNController is created with missing networkAttachment", func() { diff --git a/tests/functional/ovndbcluster_controller_test.go b/tests/functional/ovndbcluster_controller_test.go index 9f98c0cd..966e4cf9 100644 --- a/tests/functional/ovndbcluster_controller_test.go +++ b/tests/functional/ovndbcluster_controller_test.go @@ -171,6 +171,14 @@ var _ = Describe("OVNDBCluster controller", func() { }, timeout, interval).Should(ContainElement("openstack.org/ovndbcluster")) }) + It("should not create an external config map", func() { + externalCM := types.NamespacedName{ + Namespace: OVNDBClusterName.Namespace, + Name: "ovncontroller-config", + } + th.AssertConfigMapDoesNotExist(externalCM) + }) + DescribeTable("should not create the config map", func(cmName string) { cm := types.NamespacedName{ @@ -251,6 +259,28 @@ var _ = Describe("OVNDBCluster controller", func() { }).Should(Succeed()) }) + + It("should create an external config map", func() { + var instance *ovnv1.OVNDBCluster + spec := GetDefaultOVNDBClusterSpec() + spec.NetworkAttachment = "internalapi" + internalAPINADName := types.NamespacedName{Namespace: namespace, Name: "internalapi"} + nad := th.CreateNetworkAttachmentDefinition(internalAPINADName) + DeferCleanup(th.DeleteInstance, nad) + dbs := CreateOVNDBClusters(namespace, map[string][]string{namespace + "/internalapi": {"10.0.0.1"}}, 1) + if GetOVNDBCluster(dbs[0]).Spec.DBType == ovnv1.SBDBType { + instance = GetOVNDBCluster(dbs[0]) + } else { + instance = GetOVNDBCluster(dbs[1]) + } + configCM := types.NamespacedName{ + Namespace: instance.GetNamespace(), + Name: "ovncontroller-config", + } + Eventually(func() corev1.ConfigMap { + return *th.GetConfigMap(configCM) + }, timeout, interval).ShouldNot(BeNil()) + }) }) When("OVNDBCluster is created with networkAttachments", func() { @@ -298,6 +328,180 @@ var _ = Describe("OVNDBCluster controller", func() { }).Should(Succeed()) }) + It("should create an external ConfigMap with expected key-value pairs and OwnerReferences set", func() { + internalAPINADName := types.NamespacedName{Namespace: namespace, Name: "internalapi"} + nad := th.CreateNetworkAttachmentDefinition(internalAPINADName) + DeferCleanup(th.DeleteInstance, nad) + + statefulSetName := types.NamespacedName{ + Namespace: namespace, + Name: "ovsdbserver-sb", + } + th.SimulateStatefulSetReplicaReadyWithPods( + statefulSetName, + map[string][]string{namespace + "/internalapi": {"10.0.0.1"}}, + ) + + externalCM := types.NamespacedName{ + Namespace: OVNDBClusterName.Namespace, + Name: "ovncontroller-config", + } + + ExpectedExternalSBEndpoint := "tcp:ovsdbserver-sb." + namespace + ".svc:6642" + + Eventually(func() corev1.ConfigMap { + return *th.GetConfigMap(externalCM) + }, timeout, interval).ShouldNot(BeNil()) + + // Check OwnerReferences set correctly for the Config Map + Expect(th.GetConfigMap(externalCM).ObjectMeta.OwnerReferences[0].Name).To(Equal(OVNDBClusterName.Name)) + Expect(th.GetConfigMap(externalCM).ObjectMeta.OwnerReferences[0].Kind).To(Equal("OVNDBCluster")) + + Eventually(func(g Gomega) { + g.Expect(th.GetConfigMap(externalCM).Data["ovsdb-config"]).Should( + ContainSubstring("ovn-remote: %s", ExpectedExternalSBEndpoint)) + }, timeout, interval).Should(Succeed()) + }) + + It("should create an external ConfigMap with ovn-encap-type if OVNController is configured", func() { + ExpectedEncapType := "vxlan" + // Spawn OVNController with vxlan as ExternalIDs.OvnEncapType + ovncontrollerSpec := GetDefaultOVNControllerSpec() + ovncontrollerSpec.ExternalIDS.OvnEncapType = ExpectedEncapType + ovnController := CreateOVNController(namespace, ovncontrollerSpec) + DeferCleanup(th.DeleteInstance, ovnController) + internalAPINADName := types.NamespacedName{Namespace: namespace, Name: "internalapi"} + nad := th.CreateNetworkAttachmentDefinition(internalAPINADName) + DeferCleanup(th.DeleteInstance, nad) + + statefulSetName := types.NamespacedName{ + Namespace: namespace, + Name: "ovsdbserver-sb", + } + th.SimulateStatefulSetReplicaReadyWithPods( + statefulSetName, + map[string][]string{namespace + "/internalapi": {"10.0.0.1"}}, + ) + + externalCM := types.NamespacedName{ + Namespace: OVNDBClusterName.Namespace, + Name: "ovncontroller-config", + } + + ExpectedExternalSBEndpoint := "tcp:ovsdbserver-sb." + namespace + ".svc:6642" + + Eventually(func() corev1.ConfigMap { + return *th.GetConfigMap(externalCM) + }, timeout, interval).ShouldNot(BeNil()) + + // Check OwnerReferences set correctly for the Config Map + Expect(th.GetConfigMap(externalCM).ObjectMeta.OwnerReferences[0].Name).To(Equal(OVNDBClusterName.Name)) + Expect(th.GetConfigMap(externalCM).ObjectMeta.OwnerReferences[0].Kind).To(Equal("OVNDBCluster")) + + Eventually(func(g Gomega) { + g.Expect(th.GetConfigMap(externalCM).Data["ovsdb-config"]).Should( + ContainSubstring("ovn-remote: %s", ExpectedExternalSBEndpoint)) + }, timeout, interval).Should(Succeed()) + Eventually(func(g Gomega) { + g.Expect(th.GetConfigMap(externalCM).Data["ovsdb-config"]).Should( + ContainSubstring("ovn-encap-type: %s", ExpectedEncapType)) + }, timeout, interval).Should(Succeed()) + }) + + It("should remove ovnEncapType if OVNController gets deleted", func() { + ExpectedEncapType := "vxlan" + // Spawn OVNController with vxlan as ExternalIDs.OvnEncapType + ovncontrollerSpec := GetDefaultOVNControllerSpec() + ovncontrollerSpec.ExternalIDS.OvnEncapType = ExpectedEncapType + ovnController := CreateOVNController(namespace, ovncontrollerSpec) + //DeferCleanup(th.DeleteInstance, ovnController) + internalAPINADName := types.NamespacedName{Namespace: namespace, Name: "internalapi"} + nad := th.CreateNetworkAttachmentDefinition(internalAPINADName) + DeferCleanup(th.DeleteInstance, nad) + + statefulSetName := types.NamespacedName{ + Namespace: namespace, + Name: "ovsdbserver-sb", + } + th.SimulateStatefulSetReplicaReadyWithPods( + statefulSetName, + map[string][]string{namespace + "/internalapi": {"10.0.0.1"}}, + ) + + externalCM := types.NamespacedName{ + Namespace: OVNDBClusterName.Namespace, + Name: "ovncontroller-config", + } + + ExpectedExternalSBEndpoint := "tcp:ovsdbserver-sb." + namespace + ".svc:6642" + + Eventually(func() corev1.ConfigMap { + return *th.GetConfigMap(externalCM) + }, timeout, interval).ShouldNot(BeNil()) + + // Check OwnerReferences set correctly for the Config Map + Expect(th.GetConfigMap(externalCM).ObjectMeta.OwnerReferences[0].Name).To(Equal(OVNDBClusterName.Name)) + Expect(th.GetConfigMap(externalCM).ObjectMeta.OwnerReferences[0].Kind).To(Equal("OVNDBCluster")) + + Eventually(func(g Gomega) { + g.Expect(th.GetConfigMap(externalCM).Data["ovsdb-config"]).Should( + ContainSubstring("ovn-remote: %s", ExpectedExternalSBEndpoint)) + }, timeout, interval).Should(Succeed()) + Eventually(func(g Gomega) { + g.Expect(th.GetConfigMap(externalCM).Data["ovsdb-config"]).Should( + ContainSubstring("ovn-encap-type: %s", ExpectedEncapType)) + }, timeout, interval).Should(Succeed()) + + // This should trigger an OVNDBCluster reconcile and update config map + // without ovn-encap-type + DeleteOVNController(types.NamespacedName{Name: ovnController.GetName(), Namespace: namespace}) + + Eventually(func() corev1.ConfigMap { + return *th.GetConfigMap(externalCM) + }, timeout, interval).ShouldNot(BeNil()) + Eventually(func(g Gomega) { + g.Expect(th.GetConfigMap(externalCM).Data["ovsdb-config"]).Should( + ContainSubstring("ovn-remote: %s", ExpectedExternalSBEndpoint)) + }, timeout, interval).Should(Succeed()) + Eventually(func(g Gomega) { + g.Expect(th.GetConfigMap(externalCM).Data["ovsdb-config"]).ShouldNot( + ContainSubstring("ovn-encap-type: %s", ExpectedEncapType)) + }, timeout, interval).Should(Succeed()) + }) + + It("should delete an external ConfigMap once SB DBCluster is detached from NAD", func() { + internalAPINADName := types.NamespacedName{Namespace: namespace, Name: "internalapi"} + nad := th.CreateNetworkAttachmentDefinition(internalAPINADName) + DeferCleanup(th.DeleteInstance, nad) + + statefulSetName := types.NamespacedName{ + Namespace: namespace, + Name: "ovsdbserver-sb", + } + th.SimulateStatefulSetReplicaReadyWithPods( + statefulSetName, + map[string][]string{namespace + "/internalapi": {"10.0.0.1"}}, + ) + + externalCM := types.NamespacedName{ + Namespace: OVNDBClusterName.Namespace, + Name: "ovncontroller-config", + } + + // Should exist externalCM + Eventually(func() corev1.ConfigMap { + return *th.GetConfigMap(externalCM) + }, timeout, interval).ShouldNot(BeNil()) + + // Detach SBCluster from NAD + Eventually(func(g Gomega) { + ovndbcluster := GetOVNDBCluster(OVNDBClusterName) + ovndbcluster.Spec.NetworkAttachment = "" + g.Expect(k8sClient.Update(ctx, ovndbcluster)).Should(Succeed()) + }, timeout, interval).Should(Succeed()) + th.AssertConfigMapDoesNotExist(externalCM) + }) + It("reports that the definition is missing", func() { th.ExpectConditionWithDetails( OVNDBClusterName,