diff --git a/api/v1/marklogiccluster_types.go b/api/v1/marklogiccluster_types.go index b27c350..d213666 100644 --- a/api/v1/marklogiccluster_types.go +++ b/api/v1/marklogiccluster_types.go @@ -91,7 +91,9 @@ type MarklogicGroups struct { // +kubebuilder:default:=1 Replicas *int32 `json:"replicas,omitempty"` // +kubebuilder:validation:Required - Name string `json:"name,omitempty"` + Name string `json:"name,omitempty"` + Labels map[string]string `json:"labels,omitempty"` + Annotations map[string]string `json:"annotations,omitempty"` // +kubebuilder:default:={name: "Default", enableXdqpSsl: true} GroupConfig *GroupConfig `json:"groupConfig,omitempty"` Image string `json:"image,omitempty"` diff --git a/api/v1/marklogicgroup_types.go b/api/v1/marklogicgroup_types.go index 0b195b9..3ed957a 100644 --- a/api/v1/marklogicgroup_types.go +++ b/api/v1/marklogicgroup_types.go @@ -28,8 +28,10 @@ import ( // MarklogicGroupSpec defines the desired state of MarklogicGroup type MarklogicGroupSpec struct { // +kubebuilder:default:=1 - Replicas *int32 `json:"replicas,omitempty"` - Name string `json:"name,omitempty"` + Replicas *int32 `json:"replicas,omitempty"` + Name string `json:"name,omitempty"` + Labels map[string]string `json:"labels,omitempty"` + Annotations map[string]string `json:"annotations,omitempty"` // +kubebuilder:default:="cluster.local" ClusterDomain string `json:"clusterDomain,omitempty"` // +kubebuilder:default:="progressofficial/marklogic-db:11.3.0-ubi-rootless" diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index ed17b6a..8ab1994 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -565,6 +565,20 @@ func (in *MarklogicGroupSpec) DeepCopyInto(out *MarklogicGroupSpec) { *out = new(int32) **out = **in } + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } if in.ImagePullSecrets != nil { in, out := &in.ImagePullSecrets, &out.ImagePullSecrets *out = make([]corev1.LocalObjectReference, len(*in)) @@ -733,6 +747,20 @@ func (in *MarklogicGroups) DeepCopyInto(out *MarklogicGroups) { *out = new(int32) **out = **in } + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } if in.GroupConfig != nil { in, out := &in.GroupConfig, &out.GroupConfig *out = new(GroupConfig) diff --git a/charts/marklogic-operator-kubernetes/templates/marklogiccluster-crd.yaml b/charts/marklogic-operator-kubernetes/templates/marklogiccluster-crd.yaml index e32fd07..da9351a 100644 --- a/charts/marklogic-operator-kubernetes/templates/marklogiccluster-crd.yaml +++ b/charts/marklogic-operator-kubernetes/templates/marklogiccluster-crd.yaml @@ -7965,6 +7965,10 @@ spec: x-kubernetes-list-type: atomic type: object type: object + annotations: + additionalProperties: + type: string + type: object groupConfig: default: enableXdqpSsl: true @@ -9331,6 +9335,10 @@ spec: isBootstrap: default: false type: boolean + labels: + additionalProperties: + type: string + type: object logCollection: properties: enabled: diff --git a/charts/marklogic-operator-kubernetes/templates/marklogicgroup-crd.yaml b/charts/marklogic-operator-kubernetes/templates/marklogicgroup-crd.yaml index f98bb1d..befd8a4 100644 --- a/charts/marklogic-operator-kubernetes/templates/marklogicgroup-crd.yaml +++ b/charts/marklogic-operator-kubernetes/templates/marklogicgroup-crd.yaml @@ -3243,6 +3243,10 @@ spec: x-kubernetes-list-type: atomic type: object type: object + annotations: + additionalProperties: + type: string + type: object auth: properties: adminPassword: @@ -3310,6 +3314,10 @@ spec: type: object x-kubernetes-map-type: atomic type: array + labels: + additionalProperties: + type: string + type: object license: properties: key: diff --git a/config/crd/bases/marklogic.progress.com_marklogicclusters.yaml b/config/crd/bases/marklogic.progress.com_marklogicclusters.yaml index 09c2783..c5b9d03 100644 --- a/config/crd/bases/marklogic.progress.com_marklogicclusters.yaml +++ b/config/crd/bases/marklogic.progress.com_marklogicclusters.yaml @@ -7987,6 +7987,10 @@ spec: x-kubernetes-list-type: atomic type: object type: object + annotations: + additionalProperties: + type: string + type: object groupConfig: default: enableXdqpSsl: true @@ -9356,6 +9360,10 @@ spec: isBootstrap: default: false type: boolean + labels: + additionalProperties: + type: string + type: object logCollection: properties: enabled: diff --git a/config/crd/bases/marklogic.progress.com_marklogicgroups.yaml b/config/crd/bases/marklogic.progress.com_marklogicgroups.yaml index dd3e351..3e17273 100644 --- a/config/crd/bases/marklogic.progress.com_marklogicgroups.yaml +++ b/config/crd/bases/marklogic.progress.com_marklogicgroups.yaml @@ -3250,6 +3250,10 @@ spec: x-kubernetes-list-type: atomic type: object type: object + annotations: + additionalProperties: + type: string + type: object auth: properties: adminPassword: @@ -3317,6 +3321,10 @@ spec: type: object x-kubernetes-map-type: atomic type: array + labels: + additionalProperties: + type: string + type: object license: properties: key: diff --git a/config/samples/complete.yaml b/config/samples/complete.yaml index 56e7233..8c5d325 100644 --- a/config/samples/complete.yaml +++ b/config/samples/complete.yaml @@ -195,6 +195,10 @@ spec: # additionalVolumeClaimTemplates: [] markLogicGroups: - name: dnode + labels: + group-level-label: "group-level-label" + annotations: + group-level-annotation: "group-level-annotation" replicas: 3 groupConfig: name: dnode diff --git a/internal/controller/marklogiccluster_controller.go b/internal/controller/marklogiccluster_controller.go index 317ed37..2766e77 100644 --- a/internal/controller/marklogiccluster_controller.go +++ b/internal/controller/marklogiccluster_controller.go @@ -31,6 +31,9 @@ import ( "github.com/go-logr/logr" marklogicv1 "github.com/marklogic/marklogic-operator-kubernetes/api/v1" "github.com/marklogic/marklogic-operator-kubernetes/pkg/k8sutil" + "reflect" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/predicate" ) // MarklogicClusterReconciler reconciles a MarklogicCluster object @@ -81,10 +84,55 @@ func (r *MarklogicClusterReconciler) Reconcile(ctx context.Context, req ctrl.Req return result, nil } +func markLogicClusterCreateUpdateDeletePredicate() predicate.Predicate { + return predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { + return true // Reconcile on create + }, + UpdateFunc: func(e event.UpdateEvent) bool { + switch e.ObjectNew.(type) { + case *marklogicv1.MarklogicCluster: + oldAnnotations := e.ObjectOld.GetAnnotations() + newAnnotations := e.ObjectNew.GetAnnotations() + delete(newAnnotations, "banzaicloud.com/last-applied") + delete(oldAnnotations, "banzaicloud.com/last-applied") + delete(newAnnotations, "kubectl.kubernetes.io/last-applied-configuration") + delete(oldAnnotations, "kubectl.kubernetes.io/last-applied-configuration") + if !reflect.DeepEqual(oldAnnotations, newAnnotations) { + return true // Reconcile if annotations have changed + } + oldLables := e.ObjectOld.GetLabels() + newLabels := e.ObjectNew.GetLabels() + if !reflect.DeepEqual(oldLables, newLabels) { + return true // Reconcile if labels have changed + } + // If annotations and labels are the same, check if the spec has changed + oldObj := e.ObjectOld.(*marklogicv1.MarklogicCluster) + // Check if the spec has changed + newObj := e.ObjectNew.(*marklogicv1.MarklogicCluster) + if !reflect.DeepEqual(oldObj.Spec, newObj.Spec) { + return true // Reconcile if spec has changed + } + default: + return false // Ignore updates for other types + } + return false // Reconcile on update of MarklogicCluster + + }, + DeleteFunc: func(e event.DeleteEvent) bool { + return true // Reconcile on delete + }, + GenericFunc: func(e event.GenericEvent) bool { + return false // Ignore generic events (optional) + }, + } +} + // SetupWithManager sets up the controller with the Manager. func (r *MarklogicClusterReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&marklogicv1.MarklogicCluster{}). + WithEventFilter(markLogicClusterCreateUpdateDeletePredicate()). Owns(&marklogicv1.MarklogicGroup{}). Complete(r) } diff --git a/internal/controller/marklogicgroup_controller.go b/internal/controller/marklogicgroup_controller.go index 820a16c..0c02ba8 100644 --- a/internal/controller/marklogicgroup_controller.go +++ b/internal/controller/marklogicgroup_controller.go @@ -19,18 +19,20 @@ package controller import ( "context" + "github.com/go-logr/logr" + marklogicv1 "github.com/marklogic/marklogic-operator-kubernetes/api/v1" + "github.com/marklogic/marklogic-operator-kubernetes/pkg/k8sutil" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/tools/record" + "reflect" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/log" - - "github.com/go-logr/logr" - marklogicv1 "github.com/marklogic/marklogic-operator-kubernetes/api/v1" - "github.com/marklogic/marklogic-operator-kubernetes/pkg/k8sutil" + "sigs.k8s.io/controller-runtime/pkg/predicate" ) // MarklogicGroupReconciler reconciles a MarklogicGroup object @@ -94,12 +96,63 @@ func (r *MarklogicGroupReconciler) Reconcile(ctx context.Context, req ctrl.Reque return result, nil } +func markLogicGroupCreateUpdateDeletePredicate() predicate.Predicate { + return predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { + return true // Reconcile on create + }, + UpdateFunc: func(e event.UpdateEvent) bool { + switch e.ObjectNew.(type) { + case *marklogicv1.MarklogicGroup: + oldAnnotations := e.ObjectOld.GetAnnotations() + newAnnotations := e.ObjectNew.GetAnnotations() + delete(newAnnotations, "banzaicloud.com/last-applied") + delete(oldAnnotations, "banzaicloud.com/last-applied") + delete(newAnnotations, "kubectl.kubernetes.io/last-applied-configuration") + delete(oldAnnotations, "kubectl.kubernetes.io/last-applied-configuration") + if !reflect.DeepEqual(oldAnnotations, newAnnotations) { + return true // Reconcile if annotations have changed + } + oldLables := e.ObjectOld.GetLabels() + newLabels := e.ObjectNew.GetLabels() + if !reflect.DeepEqual(oldLables, newLabels) { + return true // Reconcile if labels have changed + } + oldObj := e.ObjectOld.(*marklogicv1.MarklogicGroup) + newObj := e.ObjectNew.(*marklogicv1.MarklogicGroup) + if !reflect.DeepEqual(oldObj.Spec, newObj.Spec) { + return true // Reconcile if the spec has changed + } + return false + case *appsv1.StatefulSet: + return true // Reconcile on update of StatefulSet + case *corev1.Service: + oldObj := e.ObjectOld.(*corev1.Service) + newObj := e.ObjectNew.(*corev1.Service) + if !reflect.DeepEqual(oldObj.Spec, newObj.Spec) { + return true // Reconcile if the spec has changed + } + return false // Reconcile on update of Service + default: + return false // Ignore updates for other types + } + }, + DeleteFunc: func(e event.DeleteEvent) bool { + return true // Reconcile on delete + }, + GenericFunc: func(e event.GenericEvent) bool { + return false // Ignore generic events (optional) + }, + } +} + // SetupWithManager sets up the controller with the Manager. func (r *MarklogicGroupReconciler) SetupWithManager(mgr ctrl.Manager) error { builder := ctrl.NewControllerManagedBy(mgr). Named("marklogicgroup-controller"). For(&marklogicv1.MarklogicGroup{}). + WithEventFilter(markLogicGroupCreateUpdateDeletePredicate()). Owns(&appsv1.StatefulSet{}). Owns(&corev1.Service{}) diff --git a/pkg/k8sutil/common.go b/pkg/k8sutil/common.go index 61b8952..5172c80 100644 --- a/pkg/k8sutil/common.go +++ b/pkg/k8sutil/common.go @@ -54,6 +54,7 @@ func SetCommonLabels(labels map[string]string) { } func SetCommonAnnotations(annotations map[string]string) { + delete(annotations, "kubectl.kubernetes.io/last-applied-configuration") CustomAnnotations = annotations } @@ -67,6 +68,34 @@ func getSelectorLabels(name string) map[string]string { return selectorLabels } +func getHAProxySelectorLabels(name string) map[string]string { + selectorLabels := map[string]string{ + "app.kubernetes.io/name": "marklogic", + "app.kubernetes.io/instance": name, + "app.kubernetes.io/managed-by": "marklogic-operator", + "app.kubernetes.io/component": "haproxy", + } + return selectorLabels +} + +func getHAProxyLabels(name string) map[string]string { + defaultHaproxyLabels := getHAProxySelectorLabels(name) + mergedLabels := map[string]string{} + if len(CustomLabels) > 0 { + for k, v := range defaultHaproxyLabels { + mergedLabels[k] = v + } + for k, v := range CustomLabels { + if _, ok := defaultHaproxyLabels[k]; !ok { + mergedLabels[k] = v + } + } + } else { + return defaultHaproxyLabels + } + return mergedLabels +} + func getCommonLabels(name string) map[string]string { defaultLabels := getSelectorLabels(name) mergedLabels := map[string]string{} diff --git a/pkg/k8sutil/haProxy.go b/pkg/k8sutil/haProxy.go index 96c5dd5..4de8312 100644 --- a/pkg/k8sutil/haProxy.go +++ b/pkg/k8sutil/haProxy.go @@ -24,8 +24,7 @@ func (cc *ClusterContext) ReconcileHAProxy() result.ReconcileResult { logger.Info("Reconciling HAProxy Config") - labels := getCommonLabels(cr.GetObjectMeta().GetName()) - labels["app.kubernetes.io/component"] = "haproxy" + labels := getHAProxyLabels(cr.GetObjectMeta().GetName()) annotations := getCommonAnnotations() configMapName := "marklogic-haproxy" objectMeta := generateObjectMeta(configMapName, cr.Namespace, labels, annotations) @@ -36,28 +35,36 @@ func (cc *ClusterContext) ReconcileHAProxy() result.ReconcileResult { err := client.Get(cc.Ctx, nsName, configmap) data := generateHAProxyConfigMapData(cc.MarklogicCluster) configMapDef := generateHAProxyConfigMap(objectMeta, marklogicClusterAsOwner(cr), data) + haproxyDeploymentDef := cc.createHAProxyDeploymentDef(objectMeta) haproxyServiceDef := cc.generateHaproxyServiceDef(objectMeta) configmapHash := calculateHash(configMapDef.Data) if err != nil { if errors.IsNotFound(err) { logger.Info("HAProxy ConfigMap is not found, creating a new one") + if err := patch.DefaultAnnotator.SetLastAppliedAnnotation(configMapDef); err != nil { + logger.Error(err, "Failed to set last applied annotation for HAProxy ConfigMap") + } err = cc.createConfigMapForCC(configMapDef) if err != nil { logger.Info("HAProxy configmap creation is failed") return result.Error(err) } logger.Info("HAProxy configmap creation is successful") - err = cc.createHAProxyDeployment(objectMeta) + err = cc.createHAProxyDeployment(haproxyDeploymentDef) if err != nil { logger.Info("HAProxy Deployment creation is failed") return result.Error(err) } + if err := patch.DefaultAnnotator.SetLastAppliedAnnotation(haproxyServiceDef); err != nil { + logger.Error(err, "Failed to set last applied annotation for HAProxy Service") + } err = cc.createHAProxyService(haproxyServiceDef) if err != nil { logger.Info("HAProxy Service creation is failed") return result.Error(err) } - logger.Info("HAProxy Test is successful") + logger.Info("HAProxy Deployed is successful") + return result.Continue() } else { logger.Error(err, "HAProxy configmap creation is failed") return result.Error(err) @@ -73,12 +80,14 @@ func (cc *ClusterContext) ReconcileHAProxy() result.ReconcileResult { return result.Error(err) } if !patchDiff.IsEmpty() { - logger.Info("MarkLogic statefulSet spec is different from the MarkLogicGroup spec, updating the statefulSet") - logger.Info(patchDiff.String()) + logger.Info("MarkLogic HAProxy Config spec is different from previous spec, updating the HAProxy ConfigMap") configmap.Data = configMapDef.Data + if err := patch.DefaultAnnotator.SetLastAppliedAnnotation(configmap); err != nil { + logger.Error(err, "Failed to set last applied annotation for HAProxy ConfigMap") + } err := cc.Client.Update(cc.Ctx, configmap) if err != nil { - logger.Error(err, "Error updating MakrLogicGroup") + logger.Error(err, "Error updating MarkLogic HAProxy ConfigMap") return result.Error(err) } } @@ -96,9 +105,10 @@ func (cc *ClusterContext) ReconcileHAProxy() result.ReconcileResult { return result.Error(err) } if !patchDiff.IsEmpty() { - logger.Info("HAProxy Service spec is different from the MarkLogicGroup spec, updating the haproxy service") - logger.Info(patchDiff.String()) + logger.Info("HAProxy spec is different from the previous spec, updating the haproxy service") haproxyService.Spec = haproxyServiceDef.Spec + haproxyService.ObjectMeta.Labels = haproxyServiceDef.ObjectMeta.Labels + haproxyService.ObjectMeta.Annotations = haproxyServiceDef.ObjectMeta.Annotations err := cc.Client.Update(cc.Ctx, haproxyService) if err != nil { logger.Error(err, "Error updating HAProxy service") @@ -107,18 +117,23 @@ func (cc *ClusterContext) ReconcileHAProxy() result.ReconcileResult { } haproxyDeployment := &appsv1.Deployment{} - err = client.Get(cc.Ctx, types.NamespacedName{Name: "marklogic-haproxy", Namespace: cr.Namespace}, haproxyDeployment) + deployName := types.NamespacedName{Name: "marklogic-haproxy", Namespace: cr.Namespace} + err = client.Get(cc.Ctx, deployName, haproxyDeployment) if err != nil { logger.Error(err, "Failed to get HAProxy Deployment") return result.Error(err) } - if haproxyDeployment.Spec.Template.Annotations == nil { - haproxyDeployment.Spec.Template.Annotations = make(map[string]string) + patchDiff, err = patch.DefaultPatchMaker.Calculate(haproxyDeployment, haproxyDeploymentDef, + patch.IgnoreStatusFields(), + patch.IgnoreVolumeClaimTemplateTypeMetaAndStatus(), + patch.IgnoreField("kind")) + if haproxyDeploymentDef.Spec.Template.Annotations == nil { + haproxyDeploymentDef.Spec.Template.Annotations = make(map[string]string) } - if haproxyDeployment.Spec.Template.Annotations["configmap-hash"] != configmapHash { + if haproxyDeployment.Spec.Template.Annotations["configmap-hash"] != configmapHash || !patchDiff.IsEmpty() { logger.Info("HAProxy Deployment is different from the HAProxy ConfigMap, updating the Deployment") - haproxyDeployment.Spec.Template.Annotations["configmap-hash"] = configmapHash - err := client.Update(cc.Ctx, haproxyDeployment) + haproxyDeploymentDef.Spec.Template.Annotations["configmap-hash"] = configmapHash + err := client.Update(cc.Ctx, haproxyDeploymentDef) if err != nil { logger.Error(err, "Error updating HAProxy Deployment") return result.Error(err) @@ -195,12 +210,9 @@ resolvers dns return haProxyData } -// createHAproxy Deployment -func (cc *ClusterContext) createHAProxyDeployment(meta metav1.ObjectMeta) error { - logger := cc.ReqLogger - logger.Info("Creating HAProxy Deployment") - client := cc.Client +func (cc *ClusterContext) createHAProxyDeploymentDef(meta metav1.ObjectMeta) *appsv1.Deployment { cr := cc.MarklogicCluster + selectorLabels := getHAProxySelectorLabels(cr.GetObjectMeta().GetName()) ownerDef := marklogicClusterAsOwner(cr) defaultMode := int32(420) deploymentDef := &appsv1.Deployment{ @@ -213,7 +225,7 @@ func (cc *ClusterContext) createHAProxyDeployment(meta metav1.ObjectMeta) error Spec: appsv1.DeploymentSpec{ Replicas: &cr.Spec.HAProxy.ReplicaCount, Selector: &metav1.LabelSelector{ - MatchLabels: meta.Labels, + MatchLabels: selectorLabels, }, Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ @@ -277,7 +289,14 @@ func (cc *ClusterContext) createHAProxyDeployment(meta metav1.ObjectMeta) error }) } AddOwnerRefToObject(deploymentDef, ownerDef) - logger.Info("===== HAProxy Deployment ==== ", "deployment:", deploymentDef) + return deploymentDef +} + +// createHAproxy Deployment +func (cc *ClusterContext) createHAProxyDeployment(deploymentDef *appsv1.Deployment) error { + logger := cc.ReqLogger + logger.Info("Creating HAProxy Deployment") + client := cc.Client err := client.Create(cc.Ctx, deploymentDef) if err != nil { logger.Error(err, "HAProxy Deployment creation failed") @@ -344,6 +363,7 @@ func (cc *ClusterContext) generateHaproxyServiceDef(meta metav1.ObjectMeta) *cor Port: cr.Spec.HAProxy.Stats.Port, }) } + selectorLabels := getHAProxyLabels(cr.GetObjectMeta().GetName()) serviceDef := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: "marklogic-haproxy", @@ -352,7 +372,7 @@ func (cc *ClusterContext) generateHaproxyServiceDef(meta metav1.ObjectMeta) *cor Annotations: meta.Annotations, }, Spec: corev1.ServiceSpec{ - Selector: meta.Labels, + Selector: selectorLabels, Ports: servicePort, Type: corev1.ServiceTypeClusterIP, }, diff --git a/pkg/k8sutil/ingress.go b/pkg/k8sutil/ingress.go index e249722..a6bb30d 100644 --- a/pkg/k8sutil/ingress.go +++ b/pkg/k8sutil/ingress.go @@ -114,7 +114,6 @@ func (cc *ClusterContext) ReconcileIngress() result.ReconcileResult { } if !patchDiff.IsEmpty() { logger.Info("MarkLogic Ingress spec is different from the input Ingress spec, updating the Ingress") - logger.Info(patchDiff.String()) err := cc.Client.Update(cc.Ctx, ingressDef) if err != nil { logger.Error(err, "Error updating Ingress") diff --git a/pkg/k8sutil/marklogicServer.go b/pkg/k8sutil/marklogicServer.go index 1bfbd94..66f86dd 100644 --- a/pkg/k8sutil/marklogicServer.go +++ b/pkg/k8sutil/marklogicServer.go @@ -19,6 +19,8 @@ type MarkLogicGroupParameters struct { Replicas *int32 Name string ServiceAccountName string + Labels map[string]string + Annotations map[string]string GroupConfig *marklogicv1.GroupConfig Image string ImagePullPolicy string @@ -89,6 +91,16 @@ func GenerateMarkLogicGroupDef(cr *marklogicv1.MarklogicCluster, index int, para logger.Info("ReconcileMarkLogicCluster") labels := getCommonLabels(cr.ObjectMeta.Name) annotations := getCommonAnnotations() + if params.Labels != nil { + for key, value := range params.Labels { + labels[key] = value + } + } + if params.Annotations != nil { + for key, value := range params.Annotations { + annotations[key] = value + } + } objectMeta := generateObjectMeta(cr.Spec.MarkLogicGroups[index].Name, cr.Namespace, labels, annotations) bootStrapHostName := "" bootStrapName := "" @@ -113,6 +125,8 @@ func GenerateMarkLogicGroupDef(cr *marklogicv1.MarklogicCluster, index int, para Auth: params.Auth, ServiceAccountName: params.ServiceAccountName, Image: params.Image, + Labels: params.Labels, + Annotations: params.Annotations, ImagePullSecrets: params.ImagePullSecrets, License: params.License, TerminationGracePeriodSeconds: params.TerminationGracePeriodSeconds, @@ -158,13 +172,14 @@ func (cc *ClusterContext) ReconsileMarklogicCluster() (reconcile.Result, error) namespacedName := types.NamespacedName{Name: name, Namespace: namespace} clusterParams := generateMarkLogicClusterParams(cr) params := generateMarkLogicGroupParams(cr, i, clusterParams) - logger.Info("!!! ReconcileCluster MarkLogicGroup", "MarkLogicGroupParams", params) markLogicGroupDef := GenerateMarkLogicGroupDef(operatorCR, i, params) err := cc.Client.Get(cc.Ctx, namespacedName, currentMlg) if err != nil { if apierrors.IsNotFound(err) { logger.Info("MarkLogicGroup resource not found. Creating a new one") - + if err := patch.DefaultAnnotator.SetLastAppliedAnnotation(markLogicGroupDef); err != nil { + logger.Error(err, "Failed to set last applied annotation") + } err = cc.Client.Create(ctx, markLogicGroupDef) if err != nil { logger.Error(err, "Failed to create markLogicCluster") @@ -186,18 +201,21 @@ func (cc *ClusterContext) ReconsileMarklogicCluster() (reconcile.Result, error) return result.Error(err).Output() } if !patchDiff.IsEmpty() { - logger.Info("MarkLogic statefulSet spec is different from the MarkLogicGroup spec, updating the statefulSet") - logger.Info(patchDiff.String()) - currentMlg.Spec = markLogicGroupDef.Spec - currentMlg.ObjectMeta.Labels = markLogicGroupDef.ObjectMeta.Labels - currentMlg.ObjectMeta.Annotations = markLogicGroupDef.ObjectMeta.Annotations - err := cc.Client.Update(cc.Ctx, currentMlg) + logger.Info("MarkLogicGroup spec is different from the previous spec, updating the markLogicGroup") + // currentMlg.Spec = markLogicGroupDef.Spec + // currentMlg.ObjectMeta.Labels = markLogicGroupDef.ObjectMeta.Labels + // currentMlg.ObjectMeta.Annotations = markLogicGroupDef.ObjectMeta.Annotations + markLogicGroupDef.ObjectMeta.ResourceVersion = currentMlg.ObjectMeta.ResourceVersion + if err := patch.DefaultAnnotator.SetLastAppliedAnnotation(markLogicGroupDef); err != nil { + logger.Error(err, "Failed to set last applied annotation") + } + err := cc.Client.Update(cc.Ctx, markLogicGroupDef) if err != nil { - logger.Error(err, "Error updating MakrLogicGroup") + logger.Error(err, "Error updating MarklogicGroup") return result.Error(err).Output() } } else { - logger.Info("MarkLogic statefulSet spec is the same as the MarkLogicGroup spec") + logger.Info("MarkLogicGroup spec is same as the current spec, no update required") } } @@ -246,6 +264,8 @@ func generateMarkLogicGroupParams(cr *marklogicv1.MarklogicCluster, index int, c markLogicGroupParameters := &MarkLogicGroupParameters{ Replicas: cr.Spec.MarkLogicGroups[index].Replicas, Name: cr.Spec.MarkLogicGroups[index].Name, + Labels: cr.Spec.MarkLogicGroups[index].Labels, + Annotations: cr.Spec.MarkLogicGroups[index].Annotations, GroupConfig: cr.Spec.MarkLogicGroups[index].GroupConfig, Service: cr.Spec.MarkLogicGroups[index].Service, Image: clusterParams.Image, diff --git a/pkg/k8sutil/networkPolicy.go b/pkg/k8sutil/networkPolicy.go index 30f6085..b904c79 100644 --- a/pkg/k8sutil/networkPolicy.go +++ b/pkg/k8sutil/networkPolicy.go @@ -85,7 +85,6 @@ func (cc *ClusterContext) ReconcileNetworkPolicy() result.ReconcileResult { } if !patchDiff.IsEmpty() { logger.Info("MarkLogic NetworkPolicy spec is different from the input NetworkPolicy spec, updating the NetworkPolicy") - logger.Info(patchDiff.String()) err := cc.Client.Update(cc.Ctx, networkPolicyDef) if err != nil { logger.Error(err, "Error updating NetworkPolicy") diff --git a/pkg/k8sutil/service.go b/pkg/k8sutil/service.go index 65f4d09..35bc807 100644 --- a/pkg/k8sutil/service.go +++ b/pkg/k8sutil/service.go @@ -97,6 +97,12 @@ func generateServiceDef(serviceMeta metav1.ObjectMeta, ownerRef metav1.OwnerRefe func generateService(svcName string, cr *marklogicv1.MarklogicGroup) *corev1.Service { labels := getCommonLabels(cr.Spec.Name) + groupLabels := cr.Spec.Labels + if groupLabels != nil { + for key, value := range groupLabels { + labels[key] = value + } + } var svcParams serviceParameters = serviceParameters{} svcParams = generateServiceParams(cr) svcObjectMeta := generateObjectMeta(svcName, cr.Namespace, labels, svcParams.Annotations) diff --git a/pkg/k8sutil/statefulset.go b/pkg/k8sutil/statefulset.go index 404046e..8c690f3 100644 --- a/pkg/k8sutil/statefulset.go +++ b/pkg/k8sutil/statefulset.go @@ -65,9 +65,14 @@ type containerParameters struct { func (oc *OperatorContext) ReconcileStatefulset() (reconcile.Result, error) { cr := oc.GetMarkLogicServer() logger := oc.ReqLogger - labels := getCommonLabels(cr.Spec.Name) - annotations := getCommonAnnotations() - objectMeta := generateObjectMeta(cr.Spec.Name, cr.Namespace, labels, annotations) + groupLabels := cr.Labels + if groupLabels == nil { + groupLabels = getSelectorLabels(cr.Spec.Name) + } + groupLabels["app.kubernetes.io/instance"] = cr.Spec.Name + groupAnnotations := cr.GetAnnotations() + delete(groupAnnotations, "banzaicloud.com/last-applied") + objectMeta := generateObjectMeta(cr.Spec.Name, cr.Namespace, groupLabels, groupAnnotations) currentSts, err := oc.GetStatefulSet(cr.Namespace, objectMeta.Name) containerParams := generateContainerParams(cr) statefulSetParams := generateStatefulSetsParams(cr) @@ -118,6 +123,7 @@ func (oc *OperatorContext) ReconcileStatefulset() (reconcile.Result, error) { oc.ReqLogger.Error(err, "error updating the MarkLogic Operator Internal status") } } + patchDiff, err := patch.DefaultPatchMaker.Calculate(currentSts, statefulSetDef, patch.IgnoreStatusFields(), patch.IgnoreVolumeClaimTemplateTypeMetaAndStatus(), @@ -128,14 +134,16 @@ func (oc *OperatorContext) ReconcileStatefulset() (reconcile.Result, error) { } if !patchDiff.IsEmpty() { logger.Info("MarkLogic statefulSet spec is different from the MarkLogicGroup spec, updating the statefulSet") - logger.Info(patchDiff.String()) - err := oc.Client.Update(oc.Ctx, statefulSetDef) + currentSts.Spec = statefulSetDef.Spec + currentSts.ObjectMeta.Annotations = statefulSetDef.ObjectMeta.Annotations + currentSts.ObjectMeta.Labels = statefulSetDef.ObjectMeta.Labels + err := oc.Client.Update(oc.Ctx, currentSts) if err != nil { logger.Error(err, "Error updating statefulSet") return result.Error(err).Output() } } else { - logger.Info("MarkLogic statefulSet spec is the same as the MarkLogicGroup spec") + logger.Info("MarkLogic statefulSet spec is the same as the current spec, no update needed") } logger.Info("Operator Status:", "Stage", cr.Status.Stage) if cr.Status.Stage == "STS_CREATED" { @@ -176,7 +184,6 @@ func (oc *OperatorContext) GetStatefulSet(namespace string, stateful string) (*a func (oc *OperatorContext) createStatefulSet(statefulset *appsv1.StatefulSet, cr *marklogicv1.MarklogicGroup) error { logger := oc.ReqLogger err := oc.Client.Create(context.TODO(), statefulset) - // _, err := GenerateK8sClient().AppsV1().StatefulSets(namespace).Create(context.TODO(), stateful, metav1.CreateOptions{}) if err != nil { logger.Error(err, "MarkLogic stateful creation failed") return err diff --git a/test/e2e/2_marklogic_cluster_test.go b/test/e2e/2_marklogic_cluster_test.go index ffd8609..a8e34fc 100644 --- a/test/e2e/2_marklogic_cluster_test.go +++ b/test/e2e/2_marklogic_cluster_test.go @@ -114,7 +114,7 @@ type DataSource struct { } func TestMarklogicCluster(t *testing.T) { - feature := features.New("Marklogic Cluster Test") + feature := features.New("Marklogic Cluster Test").WithLabel("type", "cluster-test") // Setup Loki and Grafana to verify Logging for Operator feature.Setup(func(ctx context.Context, t *testing.T, c *envconf.Config) context.Context { @@ -228,7 +228,7 @@ func TestMarklogicCluster(t *testing.T) { client := c.Client() podName := "node-0" - err := utils.WaitForPod(ctx, t, client, mlNamespace, podName, 120*time.Second) + err := utils.WaitForPod(ctx, t, client, mlNamespace, podName, 180*time.Second) if err != nil { t.Fatalf("Failed to wait for pod creation: %v", err) } @@ -300,7 +300,7 @@ func TestMarklogicCluster(t *testing.T) { if err != nil { t.Fatalf("Failed to execute kubectl command in grafana pod: %v", err) } - // t.Logf("Query datasource response: %s", output) + t.Logf("Query datasource response: %s", output) // Verify MarkLogic logs in Grafana using Loki and Fluent Bit if !(strings.Contains(string(output), "Starting MarkLogic Server")) { t.Fatal("Failed to Query datasource")