diff --git a/api/bases/ovn.openstack.org_ovndbclusters.yaml b/api/bases/ovn.openstack.org_ovndbclusters.yaml index 845cc2f3..aeac0666 100644 --- a/api/bases/ovn.openstack.org_ovndbclusters.yaml +++ b/api/bases/ovn.openstack.org_ovndbclusters.yaml @@ -83,6 +83,171 @@ 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. The key must be the endpoint type + (public, internal) + 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..a9bc9492 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,23 @@ 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 OverrideSpec `json:"override,omitempty"` +} + +// OverrideSpec to override the generated manifest of several child resources. +type OverrideSpec struct { + // Override configuration for the Service created to serve traffic to the cluster. + // The key must be the endpoint type (public, internal) + Service RoutedOverrideSpec `json:"service,omitempty"` +} + +// RoutedOverrideSpec - a routed service override configuration for the Service created to serve traffic +// to the cluster. Allows for the manifest of the created Service to be overwritten with custom configuration. +type RoutedOverrideSpec struct { + service.OverrideSpec `json:",inline"` } // 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..b9653f5b 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -299,6 +299,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. @@ -525,3 +526,35 @@ func (in *OVSExternalIDs) DeepCopy() *OVSExternalIDs { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OverrideSpec) DeepCopyInto(out *OverrideSpec) { + *out = *in + in.Service.DeepCopyInto(&out.Service) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OverrideSpec. +func (in *OverrideSpec) DeepCopy() *OverrideSpec { + if in == nil { + return nil + } + out := new(OverrideSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RoutedOverrideSpec) DeepCopyInto(out *RoutedOverrideSpec) { + *out = *in + in.OverrideSpec.DeepCopyInto(&out.OverrideSpec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoutedOverrideSpec. +func (in *RoutedOverrideSpec) DeepCopy() *RoutedOverrideSpec { + if in == nil { + return nil + } + out := new(RoutedOverrideSpec) + in.DeepCopyInto(out) + return out +} diff --git a/config/crd/bases/ovn.openstack.org_ovndbclusters.yaml b/config/crd/bases/ovn.openstack.org_ovndbclusters.yaml index 845cc2f3..aeac0666 100644 --- a/config/crd/bases/ovn.openstack.org_ovndbclusters.yaml +++ b/config/crd/bases/ovn.openstack.org_ovndbclusters.yaml @@ -83,6 +83,171 @@ 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. The key must be the endpoint type + (public, internal) + 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..2e47a813 100644 --- a/controllers/ovndbcluster_controller.go +++ b/controllers/ovndbcluster_controller.go @@ -670,135 +670,222 @@ 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 { + if instance.Spec.Override.Service.EmbeddedLabelsAnnotations == nil && instance.Spec.Override.Service.Spec == 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 } + // 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{} + } + if svcOverride.Spec == nil { + svcOverride.Spec = &service.OverrideServiceSpec{} + } + + // Create the service + svc, err := service.NewService( + ovndbcluster.Service(serviceName, instance, serviceLabels, serviceLabels), + time.Duration(5)*time.Second, + &svcOverride.OverrideSpec, + ) + if err != nil { + return ctrl.Result{}, err + } + + /* + svc, err := service.NewService( + service.GenericService(&service.GenericServiceDetails{ + Name: serviceName, + Namespace: instance.Namespace, + Labels: serviceLabels, + Selector: serviceLabels, + Port: service.GenericServicePort{ + Name: serviceName, + Port: data.Port, + Protocol: corev1.ProtocolTCP, + }, + }), + 5, + &svcOverride.OverrideSpec, + ) - dnsIP, err := getPodIPInNetwork(ovnPod, instance.Namespace, instance.Spec.NetworkAttachment) - dnsIPsList = append(dnsIPsList, dnsIP) if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.CreateServiceReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.CreateServiceReadyErrorMessage, + err.Error())) + return ctrl.Result{}, err } + */ + // add annotation to register service name in dnsmasq + if svc.GetServiceType() == corev1.ServiceTypeLoadBalancer { + svc.AddAnnotation(map[string]string{ + service.AnnotationHostnameKey: svc.GetServiceHostname(), + }) } - // 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, - ) + + ctrlResult, err := svc.CreateOrPatch(ctx, helper) if err != nil { return ctrl.Result{}, err + } else if (ctrlResult != ctrl.Result{}) { + return ctrl.Result{}, nil } - // 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 - } + /* + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.CreateServiceReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.CreateServiceReadyErrorMessage, + err.Error())) + + return ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.CreateServiceReadyCondition, + condition.RequestedReason, + condition.SeverityInfo, + condition.CreateServiceReadyRunningMessage)) + return ctrlResult, nil + } + */ + // create service - end + } + // dbAddress will contain ovsdbserver-(nb|sb).openstack.svc or empty scheme := "tcp" if instance.Spec.TLS.Enabled() {