diff --git a/build/crd/percona/generated/pgv2.percona.com_perconapgclusters.yaml b/build/crd/percona/generated/pgv2.percona.com_perconapgclusters.yaml index cc973070d0..88a47ad5ab 100644 --- a/build/crd/percona/generated/pgv2.percona.com_perconapgclusters.yaml +++ b/build/crd/percona/generated/pgv2.percona.com_perconapgclusters.yaml @@ -17384,6 +17384,8 @@ spec: properties: host: type: string + patroniVersion: + type: string pgbouncer: properties: ready: @@ -17421,17 +17423,11 @@ spec: size: format: int32 type: integer - required: - - instances - - ready - - size + version: + type: integer type: object state: type: string - required: - - pgbouncer - - postgres - - state type: object required: - metadata diff --git a/cmd/postgres-operator/main.go b/cmd/postgres-operator/main.go index eba40e69a7..4401ba2827 100644 --- a/cmd/postgres-operator/main.go +++ b/cmd/postgres-operator/main.go @@ -26,8 +26,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log/zap" "sigs.k8s.io/controller-runtime/pkg/manager" - //"github.com/percona/percona-postgresql-operator/internal/controller/pgupgrade" - "github.com/percona/percona-postgresql-operator/internal/controller/pgupgrade" "github.com/percona/percona-postgresql-operator/internal/controller/postgrescluster" "github.com/percona/percona-postgresql-operator/internal/controller/runtime" diff --git a/config/crd/bases/pgv2.percona.com_perconapgclusters.yaml b/config/crd/bases/pgv2.percona.com_perconapgclusters.yaml index 499c7227fe..a659363230 100644 --- a/config/crd/bases/pgv2.percona.com_perconapgclusters.yaml +++ b/config/crd/bases/pgv2.percona.com_perconapgclusters.yaml @@ -17790,6 +17790,8 @@ spec: properties: host: type: string + patroniVersion: + type: string pgbouncer: properties: ready: @@ -17827,17 +17829,11 @@ spec: size: format: int32 type: integer - required: - - instances - - ready - - size + version: + type: integer type: object state: type: string - required: - - pgbouncer - - postgres - - state type: object required: - metadata diff --git a/config/rbac/cluster/role.yaml b/config/rbac/cluster/role.yaml index 6eabd3cb6a..549d66adc3 100644 --- a/config/rbac/cluster/role.yaml +++ b/config/rbac/cluster/role.yaml @@ -9,6 +9,7 @@ rules: resources: - configmaps - persistentvolumeclaims + - pods - secrets - serviceaccounts - services @@ -46,16 +47,6 @@ rules: verbs: - create - patch -- apiGroups: - - '' - resources: - - pods - verbs: - - delete - - get - - list - - patch - - watch - apiGroups: - apps resources: @@ -158,6 +149,13 @@ rules: - get - list - watch +- apiGroups: + - pgv2.percona.com + resources: + - pods + verbs: + - create + - delete - apiGroups: - policy resources: diff --git a/config/rbac/namespace/role.yaml b/config/rbac/namespace/role.yaml index ef2e8c5047..cddae7d588 100644 --- a/config/rbac/namespace/role.yaml +++ b/config/rbac/namespace/role.yaml @@ -9,6 +9,7 @@ rules: resources: - configmaps - persistentvolumeclaims + - pods - secrets - serviceaccounts - services @@ -46,16 +47,6 @@ rules: verbs: - create - patch -- apiGroups: - - '' - resources: - - pods - verbs: - - delete - - get - - list - - patch - - watch - apiGroups: - apps resources: @@ -158,6 +149,13 @@ rules: - get - list - watch +- apiGroups: + - pgv2.percona.com + resources: + - pods + verbs: + - create + - delete - apiGroups: - policy resources: diff --git a/deploy/bundle.yaml b/deploy/bundle.yaml index 53409e176f..ebe12304e0 100644 --- a/deploy/bundle.yaml +++ b/deploy/bundle.yaml @@ -18083,6 +18083,8 @@ spec: properties: host: type: string + patroniVersion: + type: string pgbouncer: properties: ready: @@ -18120,17 +18122,11 @@ spec: size: format: int32 type: integer - required: - - instances - - ready - - size + version: + type: integer type: object state: type: string - required: - - pgbouncer - - postgres - - state type: object required: - metadata @@ -45447,6 +45443,7 @@ rules: resources: - configmaps - persistentvolumeclaims + - pods - secrets - serviceaccounts - services @@ -45484,16 +45481,6 @@ rules: verbs: - create - patch -- apiGroups: - - "" - resources: - - pods - verbs: - - delete - - get - - list - - patch - - watch - apiGroups: - apps resources: @@ -45596,6 +45583,13 @@ rules: - get - list - watch +- apiGroups: + - pgv2.percona.com + resources: + - pods + verbs: + - create + - delete - apiGroups: - policy resources: diff --git a/deploy/crd.yaml b/deploy/crd.yaml index 327b385ab5..8172257740 100644 --- a/deploy/crd.yaml +++ b/deploy/crd.yaml @@ -18083,6 +18083,8 @@ spec: properties: host: type: string + patroniVersion: + type: string pgbouncer: properties: ready: @@ -18120,17 +18122,11 @@ spec: size: format: int32 type: integer - required: - - instances - - ready - - size + version: + type: integer type: object state: type: string - required: - - pgbouncer - - postgres - - state type: object required: - metadata diff --git a/deploy/cw-bundle.yaml b/deploy/cw-bundle.yaml index 2b744cec89..13e1a4ef5c 100644 --- a/deploy/cw-bundle.yaml +++ b/deploy/cw-bundle.yaml @@ -18083,6 +18083,8 @@ spec: properties: host: type: string + patroniVersion: + type: string pgbouncer: properties: ready: @@ -18120,17 +18122,11 @@ spec: size: format: int32 type: integer - required: - - instances - - ready - - size + version: + type: integer type: object state: type: string - required: - - pgbouncer - - postgres - - state type: object required: - metadata @@ -45447,6 +45443,7 @@ rules: resources: - configmaps - persistentvolumeclaims + - pods - secrets - serviceaccounts - services @@ -45484,16 +45481,6 @@ rules: verbs: - create - patch -- apiGroups: - - "" - resources: - - pods - verbs: - - delete - - get - - list - - patch - - watch - apiGroups: - apps resources: @@ -45596,6 +45583,13 @@ rules: - get - list - watch +- apiGroups: + - pgv2.percona.com + resources: + - pods + verbs: + - create + - delete - apiGroups: - policy resources: diff --git a/deploy/cw-rbac.yaml b/deploy/cw-rbac.yaml index 37081c9a0b..dc89c4680f 100644 --- a/deploy/cw-rbac.yaml +++ b/deploy/cw-rbac.yaml @@ -13,6 +13,7 @@ rules: resources: - configmaps - persistentvolumeclaims + - pods - secrets - serviceaccounts - services @@ -50,16 +51,6 @@ rules: verbs: - create - patch -- apiGroups: - - "" - resources: - - pods - verbs: - - delete - - get - - list - - patch - - watch - apiGroups: - apps resources: @@ -162,6 +153,13 @@ rules: - get - list - watch +- apiGroups: + - pgv2.percona.com + resources: + - pods + verbs: + - create + - delete - apiGroups: - policy resources: diff --git a/deploy/rbac.yaml b/deploy/rbac.yaml index 4d630e43c4..4f12075994 100644 --- a/deploy/rbac.yaml +++ b/deploy/rbac.yaml @@ -13,6 +13,7 @@ rules: resources: - configmaps - persistentvolumeclaims + - pods - secrets - serviceaccounts - services @@ -50,16 +51,6 @@ rules: verbs: - create - patch -- apiGroups: - - "" - resources: - - pods - verbs: - - delete - - get - - list - - patch - - watch - apiGroups: - apps resources: @@ -162,6 +153,13 @@ rules: - get - list - watch +- apiGroups: + - pgv2.percona.com + resources: + - pods + verbs: + - create + - delete - apiGroups: - policy resources: diff --git a/internal/controller/postgrescluster/instance.go b/internal/controller/postgrescluster/instance.go index 162f9b9bd3..05501578fc 100644 --- a/internal/controller/postgrescluster/instance.go +++ b/internal/controller/postgrescluster/instance.go @@ -38,6 +38,7 @@ import ( "github.com/percona/percona-postgresql-operator/internal/pgbackrest" "github.com/percona/percona-postgresql-operator/internal/pki" "github.com/percona/percona-postgresql-operator/internal/postgres" + pNaming "github.com/percona/percona-postgresql-operator/percona/naming" "github.com/percona/percona-postgresql-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1" ) @@ -167,9 +168,11 @@ type instanceSorter struct { func (s *instanceSorter) Len() int { return len(s.instances) } + func (s *instanceSorter) Less(i, j int) bool { return s.less(s.instances[i], s.instances[j]) } + func (s *instanceSorter) Swap(i, j int) { s.instances[i], s.instances[j] = s.instances[j], s.instances[i] } @@ -489,7 +492,6 @@ func (r *Reconciler) deleteInstances( // have a "spec.replicas" field with the same meaning. patch := client.RawPatch(client.Merge.Type(), []byte(`{"spec":{"replicas":0}}`)) err := errors.WithStack(r.patch(ctx, instance, patch)) - // When the pod controller is missing, requeue rather than return an // error. The garbage collector will stop the pod, and it is not our // mistake that something else is deleting objects. Use RequeueAfter to @@ -598,7 +600,6 @@ func (r *Reconciler) reconcileInstanceSets( exporterQueriesConfig, exporterWebConfig *corev1.ConfigMap, backupsSpecFound bool, ) error { - // Go through the observed instances and check if a primary has been determined. // If the cluster is being shutdown and this instance is the primary, store // the instance name as the startup instance. If the primary can be determined @@ -712,8 +713,8 @@ func (r *Reconciler) cleanupPodDisruptionBudgets( // for the instance set specified that are not currently associated with an instance, and then // returning the instance names associated with those PVC's. func findAvailableInstanceNames(set v1beta1.PostgresInstanceSetSpec, - observedInstances *observedInstances, clusterVolumes []corev1.PersistentVolumeClaim) []string { - + observedInstances *observedInstances, clusterVolumes []corev1.PersistentVolumeClaim, +) []string { availableInstanceNames := []string{} // first identify any PGDATA volumes for the instance set specified @@ -810,10 +811,13 @@ func (r *Reconciler) rolloutInstance( ctx, span = r.Tracer.Start(ctx, "patroni-change-primary") defer span.End() - patroniVer, err := patroni.GetVersionFromPod(pod) + patroniVerStr, ok := cluster.Annotations[pNaming.ToCrunchyAnnotation(pNaming.AnnotationPatroniVersion)] + if !ok { + return errors.New("patroni version annoation was not found") + } + patroniVer, err := gover.NewVersion(patroniVerStr) if err != nil { - span.RecordError(err) - return errors.Wrap(err, "failed to get patroni version from pod") + return errors.Wrap(err, "failed to get patroni ver") } ver4 := patroniVer.Compare(gover.Must(gover.NewVersion("4.0.0"))) >= 0 @@ -986,7 +990,6 @@ func (r *Reconciler) scaleDownInstances( cluster *v1beta1.PostgresCluster, observedInstances *observedInstances, ) error { - // want defines the number of replicas we want for each instance set want := map[string]int{} for _, set := range cluster.Spec.InstanceSets { @@ -1025,7 +1028,6 @@ func (r *Reconciler) scaleDownInstances( // the number of replicas we want for each instance set // then returns a list of the pods that we want to keep func podsToKeep(instances []corev1.Pod, want map[string]int) []corev1.Pod { - f := func(instances []corev1.Pod, want int) []corev1.Pod { keep := []corev1.Pod{} @@ -1062,7 +1064,6 @@ func podsToKeep(instances []corev1.Pod, want map[string]int) []corev1.Pod { } return keepPodList - } // +kubebuilder:rbac:groups="apps",resources="statefulsets",verbs={list} @@ -1230,7 +1231,6 @@ func (r *Reconciler) reconcileInstance( config.PostgresContainerImage(cluster), cluster.Spec.ImagePullPolicy, &instance.Spec.Template) - } // K8SPG-435 sizeLimit := getTMPSizeLimit(instance.Labels[naming.LabelVersion], spec.Resources) diff --git a/internal/controller/postgrescluster/patroni.go b/internal/controller/postgrescluster/patroni.go index a7b08f8ce8..517b0e7668 100644 --- a/internal/controller/postgrescluster/patroni.go +++ b/internal/controller/postgrescluster/patroni.go @@ -23,6 +23,7 @@ import ( "github.com/percona/percona-postgresql-operator/internal/patroni" "github.com/percona/percona-postgresql-operator/internal/pki" "github.com/percona/percona-postgresql-operator/internal/postgres" + pNaming "github.com/percona/percona-postgresql-operator/percona/naming" "github.com/percona/percona-postgresql-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1" ) @@ -95,11 +96,15 @@ func (r *Reconciler) handlePatroniRestarts( return r.PodExec(ctx, pod.Namespace, pod.Name, container, stdin, stdout, stderr, command...) }) - patroniVersion, err := patroni.GetVersionFromPod(pod) + patroniVerStr, ok := cluster.Annotations[pNaming.ToCrunchyAnnotation(pNaming.AnnotationPatroniVersion)] + if !ok { + return errors.New("patroni version annoation was not found") + } + patroniVer, err := gover.NewVersion(patroniVerStr) if err != nil { - return errors.Wrap(err, "failed to get patroni version") + return errors.Wrap(err, "failed to get patroni ver") } - ver4 := patroniVersion.Compare(gover.Must(gover.NewVersion("4.0.0"))) >= 0 + ver4 := patroniVer.Compare(gover.Must(gover.NewVersion("4.0.0"))) >= 0 // K8SPG-648: patroni v4.0.0 deprecated "master" role. // We should use "primary" instead @@ -369,7 +374,6 @@ func (r *Reconciler) reconcileReplicationSecret( ctx context.Context, cluster *v1beta1.PostgresCluster, root *pki.RootCertificateAuthority, ) (*corev1.Secret, error) { - // if a custom postgrescluster secret is provided, just return it if cluster.Spec.CustomReplicationClientTLSSecret != nil { custom := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{ @@ -463,7 +467,8 @@ func replicationCertSecretProjection(certificate *corev1.Secret) *corev1.SecretP } func (r *Reconciler) reconcilePatroniSwitchover(ctx context.Context, - cluster *v1beta1.PostgresCluster, instances *observedInstances) error { + cluster *v1beta1.PostgresCluster, instances *observedInstances, +) error { log := logging.FromContext(ctx) // If switchover is not enabled, clear out the Patroni switchover status fields @@ -543,7 +548,8 @@ func (r *Reconciler) reconcilePatroniSwitchover(ctx context.Context, return errors.New("Could not find a running pod when attempting switchover.") } exec := func(_ context.Context, stdin io.Reader, stdout, stderr io.Writer, - command ...string) error { + command ...string, + ) error { return r.PodExec(ctx, runningPod.Namespace, runningPod.Name, naming.ContainerDatabase, stdin, stdout, stderr, command...) } @@ -554,7 +560,6 @@ func (r *Reconciler) reconcilePatroniSwitchover(ctx context.Context, // have shown that the annotation on the Leader pod is up to date during a switchover, but // missing from the Replica pods. timeline, err := patroni.Executor(exec).GetTimeline(ctx) - if err != nil { return err } diff --git a/internal/patroni/config.go b/internal/patroni/config.go index f918b0a5fe..b70093b1c8 100644 --- a/internal/patroni/config.go +++ b/internal/patroni/config.go @@ -5,16 +5,19 @@ package patroni import ( + "errors" "fmt" "path" "strings" + gover "github.com/hashicorp/go-version" corev1 "k8s.io/api/core/v1" "sigs.k8s.io/yaml" "github.com/percona/percona-postgresql-operator/internal/config" "github.com/percona/percona-postgresql-operator/internal/naming" "github.com/percona/percona-postgresql-operator/internal/postgres" + pNaming "github.com/percona/percona-postgresql-operator/percona/naming" "github.com/percona/percona-postgresql-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1" ) @@ -570,7 +573,14 @@ func instanceYAML( } // K8SPG-648: patroni v4.0.0 deprecated "no_master". // We should use "no_leader" instead - if cluster.Spec.PostgresVersion < 17 { + + patroniVerStr, ok := cluster.Annotations[pNaming.ToCrunchyAnnotation(pNaming.AnnotationPatroniVersion)] + if !ok { + return "", errors.New("patroni version annoation was not found") + } + patroniVer := gover.Must(gover.NewVersion(patroniVerStr)) + ver4 := patroniVer.Compare(gover.Must(gover.NewVersion("4.0.0"))) >= 0 + if !ver4 { postgresql[pgBackRestCreateReplicaMethod] = map[string]any{ "command": strings.Join(quoted, " "), "keep_data": true, diff --git a/internal/patroni/pod.go b/internal/patroni/pod.go deleted file mode 100644 index cb41a90fe0..0000000000 --- a/internal/patroni/pod.go +++ /dev/null @@ -1,36 +0,0 @@ -package patroni - -import ( - "encoding/json" - - gover "github.com/hashicorp/go-version" - "github.com/pkg/errors" - corev1 "k8s.io/api/core/v1" -) - -func GetVersionFromPod(pod *corev1.Pod) (*gover.Version, error) { - patroniJson, ok := pod.Annotations["status"] - if !ok { - return nil, errors.New("pod doesn't have status annotation") - } - patroniStatus := make(map[string]any) - if err := json.Unmarshal([]byte(patroniJson), &patroniStatus); err != nil { - return nil, errors.Wrap(err, "failed to unmarshal patroni status") - } - versionI, ok := patroniStatus["version"] - if !ok { - return nil, errors.New("status doesn't have version field") - } - - versionStr, ok := versionI.(string) - if !ok { - return nil, errors.New("version is not string") - } - - version, err := gover.NewVersion(versionStr) - if err != nil { - return nil, errors.Wrap(err, "failed to create new version from string") - } - - return version, nil -} diff --git a/percona/controller/pgcluster/controller.go b/percona/controller/pgcluster/controller.go index 1d5d1dac92..13cca0d637 100644 --- a/percona/controller/pgcluster/controller.go +++ b/percona/controller/pgcluster/controller.go @@ -1,6 +1,7 @@ package pgcluster import ( + "bytes" "context" "crypto/md5" "fmt" @@ -8,6 +9,7 @@ import ( "strings" "time" + gover "github.com/hashicorp/go-version" "github.com/pkg/errors" "go.opentelemetry.io/otel/trace" batchv1 "k8s.io/api/batch/v1" @@ -16,7 +18,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/tools/record" + "k8s.io/client-go/util/retry" "k8s.io/client-go/util/workqueue" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" @@ -31,6 +35,7 @@ import ( "github.com/percona/percona-postgresql-operator/internal/logging" "github.com/percona/percona-postgresql-operator/internal/naming" + "github.com/percona/percona-postgresql-operator/percona/clientcmd" perconaController "github.com/percona/percona-postgresql-operator/percona/controller" "github.com/percona/percona-postgresql-operator/percona/extensions" "github.com/percona/percona-postgresql-operator/percona/k8s" @@ -161,6 +166,8 @@ func (r *PGClusterReconciler) watchSecrets() handler.TypedFuncs[*corev1.Secret, // +kubebuilder:rbac:groups=apps,resources=replicasets,verbs=create;delete;get;list;patch;watch // +kubebuilder:rbac:groups=pgv2.percona.com,resources=perconapgclusters/finalizers,verbs=update // +kubebuilder:rbac:groups=batch,resources=jobs,verbs=create;list;update +// +kubebuilder:rbac:groups=pgv2.percona.com,resources=pods,verbs=create;delete +// +kubebuilder:rbac:groups="",resources="pods",verbs=create;delete func (r *PGClusterReconciler) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) { log := logging.FromContext(ctx).WithValues("cluster", request.Name, "namespace", request.Namespace) @@ -213,6 +220,15 @@ func (r *PGClusterReconciler) Reconcile(ctx context.Context, request reconcile.R return reconcile.Result{}, nil } + if err := r.reconcilePatroniVersionCheck(ctx, cr); err != nil { + if errors.Is(err, errPatroniVersionCheckWait) { + return reconcile.Result{ + RequeueAfter: 5 * time.Second, + }, nil + } + return reconcile.Result{}, errors.Wrap(err, "check patroni version") + } + if err := r.reconcileTLS(ctx, cr); err != nil { return reconcile.Result{}, errors.Wrap(err, "reconcile TLS") } @@ -286,6 +302,104 @@ func (r *PGClusterReconciler) Reconcile(ctx context.Context, request reconcile.R return ctrl.Result{}, nil } +var errPatroniVersionCheckWait = errors.New("waiting for pod to initialize") + +func (r *PGClusterReconciler) reconcilePatroniVersionCheck(ctx context.Context, cr *v2.PerconaPGCluster) error { + meta := metav1.ObjectMeta{ + Name: cr.Name + "-patroni-version-check", + Namespace: cr.Namespace, + } + + p := &corev1.Pod{ + ObjectMeta: meta, + } + + err := r.Client.Get(ctx, client.ObjectKeyFromObject(p), p) + if client.IgnoreNotFound(err) != nil { + return errors.Wrap(err, "failed to get patroni version check pod") + } + if k8serrors.IsNotFound(err) { + if cr.Status.Postgres.Version == cr.Spec.PostgresVersion && cr.Status.PatroniVersion != "" { + cr.Annotations[pNaming.AnnotationPatroniVersion] = cr.Status.PatroniVersion + return nil + } + p = &corev1.Pod{ + ObjectMeta: meta, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: pNaming.ContainerPatroniVersionCheck, + Image: cr.Spec.Image, + Command: []string{ + "bash", + }, + Args: []string{ + "-c", "sleep 300", + }, + }, + }, + }, + } + + if err := r.Client.Create(ctx, p); err != nil { + return errors.Wrap(err, "failed to create pod to check patrni version") + } + + return errPatroniVersionCheckWait + } else { + if cr.Status.Postgres.Version == cr.Spec.PostgresVersion && cr.Status.PatroniVersion != "" { + cr.Annotations[pNaming.AnnotationPatroniVersion] = cr.Status.PatroniVersion + if err := r.Client.Delete(ctx, p); err != nil { + return errors.Wrap(err, "failed to delete patroni version check pod") + } + return nil + } + } + + if p.Status.Phase == corev1.PodRunning { + return errPatroniVersionCheckWait + } + + var stdout, stderr bytes.Buffer + execCli, err := clientcmd.NewClient() + if err != nil { + return errors.Wrap(err, "failed to create exec client") + } + b := wait.Backoff{ + Duration: 5 * time.Second, + Factor: 1.0, + Steps: 12, + Cap: time.Minute, + } + if err := retry.OnError(b, func(err error) bool { return err != nil && strings.Contains(err.Error(), "container not found") }, func() error { + return execCli.Exec(ctx, p, pNaming.ContainerPatroniVersionCheck, nil, &stdout, &stderr, "patronictl", "version") + }); err != nil { + return errors.Wrap(err, "exec") + } + + patroniVersion := strings.TrimSpace(strings.TrimPrefix(stdout.String(), "patronictl version ")) + + if _, err := gover.NewVersion(patroniVersion); err != nil { + return errors.Wrap(err, "failed to validate patroni version") + } + + orig := cr.DeepCopy() + + cr.Status.PatroniVersion = patroniVersion + cr.Status.Postgres.Version = cr.Spec.PostgresVersion + + if err := r.Client.Status().Patch(ctx, cr.DeepCopy(), client.MergeFrom(orig)); err != nil { + return errors.Wrap(err, "failed to patch patroni version") + } + + if err := r.Client.Delete(ctx, p); err != nil { + return errors.Wrap(err, "failed to delete patroni version check pod") + } + cr.Annotations[pNaming.AnnotationPatroniVersion] = patroniVersion + + return nil +} + func (r *PGClusterReconciler) reconcileTLS(ctx context.Context, cr *v2.PerconaPGCluster) error { if err := r.validateTLS(ctx, cr); err != nil { return errors.Wrap(err, "validate TLS") diff --git a/percona/controller/pgcluster/status.go b/percona/controller/pgcluster/status.go index 477fe2a604..a4fb42a59d 100644 --- a/percona/controller/pgcluster/status.go +++ b/percona/controller/pgcluster/status.go @@ -100,18 +100,15 @@ func (r *PGClusterReconciler) updateStatus(ctx context.Context, cr *v2.PerconaPG return errors.Wrap(err, "get PerconaPGCluster") } - cluster.Status = v2.PerconaPGClusterStatus{ - Postgres: v2.PostgresStatus{ - Size: size, - Ready: ready, - InstanceSets: ss, - }, - PGBouncer: v2.PGBouncerStatus{ - Size: status.Proxy.PGBouncer.Replicas, - Ready: status.Proxy.PGBouncer.ReadyReplicas, - }, - Host: host, + cluster.Status.Postgres.Size = size + cluster.Status.Postgres.Ready = ready + cluster.Status.Postgres.InstanceSets = ss + + cluster.Status.PGBouncer = v2.PGBouncerStatus{ + Size: status.Proxy.PGBouncer.Replicas, + Ready: status.Proxy.PGBouncer.ReadyReplicas, } + cluster.Status.Host = host cluster.Status.State = r.getState(cr, &cluster.Status, status) diff --git a/percona/naming/annotations.go b/percona/naming/annotations.go index 0a861e606d..b9e223215a 100644 --- a/percona/naming/annotations.go +++ b/percona/naming/annotations.go @@ -1,4 +1,4 @@ -package util +package naming import ( "strings" @@ -46,6 +46,8 @@ const ( // AnnotationClusterBootstrapRestore is the annotation that is added to PerconaPGRestore to // indicate that it is a cluster bootstrap restore. AnnotationClusterBootstrapRestore = AnnotationPrefix + "cluster-bootstrap-restore" + + AnnotationPatroniVersion = AnnotationPrefix + "patroni-version" ) func ToCrunchyAnnotation(annotation string) string { diff --git a/percona/naming/container.go b/percona/naming/container.go new file mode 100644 index 0000000000..f6785ced74 --- /dev/null +++ b/percona/naming/container.go @@ -0,0 +1,5 @@ +package naming + +const ( + ContainerPatroniVersionCheck = "patroni-version-check" +) diff --git a/percona/watcher/wal.go b/percona/watcher/wal.go index fcd3cbc3af..fc3e61ae04 100644 --- a/percona/watcher/wal.go +++ b/percona/watcher/wal.go @@ -6,6 +6,7 @@ import ( "strings" "time" + gover "github.com/hashicorp/go-version" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -175,7 +176,9 @@ func getPrimaryPod(ctx context.Context, cli client.Client, cr *pgv2.PerconaPGClu // K8SPG-648: patroni v4.0.0 deprecated "master" role. // We should use "primary" instead role := "primary" - if cr.Spec.PostgresVersion < 17 { + patroniVer := gover.Must(gover.NewVersion(cr.Status.PatroniVersion)) + ver4 := patroniVer.Compare(gover.Must(gover.NewVersion("4.0.0"))) >= 0 + if !ver4 { role = "master" } err := cli.List(ctx, podList, &client.ListOptions{ diff --git a/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types.go b/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types.go index 55e72efeec..463d7e836f 100644 --- a/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types.go +++ b/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types.go @@ -354,42 +354,48 @@ const ( type PostgresInstanceSetStatus struct { Name string `json:"name"` - // +kubebuilder:validation:Required Size int32 `json:"size"` - // +kubebuilder:validation:Required Ready int32 `json:"ready"` } type PostgresStatus struct { - // +kubebuilder:validation:Required + // +optional Size int32 `json:"size"` - // +kubebuilder:validation:Required + // +optional Ready int32 `json:"ready"` - // +kubebuilder:validation:Required + // +optional InstanceSets []PostgresInstanceSetStatus `json:"instances"` + + // +optional + Version int `json:"version"` } type PGBouncerStatus struct { - // +kubebuilder:validation:Required Size int32 `json:"size"` - // +kubebuilder:validation:Required Ready int32 `json:"ready"` } type PerconaPGClusterStatus struct { + // +optional // +operator-sdk:csv:customresourcedefinitions:type=status Postgres PostgresStatus `json:"postgres"` + // +optional // +operator-sdk:csv:customresourcedefinitions:type=status PGBouncer PGBouncerStatus `json:"pgbouncer"` + // +optional // +operator-sdk:csv:customresourcedefinitions:type=status State AppState `json:"state"` + // +optional + // +operator-sdk:csv:customresourcedefinitions:type=status + PatroniVersion string `json:"patroniVersion"` + // +optional // +operator-sdk:csv:customresourcedefinitions:type=status Host string `json:"host"`