From c8e27ba73257f4e162f2d7396feab051a14333b9 Mon Sep 17 00:00:00 2001 From: Martin Schuppert Date: Thu, 31 Oct 2024 15:34:52 +0100 Subject: [PATCH] [wip] allow creating a lb service using overrides Signed-off-by: Martin Schuppert --- .../ovn.openstack.org_ovndbclusters.yaml | 164 +++++++++++++ api/v1beta1/ovndbcluster_types.go | 11 + api/v1beta1/zz_generated.deepcopy.go | 22 ++ .../ovn.openstack.org_ovndbclusters.yaml | 164 +++++++++++++ controllers/ovndbcluster_controller.go | 222 +++++++++++------- 5 files changed, 493 insertions(+), 90 deletions(-) diff --git a/api/bases/ovn.openstack.org_ovndbclusters.yaml b/api/bases/ovn.openstack.org_ovndbclusters.yaml index 845cc2f3..06e52f1b 100644 --- a/api/bases/ovn.openstack.org_ovndbclusters.yaml +++ b/api/bases/ovn.openstack.org_ovndbclusters.yaml @@ -83,6 +83,170 @@ spec: description: NodeSelector to target subset of worker nodes running this service type: object + override: + description: Override, provides the ability to override the generated + manifest of several child resources. + properties: + service: + description: Override configuration for the Service created to + serve traffic to the cluster. + properties: + metadata: + description: EmbeddedLabelsAnnotations is an embedded subset + of the fields included in k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta. + Only labels and annotations are included. + properties: + annotations: + additionalProperties: + type: string + description: 'Annotations is an unstructured key value + map stored with a resource that may be set by external + tools to store and retrieve arbitrary metadata. They + are not queryable and should be preserved when modifying + objects. More info: http://kubernetes.io/docs/user-guide/annotations' + type: object + labels: + additionalProperties: + type: string + description: 'Map of string keys and values that can be + used to organize and categorize (scope and select) objects. + May match selectors of replication controllers and services. + More info: http://kubernetes.io/docs/user-guide/labels' + type: object + type: object + spec: + description: OverrideServiceSpec is a subset of the fields + included in https://pkg.go.dev/k8s.io/api@v0.26.6/core/v1#ServiceSpec + Limited to Type, SessionAffinity, LoadBalancerSourceRanges, + ExternalName, ExternalTrafficPolicy, SessionAffinityConfig, + IPFamilyPolicy, LoadBalancerClass and InternalTrafficPolicy + properties: + externalName: + description: externalName is the external reference that + discovery mechanisms will return as an alias for this + service (e.g. a DNS CNAME record). No proxying will + be involved. Must be a lowercase RFC-1123 hostname + (https://tools.ietf.org/html/rfc1123) and requires `type` + to be "ExternalName". + type: string + externalTrafficPolicy: + description: externalTrafficPolicy describes how nodes + distribute service traffic they receive on one of the + Service's "externally-facing" addresses (NodePorts, + ExternalIPs, and LoadBalancer IPs). If set to "Local", + the proxy will configure the service in a way that assumes + that external load balancers will take care of balancing + the service traffic between nodes, and so each node + will deliver traffic only to the node-local endpoints + of the service, without masquerading the client source + IP. (Traffic mistakenly sent to a node with no endpoints + will be dropped.) The default value, "Cluster", uses + the standard behavior of routing to all endpoints evenly + (possibly modified by topology and other features). + Note that traffic sent to an External IP or LoadBalancer + IP from within the cluster will always get "Cluster" + semantics, but clients sending to a NodePort from within + the cluster may need to take traffic policy into account + when picking a node. + type: string + internalTrafficPolicy: + description: InternalTrafficPolicy describes how nodes + distribute service traffic they receive on the ClusterIP. + If set to "Local", the proxy will assume that pods only + want to talk to endpoints of the service on the same + node as the pod, dropping the traffic if there are no + local endpoints. The default value, "Cluster", uses + the standard behavior of routing to all endpoints evenly + (possibly modified by topology and other features). + type: string + ipFamilyPolicy: + description: IPFamilyPolicy represents the dual-stack-ness + requested or required by this Service. If there is no + value provided, then this field will be set to SingleStack. + Services can be "SingleStack" (a single IP family), + "PreferDualStack" (two IP families on dual-stack configured + clusters or a single IP family on single-stack clusters), + or "RequireDualStack" (two IP families on dual-stack + configured clusters, otherwise fail). The ipFamilies + and clusterIPs fields depend on the value of this field. + This field will be wiped when updating a service to + type ExternalName. + type: string + loadBalancerClass: + description: loadBalancerClass is the class of the load + balancer implementation this Service belongs to. If + specified, the value of this field must be a label-style + identifier, with an optional prefix, e.g. "internal-vip" + or "example.com/internal-vip". Unprefixed names are + reserved for end-users. This field can only be set when + the Service type is 'LoadBalancer'. If not set, the + default load balancer implementation is used, today + this is typically done through the cloud provider integration, + but should apply for any default implementation. If + set, it is assumed that a load balancer implementation + is watching for Services with a matching class. Any + default load balancer implementation (e.g. cloud providers) + should ignore Services that set this field. This field + can only be set when creating or updating a Service + to type 'LoadBalancer'. Once set, it can not be changed. + This field will be wiped when a service is updated to + a non 'LoadBalancer' type. + type: string + loadBalancerSourceRanges: + description: 'If specified and supported by the platform, + this will restrict traffic through the cloud-provider + load-balancer will be restricted to the specified client + IPs. This field will be ignored if the cloud-provider + does not support the feature." More info: https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/' + items: + type: string + type: array + sessionAffinity: + description: 'Supports "ClientIP" and "None". Used to + maintain session affinity. Enable client IP based session + affinity. Must be ClientIP or None. Defaults to None. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies' + type: string + sessionAffinityConfig: + description: sessionAffinityConfig contains the configurations + of session affinity. + properties: + clientIP: + description: clientIP contains the configurations + of Client IP based session affinity. + properties: + timeoutSeconds: + description: timeoutSeconds specifies the seconds + of ClientIP type session sticky time. The value + must be >0 && <=86400(for 1 day) if ServiceAffinity + == "ClientIP". Default value is 10800(for 3 + hours). + format: int32 + type: integer + type: object + type: object + type: + description: 'type determines how the Service is exposed. + Defaults to ClusterIP. Valid options are ExternalName, + ClusterIP, NodePort, and LoadBalancer. "ClusterIP" allocates + a cluster-internal IP address for load-balancing to + endpoints. Endpoints are determined by the selector + or if that is not specified, by manual construction + of an Endpoints object or EndpointSlice objects. If + clusterIP is "None", no virtual IP is allocated and + the endpoints are published as a set of endpoints rather + than a virtual IP. "NodePort" builds on ClusterIP and + allocates a port on every node which routes to the same + endpoints as the clusterIP. "LoadBalancer" builds on + NodePort and creates an external load-balancer (if supported + in the current cloud) which routes to the same endpoints + as the clusterIP. "ExternalName" aliases this service + to the specified externalName. Several other fields + do not apply to ExternalName services. More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types' + type: string + type: object + type: object + type: object probeIntervalToActive: default: 60000 description: Active probe interval from standby to active ovsdb-server diff --git a/api/v1beta1/ovndbcluster_types.go b/api/v1beta1/ovndbcluster_types.go index 91a31d61..a915d444 100644 --- a/api/v1beta1/ovndbcluster_types.go +++ b/api/v1beta1/ovndbcluster_types.go @@ -20,6 +20,7 @@ import ( "fmt" "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + "github.com/openstack-k8s-operators/lib-common/modules/common/service" "github.com/openstack-k8s-operators/lib-common/modules/common/tls" corev1 "k8s.io/api/core/v1" @@ -122,6 +123,16 @@ type OVNDBClusterSpecCore struct { // +operator-sdk:csv:customresourcedefinitions:type=spec // TLS - Parameters related to TLS TLS tls.SimpleService `json:"tls,omitempty"` + + // +kubebuilder:validation:Optional + // Override, provides the ability to override the generated manifest of several child resources. + Override OVNDBClusterOverrideSpec `json:"override,omitempty"` +} + +// OVNDBClusterOverrideSpec to override the generated manifest of several child resources. +type OVNDBClusterOverrideSpec struct { + // Override configuration for the Service created to serve traffic to the cluster. + Service *service.OverrideSpec `json:"service,omitempty"` } // OVNDBClusterStatus defines the observed state of OVNDBCluster diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index dd193ae3..71d7a349 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -23,6 +23,7 @@ package v1beta1 import ( "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + "github.com/openstack-k8s-operators/lib-common/modules/common/service" "k8s.io/apimachinery/pkg/runtime" ) @@ -266,6 +267,26 @@ func (in *OVNDBClusterList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OVNDBClusterOverrideSpec) DeepCopyInto(out *OVNDBClusterOverrideSpec) { + *out = *in + if in.Service != nil { + in, out := &in.Service, &out.Service + *out = new(service.OverrideSpec) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OVNDBClusterOverrideSpec. +func (in *OVNDBClusterOverrideSpec) DeepCopy() *OVNDBClusterOverrideSpec { + if in == nil { + return nil + } + out := new(OVNDBClusterOverrideSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OVNDBClusterSpec) DeepCopyInto(out *OVNDBClusterSpec) { *out = *in @@ -299,6 +320,7 @@ func (in *OVNDBClusterSpecCore) DeepCopyInto(out *OVNDBClusterSpecCore) { } in.Resources.DeepCopyInto(&out.Resources) in.TLS.DeepCopyInto(&out.TLS) + in.Override.DeepCopyInto(&out.Override) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OVNDBClusterSpecCore. diff --git a/config/crd/bases/ovn.openstack.org_ovndbclusters.yaml b/config/crd/bases/ovn.openstack.org_ovndbclusters.yaml index 845cc2f3..06e52f1b 100644 --- a/config/crd/bases/ovn.openstack.org_ovndbclusters.yaml +++ b/config/crd/bases/ovn.openstack.org_ovndbclusters.yaml @@ -83,6 +83,170 @@ spec: description: NodeSelector to target subset of worker nodes running this service type: object + override: + description: Override, provides the ability to override the generated + manifest of several child resources. + properties: + service: + description: Override configuration for the Service created to + serve traffic to the cluster. + properties: + metadata: + description: EmbeddedLabelsAnnotations is an embedded subset + of the fields included in k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta. + Only labels and annotations are included. + properties: + annotations: + additionalProperties: + type: string + description: 'Annotations is an unstructured key value + map stored with a resource that may be set by external + tools to store and retrieve arbitrary metadata. They + are not queryable and should be preserved when modifying + objects. More info: http://kubernetes.io/docs/user-guide/annotations' + type: object + labels: + additionalProperties: + type: string + description: 'Map of string keys and values that can be + used to organize and categorize (scope and select) objects. + May match selectors of replication controllers and services. + More info: http://kubernetes.io/docs/user-guide/labels' + type: object + type: object + spec: + description: OverrideServiceSpec is a subset of the fields + included in https://pkg.go.dev/k8s.io/api@v0.26.6/core/v1#ServiceSpec + Limited to Type, SessionAffinity, LoadBalancerSourceRanges, + ExternalName, ExternalTrafficPolicy, SessionAffinityConfig, + IPFamilyPolicy, LoadBalancerClass and InternalTrafficPolicy + properties: + externalName: + description: externalName is the external reference that + discovery mechanisms will return as an alias for this + service (e.g. a DNS CNAME record). No proxying will + be involved. Must be a lowercase RFC-1123 hostname + (https://tools.ietf.org/html/rfc1123) and requires `type` + to be "ExternalName". + type: string + externalTrafficPolicy: + description: externalTrafficPolicy describes how nodes + distribute service traffic they receive on one of the + Service's "externally-facing" addresses (NodePorts, + ExternalIPs, and LoadBalancer IPs). If set to "Local", + the proxy will configure the service in a way that assumes + that external load balancers will take care of balancing + the service traffic between nodes, and so each node + will deliver traffic only to the node-local endpoints + of the service, without masquerading the client source + IP. (Traffic mistakenly sent to a node with no endpoints + will be dropped.) The default value, "Cluster", uses + the standard behavior of routing to all endpoints evenly + (possibly modified by topology and other features). + Note that traffic sent to an External IP or LoadBalancer + IP from within the cluster will always get "Cluster" + semantics, but clients sending to a NodePort from within + the cluster may need to take traffic policy into account + when picking a node. + type: string + internalTrafficPolicy: + description: InternalTrafficPolicy describes how nodes + distribute service traffic they receive on the ClusterIP. + If set to "Local", the proxy will assume that pods only + want to talk to endpoints of the service on the same + node as the pod, dropping the traffic if there are no + local endpoints. The default value, "Cluster", uses + the standard behavior of routing to all endpoints evenly + (possibly modified by topology and other features). + type: string + ipFamilyPolicy: + description: IPFamilyPolicy represents the dual-stack-ness + requested or required by this Service. If there is no + value provided, then this field will be set to SingleStack. + Services can be "SingleStack" (a single IP family), + "PreferDualStack" (two IP families on dual-stack configured + clusters or a single IP family on single-stack clusters), + or "RequireDualStack" (two IP families on dual-stack + configured clusters, otherwise fail). The ipFamilies + and clusterIPs fields depend on the value of this field. + This field will be wiped when updating a service to + type ExternalName. + type: string + loadBalancerClass: + description: loadBalancerClass is the class of the load + balancer implementation this Service belongs to. If + specified, the value of this field must be a label-style + identifier, with an optional prefix, e.g. "internal-vip" + or "example.com/internal-vip". Unprefixed names are + reserved for end-users. This field can only be set when + the Service type is 'LoadBalancer'. If not set, the + default load balancer implementation is used, today + this is typically done through the cloud provider integration, + but should apply for any default implementation. If + set, it is assumed that a load balancer implementation + is watching for Services with a matching class. Any + default load balancer implementation (e.g. cloud providers) + should ignore Services that set this field. This field + can only be set when creating or updating a Service + to type 'LoadBalancer'. Once set, it can not be changed. + This field will be wiped when a service is updated to + a non 'LoadBalancer' type. + type: string + loadBalancerSourceRanges: + description: 'If specified and supported by the platform, + this will restrict traffic through the cloud-provider + load-balancer will be restricted to the specified client + IPs. This field will be ignored if the cloud-provider + does not support the feature." More info: https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/' + items: + type: string + type: array + sessionAffinity: + description: 'Supports "ClientIP" and "None". Used to + maintain session affinity. Enable client IP based session + affinity. Must be ClientIP or None. Defaults to None. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies' + type: string + sessionAffinityConfig: + description: sessionAffinityConfig contains the configurations + of session affinity. + properties: + clientIP: + description: clientIP contains the configurations + of Client IP based session affinity. + properties: + timeoutSeconds: + description: timeoutSeconds specifies the seconds + of ClientIP type session sticky time. The value + must be >0 && <=86400(for 1 day) if ServiceAffinity + == "ClientIP". Default value is 10800(for 3 + hours). + format: int32 + type: integer + type: object + type: object + type: + description: 'type determines how the Service is exposed. + Defaults to ClusterIP. Valid options are ExternalName, + ClusterIP, NodePort, and LoadBalancer. "ClusterIP" allocates + a cluster-internal IP address for load-balancing to + endpoints. Endpoints are determined by the selector + or if that is not specified, by manual construction + of an Endpoints object or EndpointSlice objects. If + clusterIP is "None", no virtual IP is allocated and + the endpoints are published as a set of endpoints rather + than a virtual IP. "NodePort" builds on ClusterIP and + allocates a port on every node which routes to the same + endpoints as the clusterIP. "LoadBalancer" builds on + NodePort and creates an external load-balancer (if supported + in the current cloud) which routes to the same endpoints + as the clusterIP. "ExternalName" aliases this service + to the specified externalName. Several other fields + do not apply to ExternalName services. More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types' + type: string + type: object + type: object + type: object probeIntervalToActive: default: 60000 description: Active probe interval from standby to active ovsdb-server diff --git a/controllers/ovndbcluster_controller.go b/controllers/ovndbcluster_controller.go index 4a21566a..64f93a6d 100644 --- a/controllers/ovndbcluster_controller.go +++ b/controllers/ovndbcluster_controller.go @@ -670,135 +670,177 @@ func (r *OVNDBClusterReconciler) reconcileServices( Log.Info("Reconciling OVN DB Cluster Service") - // - // Ensure the ovndbcluster headless service Exists - // - headlessServiceLabels := util.MergeMaps(serviceLabels, map[string]string{"type": ovnv1.ServiceHeadlessType}) - - headlesssvc, err := service.NewService( - ovndbcluster.HeadlessService(serviceName, instance, headlessServiceLabels, serviceLabels), - time.Duration(5)*time.Second, - nil, - ) - if err != nil { - return ctrl.Result{}, err - } - - ctrlResult, err := headlesssvc.CreateOrPatch(ctx, helper) - if err != nil { - return ctrl.Result{}, err - } else if (ctrlResult != ctrl.Result{}) { - return ctrl.Result{}, nil - } - - podList, err := ovndbcluster.OVNDBPods(ctx, instance, helper, serviceLabels) - if err != nil { - return ctrl.Result{}, err - } + var svc *corev1.Service - for _, ovnPod := range podList.Items { + // for backward compatability keep the current behavior, at least for now + if instance.Spec.Override.Service == nil { // - // Create the ovndbcluster pod service if none exists + // Ensure the ovndbcluster headless service Exists // - ovndbSelectorLabels := map[string]string{ - common.AppSelector: serviceName, - "statefulset.kubernetes.io/pod-name": ovnPod.Name, - } - ovndbServiceLabels := util.MergeMaps(ovndbSelectorLabels, map[string]string{"type": ovnv1.ServiceClusterType}) - svc, err := service.NewService( - ovndbcluster.Service(ovnPod.Name, instance, ovndbServiceLabels, ovndbSelectorLabels), + headlessServiceLabels := util.MergeMaps(serviceLabels, map[string]string{"type": ovnv1.ServiceHeadlessType}) + + headlesssvc, err := service.NewService( + ovndbcluster.HeadlessService(serviceName, instance, headlessServiceLabels, serviceLabels), time.Duration(5)*time.Second, nil, ) if err != nil { return ctrl.Result{}, err } - ctrlResult, err := svc.CreateOrPatch(ctx, helper) + + ctrlResult, err := headlesssvc.CreateOrPatch(ctx, helper) if err != nil { return ctrl.Result{}, err } else if (ctrlResult != ctrl.Result{}) { return ctrl.Result{}, nil } - // create service - end - } - // Delete any extra services left after scale down - clusterServiceLabels := util.MergeMaps(serviceLabels, map[string]string{"type": ovnv1.ServiceClusterType}) - svcList, err := service.GetServicesListWithLabel( - ctx, - helper, - helper.GetBeforeObject().GetNamespace(), - clusterServiceLabels, - ) - if err == nil && len(svcList.Items) > int(*(instance.Spec.Replicas)) { - for i := len(svcList.Items) - 1; i >= int(*(instance.Spec.Replicas)); i-- { - fullServiceName := fmt.Sprintf("%s-%d", serviceName, i) - svcLabels := map[string]string{ + podList, err := ovndbcluster.OVNDBPods(ctx, instance, helper, serviceLabels) + if err != nil { + return ctrl.Result{}, err + } + + for _, ovnPod := range podList.Items { + // + // Create the ovndbcluster pod service if none exists + // + ovndbSelectorLabels := map[string]string{ common.AppSelector: serviceName, - "statefulset.kubernetes.io/pod-name": fullServiceName, + "statefulset.kubernetes.io/pod-name": ovnPod.Name, } - err = service.DeleteServicesWithLabel( - ctx, - helper, - instance, - svcLabels, + ovndbServiceLabels := util.MergeMaps(ovndbSelectorLabels, map[string]string{"type": ovnv1.ServiceClusterType}) + svc, err := service.NewService( + ovndbcluster.Service(ovnPod.Name, instance, ovndbServiceLabels, ovndbSelectorLabels), + time.Duration(5)*time.Second, + nil, ) if err != nil { - err = fmt.Errorf("error while deleting service with name %s: %w", fullServiceName, err) return ctrl.Result{}, err } + ctrlResult, err := svc.CreateOrPatch(ctx, helper) + if err != nil { + return ctrl.Result{}, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrl.Result{}, nil + } + // create service - end } - } - var svc *corev1.Service + // Delete any extra services left after scale down + clusterServiceLabels := util.MergeMaps(serviceLabels, map[string]string{"type": ovnv1.ServiceClusterType}) + svcList, err := service.GetServicesListWithLabel( + ctx, + helper, + helper.GetBeforeObject().GetNamespace(), + clusterServiceLabels, + ) + if err == nil && len(svcList.Items) > int(*(instance.Spec.Replicas)) { + for i := len(svcList.Items) - 1; i >= int(*(instance.Spec.Replicas)); i-- { + fullServiceName := fmt.Sprintf("%s-%d", serviceName, i) + svcLabels := map[string]string{ + common.AppSelector: serviceName, + "statefulset.kubernetes.io/pod-name": fullServiceName, + } + err = service.DeleteServicesWithLabel( + ctx, + helper, + instance, + svcLabels, + ) + if err != nil { + err = fmt.Errorf("error while deleting service with name %s: %w", fullServiceName, err) + return ctrl.Result{}, err + } + } + } - // When the cluster is attached to an external network, create DNS record for every - // cluster member so it can be resolved from outside cluster (edpm nodes) - if instance.Spec.NetworkAttachment != "" { - var dnsIPsList []string - // TODO(averdagu): use built in Min once go1.21 is used - minLen := ovn_common.Min(len(podList.Items), int(*(instance.Spec.Replicas))) - for _, ovnPod := range podList.Items[:minLen] { - svc, err = service.GetServiceWithName( + // When the cluster is attached to an external network, create DNS record for every + // cluster member so it can be resolved from outside cluster (edpm nodes) + if instance.Spec.NetworkAttachment != "" { + var dnsIPsList []string + // TODO(averdagu): use built in Min once go1.21 is used + minLen := ovn_common.Min(len(podList.Items), int(*(instance.Spec.Replicas))) + for _, ovnPod := range podList.Items[:minLen] { + svc, err = service.GetServiceWithName( + ctx, + helper, + ovnPod.Name, + ovnPod.Namespace, + ) + if err != nil { + return ctrl.Result{}, err + } + + dnsIP, err := getPodIPInNetwork(ovnPod, instance.Namespace, instance.Spec.NetworkAttachment) + dnsIPsList = append(dnsIPsList, dnsIP) + if err != nil { + return ctrl.Result{}, err + } + + } + // DNSData info is called every reconcile loop to ensure that even if a pod gets + // restarted and it's IP has changed, the DNSData CR will have the correct info. + // If nothing changed this won't modify the current dnsmasq pod. + err = ovndbcluster.DNSData( ctx, helper, - ovnPod.Name, - ovnPod.Namespace, + serviceName, + dnsIPsList, + instance, + serviceLabels, ) if err != nil { return ctrl.Result{}, err } - - dnsIP, err := getPodIPInNetwork(ovnPod, instance.Namespace, instance.Spec.NetworkAttachment) - dnsIPsList = append(dnsIPsList, dnsIP) - if err != nil { - return ctrl.Result{}, err + // It can be possible that not all pods are ready, so DNSData won't + // have complete information, return error to retrigger reconcile loop + // Returning here instead of at the beggining of the for is done to + // expose the already created pods to other services/dataplane nodes + if len(podList.Items) < int(*(instance.Spec.Replicas)) { + Log.Info(fmt.Sprintf("not all pods are yet created, number of expected pods: %v, current pods: %v", *(instance.Spec.Replicas), len(podList.Items))) + return ctrl.Result{RequeueAfter: 1 * time.Second}, nil } + } + } else { + svcOverride := instance.Spec.Override.Service + if svcOverride.EmbeddedLabelsAnnotations == nil { + svcOverride.EmbeddedLabelsAnnotations = &service.EmbeddedLabelsAnnotations{} } - // DNSData info is called every reconcile loop to ensure that even if a pod gets - // restarted and it's IP has changed, the DNSData CR will have the correct info. - // If nothing changed this won't modify the current dnsmasq pod. - err = ovndbcluster.DNSData( - ctx, - helper, - serviceName, - dnsIPsList, - instance, - serviceLabels, + if svcOverride.Spec == nil { + svcOverride.Spec = &service.OverrideServiceSpec{} + } + + // Create the service + svcDef := ovndbcluster.Service(serviceName, instance, serviceLabels, serviceLabels) + // make sure that connections from a particular client are passed to the same Pod each time + svcDef.Spec.SessionAffinity = corev1.ServiceAffinityClientIP + svc, err := service.NewService( + svcDef, + time.Duration(5)*time.Second, + svcOverride, ) if err != nil { return ctrl.Result{}, err } - // It can be possible that not all pods are ready, so DNSData won't - // have complete information, return error to retrigger reconcile loop - // Returning here instead of at the beggining of the for is done to - // expose the already created pods to other services/dataplane nodes - if len(podList.Items) < int(*(instance.Spec.Replicas)) { - Log.Info(fmt.Sprintf("not all pods are yet created, number of expected pods: %v, current pods: %v", *(instance.Spec.Replicas), len(podList.Items))) - return ctrl.Result{RequeueAfter: 1 * time.Second}, nil + + // add annotation to register service name in dnsmasq + if svc.GetServiceType() == corev1.ServiceTypeLoadBalancer { + svc.AddAnnotation(map[string]string{ + service.AnnotationHostnameKey: svc.GetServiceHostname(), + }) + } + + ctrlResult, err := svc.CreateOrPatch(ctx, helper) + if err != nil { + return ctrl.Result{}, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrl.Result{}, nil } + + // create service - end } + // dbAddress will contain ovsdbserver-(nb|sb).openstack.svc or empty scheme := "tcp" if instance.Spec.TLS.Enabled() {