From fb8b8a9267b7d582f451e73307fcc9ea64b23468 Mon Sep 17 00:00:00 2001 From: Jakub Koterba Date: Thu, 2 Apr 2026 16:34:26 +0200 Subject: [PATCH] sync logic idea --- .../controller/postgrescluster_controller.go | 2 +- .../cluster/business/adapters/README.md | 0 .../cluster/{ => business}/core/cluster.go | 148 ++++++++++++++---- .../{ => business}/core/cluster_unit_test.go | 0 .../cluster/{ => business}/core/events.go | 0 .../cluster/{ => business}/core/types.go | 0 .../cluster/business/core/types/README.md | 0 .../business/core/types/constants/reasons.go | 40 +++++ .../business/core/types/constants/state.go | 41 +++++ .../core/types/dto/provisioner_state.go | 3 + .../business/ports/secondary/provisioner.go | 12 ++ pkg/postgresql/cluster/service/reconciler.go | 1 + .../cluster/service/reconciler_test.go | 1 + 13 files changed, 213 insertions(+), 35 deletions(-) create mode 100644 pkg/postgresql/cluster/business/adapters/README.md rename pkg/postgresql/cluster/{ => business}/core/cluster.go (92%) rename pkg/postgresql/cluster/{ => business}/core/cluster_unit_test.go (100%) rename pkg/postgresql/cluster/{ => business}/core/events.go (100%) rename pkg/postgresql/cluster/{ => business}/core/types.go (100%) create mode 100644 pkg/postgresql/cluster/business/core/types/README.md create mode 100644 pkg/postgresql/cluster/business/core/types/constants/reasons.go create mode 100644 pkg/postgresql/cluster/business/core/types/constants/state.go create mode 100644 pkg/postgresql/cluster/business/core/types/dto/provisioner_state.go create mode 100644 pkg/postgresql/cluster/business/ports/secondary/provisioner.go create mode 100644 pkg/postgresql/cluster/service/reconciler.go create mode 100644 pkg/postgresql/cluster/service/reconciler_test.go diff --git a/internal/controller/postgrescluster_controller.go b/internal/controller/postgrescluster_controller.go index 70b11c9e6..c902222a1 100644 --- a/internal/controller/postgrescluster_controller.go +++ b/internal/controller/postgrescluster_controller.go @@ -21,7 +21,7 @@ import ( cnpgv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1" enterprisev4 "github.com/splunk/splunk-operator/api/v4" - clustercore "github.com/splunk/splunk-operator/pkg/postgresql/cluster/core" + clustercore "github.com/splunk/splunk-operator/pkg/postgresql/cluster/business/core" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/equality" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/pkg/postgresql/cluster/business/adapters/README.md b/pkg/postgresql/cluster/business/adapters/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/pkg/postgresql/cluster/core/cluster.go b/pkg/postgresql/cluster/business/core/cluster.go similarity index 92% rename from pkg/postgresql/cluster/core/cluster.go rename to pkg/postgresql/cluster/business/core/cluster.go index 3334011c6..118ab68cc 100644 --- a/pkg/postgresql/cluster/core/cluster.go +++ b/pkg/postgresql/cluster/business/core/cluster.go @@ -24,6 +24,8 @@ import ( cnpgv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1" password "github.com/sethvargo/go-password/password" enterprisev4 "github.com/splunk/splunk-operator/api/v4" + pgcConstants "github.com/splunk/splunk-operator/pkg/postgresql/cluster/business/core/types/constants" + "github.com/splunk/splunk-operator/pkg/postgresql/cluster/business/ports/secondary" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/equality" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -38,6 +40,10 @@ import ( log "sigs.k8s.io/controller-runtime/pkg/log" ) +type PgClusterReconciler struct { + Provisioner secondary.Provisioner +} + // PostgresClusterService is the application service entry point called by the primary adapter (reconciler). func PostgresClusterService(ctx context.Context, rc *ReconcileContext, req ctrl.Request) (ctrl.Result, error) { c := rc.Client @@ -445,45 +451,119 @@ func PostgresClusterService(ctx context.Context, rc *ReconcileContext, req ctrl. } } - // Final status sync. - var oldPhase string - if postgresCluster.Status.Phase != nil { - oldPhase = *postgresCluster.Status.Phase + /* + rewrite to consider taking state of other objects into account + before declaring readyness. + + CNPG cluster + Poolers + Access resources (configmap and secret) + And all of them needs to be set to Ready for our PostgresCluster phase to become Ready? + + */ + + // basically a sync logic + state := pgcConstants.EmptyState + conditions := []clusterReadynessCheck{ + &provisionerHealthCheck{}, + &poolerHealthCheck{}, + &configMapHealthCheck{}, + &secretHealthCheck{}, } - if err := syncStatus(ctx, c, postgresCluster, cnpgCluster); err != nil { - logger.Error(err, "Failed to sync status") - if apierrors.IsConflict(err) { - logger.Info("Conflict during status update, will requeue") - return ctrl.Result{Requeue: true}, nil + + for _, check := range conditions { + componentHealth, err := check.Condition() + if err != nil { + if apierrors.IsConflict(err) { + logger.Info("Conflict during whatever happened") + return ctrl.Result{Requeue: true}, nil + } } - return ctrl.Result{}, fmt.Errorf("failed to sync status: %w", err) + state |= componentHealth } - var newPhase string - if postgresCluster.Status.Phase != nil { - newPhase = *postgresCluster.Status.Phase - } - rc.emitClusterPhaseTransition(postgresCluster, oldPhase, newPhase) - if cnpgCluster.Status.Phase == cnpgv1.PhaseHealthy { - rwPooler := &cnpgv1.Pooler{} - rwErr := c.Get(ctx, types.NamespacedName{ - Name: poolerResourceName(postgresCluster.Name, readWriteEndpoint), - Namespace: postgresCluster.Namespace, - }, rwPooler) - roPooler := &cnpgv1.Pooler{} - roErr := c.Get(ctx, types.NamespacedName{ - Name: poolerResourceName(postgresCluster.Name, readOnlyEndpoint), - Namespace: postgresCluster.Namespace, - }, roPooler) - if rwErr == nil && roErr == nil && arePoolersReady(rwPooler, roPooler) { - logger.Info("Poolers ready, syncing status") - poolerOldConditions := make([]metav1.Condition, len(postgresCluster.Status.Conditions)) - copy(poolerOldConditions, postgresCluster.Status.Conditions) - _ = syncPoolerStatus(ctx, c, postgresCluster) - rc.emitPoolerReadyTransition(postgresCluster, poolerOldConditions) - } + + if state&pgcConstants.ComponentsReady != 0 { + logger.Info("Reconciliation complete") + state = pgcConstants.ClusterReady } - logger.Info("Reconciliation complete") return ctrl.Result{}, nil + + // // Final status sync. + // var oldPhase string + // if postgresCluster.Status.Phase != nil { + // oldPhase = *postgresCluster.Status.Phase + // } + // if err := syncStatus(ctx, c, postgresCluster, cnpgCluster); err != nil { + // logger.Error(err, "Failed to sync status") + // if apierrors.IsConflict(err) { + // logger.Info("Conflict during status update, will requeue") + // return ctrl.Result{Requeue: true}, nil + // } + // return ctrl.Result{}, fmt.Errorf("failed to sync status: %w", err) + // } + // var newPhase string + // if postgresCluster.Status.Phase != nil { + // newPhase = *postgresCluster.Status.Phase + // } + // rc.emitClusterPhaseTransition(postgresCluster, oldPhase, newPhase) + // if cnpgCluster.Status.Phase == cnpgv1.PhaseHealthy { + // rwPooler := &cnpgv1.Pooler{} + // rwErr := c.Get(ctx, types.NamespacedName{ + // Name: poolerResourceName(postgresCluster.Name, readWriteEndpoint), + // Namespace: postgresCluster.Namespace, + // }, rwPooler) + // roPooler := &cnpgv1.Pooler{} + // roErr := c.Get(ctx, types.NamespacedName{ + // Name: poolerResourceName(postgresCluster.Name, readOnlyEndpoint), + // Namespace: postgresCluster.Namespace, + // }, roPooler) + // if rwErr == nil && roErr == nil && arePoolersReady(rwPooler, roPooler) { + // logger.Info("Poolers ready, syncing status") + // poolerOldConditions := make([]metav1.Condition, len(postgresCluster.Status.Conditions)) + // copy(poolerOldConditions, postgresCluster.Status.Conditions) + // _ = syncPoolerStatus(ctx, c, postgresCluster) + // rc.emitPoolerReadyTransition(postgresCluster, poolerOldConditions) + // } + // } + // logger.Info("Reconciliation complete") + // return ctrl.Result{}, nil +} + +type clusterReadynessCheck interface { + Condition() (pgcConstants.State, error) +} + +type provisionerHealthCheck struct { + cnpgCluster *cnpgv1.Cluster +} + +func (c *provisionerHealthCheck) Condition() (pgcConstants.State, error) { + return pgcConstants.ProvisionerReady, nil +} + +type poolerHealthCheck struct { + rwPooler *cnpgv1.Pooler + roPooler *cnpgv1.Pooler +} + +func (p *poolerHealthCheck) Condition() (pgcConstants.State, error) { + return pgcConstants.PoolerReady, nil +} + +type configMapHealthCheck struct { + configMap *corev1.ConfigMap +} + +func (c *configMapHealthCheck) Condition() (pgcConstants.State, error) { + return pgcConstants.ConfigMapReady, nil +} + +type secretHealthCheck struct { + secret *corev1.Secret +} + +func (s *secretHealthCheck) Condition() (pgcConstants.State, error) { + return pgcConstants.SecretReady, nil } // getMergedConfig overlays PostgresCluster spec on top of the class defaults. diff --git a/pkg/postgresql/cluster/core/cluster_unit_test.go b/pkg/postgresql/cluster/business/core/cluster_unit_test.go similarity index 100% rename from pkg/postgresql/cluster/core/cluster_unit_test.go rename to pkg/postgresql/cluster/business/core/cluster_unit_test.go diff --git a/pkg/postgresql/cluster/core/events.go b/pkg/postgresql/cluster/business/core/events.go similarity index 100% rename from pkg/postgresql/cluster/core/events.go rename to pkg/postgresql/cluster/business/core/events.go diff --git a/pkg/postgresql/cluster/core/types.go b/pkg/postgresql/cluster/business/core/types.go similarity index 100% rename from pkg/postgresql/cluster/core/types.go rename to pkg/postgresql/cluster/business/core/types.go diff --git a/pkg/postgresql/cluster/business/core/types/README.md b/pkg/postgresql/cluster/business/core/types/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/pkg/postgresql/cluster/business/core/types/constants/reasons.go b/pkg/postgresql/cluster/business/core/types/constants/reasons.go new file mode 100644 index 000000000..6c53598d7 --- /dev/null +++ b/pkg/postgresql/cluster/business/core/types/constants/reasons.go @@ -0,0 +1,40 @@ +package pgcConstants + +type Reason string + +const ( + // condition reasons — clusterReady + reasonClusterClassNotFound Reason = "ClusterClassNotFound" + reasonManagedRolesFailed Reason = "ManagedRolesReconciliationFailed" + reasonClusterBuildFailed Reason = "ClusterBuildFailed" + reasonClusterBuildSucceeded Reason = "ClusterBuildSucceeded" + reasonClusterGetFailed Reason = "ClusterGetFailed" + reasonClusterPatchFailed Reason = "ClusterPatchFailed" + reasonInvalidConfiguration Reason = "InvalidConfiguration" + reasonConfigMapFailed Reason = "ConfigMapReconciliationFailed" + reasonUserSecretFailed Reason = "UserSecretReconciliationFailed" + reasonSuperUserSecretFailed Reason = "SuperUserSecretFailed" + reasonClusterDeleteFailed Reason = "ClusterDeleteFailed" + + // condition reasons — poolerReady + reasonPoolerReconciliationFailed Reason = "PoolerReconciliationFailed" + reasonPoolerConfigMissing Reason = "PoolerConfigMissing" + reasonPoolerCreating Reason = "PoolerCreating" + reasonAllInstancesReady Reason = "AllInstancesReady" + + // condition reasons — Provisioner cluster phase mapping + reasonProvisionerClusterNotHealthy Reason = "ClusterNotHealthy" + reasonProvisionerClusterHealthy Reason = "ClusterHealthy" + reasonProvisionerProvisioning Reason = "ClusterProvisioning" + reasonProvisionerSwitchover Reason = "Switchover" + reasonProvisionerFailingOver Reason = "FailingOver" + reasonProvisionerRestarting Reason = "Restarting" + reasonProvisionerUpgrading Reason = "Upgrading" + reasonProvisionerApplyingConfig Reason = "ApplyingConfiguration" + reasonProvisionerPromoting Reason = "Promoting" + reasonProvisionerWaitingForUser Reason = "WaitingForUser" + reasonProvisionerUnrecoverable Reason = "Unrecoverable" + reasonProvisionerProvisioningFailed Reason = "ProvisioningFailed" + reasonProvisionerPluginError Reason = "PluginError" + reasonProvisionerImageError Reason = "ImageError" +) diff --git a/pkg/postgresql/cluster/business/core/types/constants/state.go b/pkg/postgresql/cluster/business/core/types/constants/state.go new file mode 100644 index 000000000..0bd59d89b --- /dev/null +++ b/pkg/postgresql/cluster/business/core/types/constants/state.go @@ -0,0 +1,41 @@ +package pgcConstants + +type State uint8 + +const ( + EmptyState State = iota + PoolerReady + PoolerPending + PoolerProvisioning + PoolerConfiguring + PoolerFailed + + ProvisionerReady + ProvisionerPending + ProvisionerProvisioning + ProvisionerConfiguring + ProvisionerFailed + + ConfigMapReady + ConfigMapPending + ConfigMapProvisioning + ConfigMapConfiguring + ConfigMapFailed + + SecretReady + SecretPending + SecretProvisioning + SecretConfiguring + SecretFailed + + ClusterReady + ClusterPending + ClusterProvisioning + ClusterConfiguring + ClusterFailed +) + +const ( + ComponentsReady = PoolerReady | ProvisionerReady | SecretReady | ConfigMapReady + OwnershipReady +) diff --git a/pkg/postgresql/cluster/business/core/types/dto/provisioner_state.go b/pkg/postgresql/cluster/business/core/types/dto/provisioner_state.go new file mode 100644 index 000000000..6ab86cb3c --- /dev/null +++ b/pkg/postgresql/cluster/business/core/types/dto/provisioner_state.go @@ -0,0 +1,3 @@ +package pgcDto + +type ProvisionerState struct{} diff --git a/pkg/postgresql/cluster/business/ports/secondary/provisioner.go b/pkg/postgresql/cluster/business/ports/secondary/provisioner.go new file mode 100644 index 000000000..6b7395fc8 --- /dev/null +++ b/pkg/postgresql/cluster/business/ports/secondary/provisioner.go @@ -0,0 +1,12 @@ +package secondary + +import ( + pgcConstants "github.com/splunk/splunk-operator/pkg/postgresql/cluster/business/core/types/constants" +) + +type Provisioner interface { + PrepareSpec() + Build() error + Await() error + State() (pgcConstants.State, pgcConstants.Reason, error) +} diff --git a/pkg/postgresql/cluster/service/reconciler.go b/pkg/postgresql/cluster/service/reconciler.go new file mode 100644 index 000000000..c4833dd20 --- /dev/null +++ b/pkg/postgresql/cluster/service/reconciler.go @@ -0,0 +1 @@ +package gpClusterService diff --git a/pkg/postgresql/cluster/service/reconciler_test.go b/pkg/postgresql/cluster/service/reconciler_test.go new file mode 100644 index 000000000..c4833dd20 --- /dev/null +++ b/pkg/postgresql/cluster/service/reconciler_test.go @@ -0,0 +1 @@ +package gpClusterService