diff --git a/build/crd/crunchy/generated/postgres-operator.crunchydata.com_pgupgrades.yaml b/build/crd/crunchy/generated/postgres-operator.crunchydata.com_pgupgrades.yaml index 472b261240..ceffaff5ee 100644 --- a/build/crd/crunchy/generated/postgres-operator.crunchydata.com_pgupgrades.yaml +++ b/build/crd/crunchy/generated/postgres-operator.crunchydata.com_pgupgrades.yaml @@ -964,7 +964,7 @@ spec: fromPostgresVersion: description: The major version of PostgreSQL before the upgrade. maximum: 17 - minimum: 10 + minimum: 12 type: integer image: description: The image name to use for major PostgreSQL upgrades. @@ -2513,7 +2513,7 @@ spec: toPostgresVersion: description: The major version of PostgreSQL to be upgraded to. maximum: 17 - minimum: 10 + minimum: 13 type: integer tolerations: description: |- diff --git a/build/crd/crunchy/generated/postgres-operator.crunchydata.com_postgresclusters.yaml b/build/crd/crunchy/generated/postgres-operator.crunchydata.com_postgresclusters.yaml index bc14ab6042..0c20a20096 100644 --- a/build/crd/crunchy/generated/postgres-operator.crunchydata.com_postgresclusters.yaml +++ b/build/crd/crunchy/generated/postgres-operator.crunchydata.com_postgresclusters.yaml @@ -14108,7 +14108,7 @@ spec: description: The major version of PostgreSQL installed in the PostgreSQL image maximum: 17 - minimum: 10 + minimum: 12 type: integer proxy: description: The specification of a proxy that connects to PostgreSQL. diff --git a/build/crd/percona/generated/pgv2.percona.com_perconapgclusters.yaml b/build/crd/percona/generated/pgv2.percona.com_perconapgclusters.yaml index 7982ea4dcf..c9d7b38ae9 100644 --- a/build/crd/percona/generated/pgv2.percona.com_perconapgclusters.yaml +++ b/build/crd/percona/generated/pgv2.percona.com_perconapgclusters.yaml @@ -13724,7 +13724,7 @@ spec: postgresVersion: description: The major version of PostgreSQL installed in the PostgreSQL image - maximum: 16 + maximum: 17 minimum: 12 type: integer proxy: @@ -17513,6 +17513,8 @@ spec: items: type: string type: array + patroniVersion: + type: string pgbouncer: properties: ready: @@ -17550,17 +17552,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/build/crd/percona/generated/pgv2.percona.com_perconapgupgrades.yaml b/build/crd/percona/generated/pgv2.percona.com_perconapgupgrades.yaml index 93e5421991..b44748b1d1 100644 --- a/build/crd/percona/generated/pgv2.percona.com_perconapgupgrades.yaml +++ b/build/crd/percona/generated/pgv2.percona.com_perconapgupgrades.yaml @@ -2515,7 +2515,7 @@ spec: type: string toPostgresVersion: description: The major version of PostgreSQL to be upgraded to. - maximum: 16 + maximum: 17 minimum: 13 type: integer tolerations: diff --git a/cmd/postgres-operator/main.go b/cmd/postgres-operator/main.go index 1447b8e8cd..f43069e8a7 100644 --- a/cmd/postgres-operator/main.go +++ b/cmd/postgres-operator/main.go @@ -26,7 +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 4e2719326b..21070014e1 100644 --- a/config/crd/bases/pgv2.percona.com_perconapgclusters.yaml +++ b/config/crd/bases/pgv2.percona.com_perconapgclusters.yaml @@ -14130,7 +14130,7 @@ spec: postgresVersion: description: The major version of PostgreSQL installed in the PostgreSQL image - maximum: 16 + maximum: 17 minimum: 12 type: integer proxy: @@ -17919,6 +17919,8 @@ spec: items: type: string type: array + patroniVersion: + type: string pgbouncer: properties: ready: @@ -17956,17 +17958,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 @@ -20591,7 +20587,7 @@ spec: type: string toPostgresVersion: description: The major version of PostgreSQL to be upgraded to. - maximum: 16 + maximum: 17 minimum: 13 type: integer tolerations: diff --git a/config/crd/bases/postgres-operator.crunchydata.com_pgupgrades.yaml b/config/crd/bases/postgres-operator.crunchydata.com_pgupgrades.yaml index 7afbf9ce9d..46b28a9a75 100644 --- a/config/crd/bases/postgres-operator.crunchydata.com_pgupgrades.yaml +++ b/config/crd/bases/postgres-operator.crunchydata.com_pgupgrades.yaml @@ -966,7 +966,7 @@ spec: fromPostgresVersion: description: The major version of PostgreSQL before the upgrade. maximum: 17 - minimum: 10 + minimum: 12 type: integer image: description: The image name to use for major PostgreSQL upgrades. @@ -2510,7 +2510,7 @@ spec: toPostgresVersion: description: The major version of PostgreSQL to be upgraded to. maximum: 17 - minimum: 10 + minimum: 13 type: integer tolerations: description: |- diff --git a/config/crd/bases/postgres-operator.crunchydata.com_postgresclusters.yaml b/config/crd/bases/postgres-operator.crunchydata.com_postgresclusters.yaml index f73d1c5da3..1bf658737b 100644 --- a/config/crd/bases/postgres-operator.crunchydata.com_postgresclusters.yaml +++ b/config/crd/bases/postgres-operator.crunchydata.com_postgresclusters.yaml @@ -14050,7 +14050,7 @@ spec: description: The major version of PostgreSQL installed in the PostgreSQL image maximum: 17 - minimum: 10 + minimum: 12 type: integer proxy: description: The specification of a proxy that connects to PostgreSQL. diff --git a/config/rbac/cluster/role.yaml b/config/rbac/cluster/role.yaml index 6eabd3cb6a..0b2a74cb8b 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: diff --git a/config/rbac/namespace/role.yaml b/config/rbac/namespace/role.yaml index ef2e8c5047..6cec1f747c 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: diff --git a/deploy/bundle.yaml b/deploy/bundle.yaml index 1743845181..2588d29cd5 100644 --- a/deploy/bundle.yaml +++ b/deploy/bundle.yaml @@ -14423,7 +14423,7 @@ spec: postgresVersion: description: The major version of PostgreSQL installed in the PostgreSQL image - maximum: 16 + maximum: 17 minimum: 12 type: integer proxy: @@ -18212,6 +18212,8 @@ spec: items: type: string type: array + patroniVersion: + type: string pgbouncer: properties: ready: @@ -18249,17 +18251,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 @@ -20884,7 +20880,7 @@ spec: type: string toPostgresVersion: description: The major version of PostgreSQL to be upgraded to. - maximum: 16 + maximum: 17 minimum: 13 type: integer tolerations: @@ -23952,7 +23948,7 @@ spec: fromPostgresVersion: description: The major version of PostgreSQL before the upgrade. maximum: 17 - minimum: 10 + minimum: 12 type: integer image: description: The image name to use for major PostgreSQL upgrades. @@ -25496,7 +25492,7 @@ spec: toPostgresVersion: description: The major version of PostgreSQL to be upgraded to. maximum: 17 - minimum: 10 + minimum: 13 type: integer tolerations: description: |- @@ -39737,7 +39733,7 @@ spec: description: The major version of PostgreSQL installed in the PostgreSQL image maximum: 17 - minimum: 10 + minimum: 12 type: integer proxy: description: The specification of a proxy that connects to PostgreSQL. @@ -45706,6 +45702,7 @@ rules: resources: - configmaps - persistentvolumeclaims + - pods - secrets - serviceaccounts - services @@ -45743,16 +45740,6 @@ rules: verbs: - create - patch -- apiGroups: - - "" - resources: - - pods - verbs: - - delete - - get - - list - - patch - - watch - apiGroups: - apps resources: diff --git a/deploy/cr.yaml b/deploy/cr.yaml index c8a20b7cee..6124a8c72c 100644 --- a/deploy/cr.yaml +++ b/deploy/cr.yaml @@ -120,9 +120,9 @@ spec: # test-label: value - image: perconalab/percona-postgresql-operator:main-ppg16-postgres + image: perconalab/percona-postgresql-operator:main-ppg17-postgres imagePullPolicy: Always - postgresVersion: 16 + postgresVersion: 17 # port: 5432 # expose: @@ -236,7 +236,7 @@ spec: proxy: pgBouncer: replicas: 3 - image: perconalab/percona-postgresql-operator:main-ppg16-pgbouncer + image: perconalab/percona-postgresql-operator:main-ppg17-pgbouncer # exposeSuperusers: true # resources: # limits: @@ -318,7 +318,7 @@ spec: pgbackrest: # metadata: # labels: - image: perconalab/percona-postgresql-operator:main-ppg16-pgbackrest + image: perconalab/percona-postgresql-operator:main-ppg17-pgbackrest # # containers: # pgbackrest: diff --git a/deploy/crd.yaml b/deploy/crd.yaml index 8f6116adf4..795cd8257e 100644 --- a/deploy/crd.yaml +++ b/deploy/crd.yaml @@ -14423,7 +14423,7 @@ spec: postgresVersion: description: The major version of PostgreSQL installed in the PostgreSQL image - maximum: 16 + maximum: 17 minimum: 12 type: integer proxy: @@ -18212,6 +18212,8 @@ spec: items: type: string type: array + patroniVersion: + type: string pgbouncer: properties: ready: @@ -18249,17 +18251,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 @@ -20884,7 +20880,7 @@ spec: type: string toPostgresVersion: description: The major version of PostgreSQL to be upgraded to. - maximum: 16 + maximum: 17 minimum: 13 type: integer tolerations: @@ -23952,7 +23948,7 @@ spec: fromPostgresVersion: description: The major version of PostgreSQL before the upgrade. maximum: 17 - minimum: 10 + minimum: 12 type: integer image: description: The image name to use for major PostgreSQL upgrades. @@ -25496,7 +25492,7 @@ spec: toPostgresVersion: description: The major version of PostgreSQL to be upgraded to. maximum: 17 - minimum: 10 + minimum: 13 type: integer tolerations: description: |- @@ -39737,7 +39733,7 @@ spec: description: The major version of PostgreSQL installed in the PostgreSQL image maximum: 17 - minimum: 10 + minimum: 12 type: integer proxy: description: The specification of a proxy that connects to PostgreSQL. diff --git a/deploy/cw-bundle.yaml b/deploy/cw-bundle.yaml index 683f505987..a3f5637b97 100644 --- a/deploy/cw-bundle.yaml +++ b/deploy/cw-bundle.yaml @@ -14423,7 +14423,7 @@ spec: postgresVersion: description: The major version of PostgreSQL installed in the PostgreSQL image - maximum: 16 + maximum: 17 minimum: 12 type: integer proxy: @@ -18212,6 +18212,8 @@ spec: items: type: string type: array + patroniVersion: + type: string pgbouncer: properties: ready: @@ -18249,17 +18251,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 @@ -20884,7 +20880,7 @@ spec: type: string toPostgresVersion: description: The major version of PostgreSQL to be upgraded to. - maximum: 16 + maximum: 17 minimum: 13 type: integer tolerations: @@ -23952,7 +23948,7 @@ spec: fromPostgresVersion: description: The major version of PostgreSQL before the upgrade. maximum: 17 - minimum: 10 + minimum: 12 type: integer image: description: The image name to use for major PostgreSQL upgrades. @@ -25496,7 +25492,7 @@ spec: toPostgresVersion: description: The major version of PostgreSQL to be upgraded to. maximum: 17 - minimum: 10 + minimum: 13 type: integer tolerations: description: |- @@ -39737,7 +39733,7 @@ spec: description: The major version of PostgreSQL installed in the PostgreSQL image maximum: 17 - minimum: 10 + minimum: 12 type: integer proxy: description: The specification of a proxy that connects to PostgreSQL. @@ -45706,6 +45702,7 @@ rules: resources: - configmaps - persistentvolumeclaims + - pods - secrets - serviceaccounts - services @@ -45743,16 +45740,6 @@ rules: verbs: - create - patch -- apiGroups: - - "" - resources: - - pods - verbs: - - delete - - get - - list - - patch - - watch - apiGroups: - apps resources: diff --git a/deploy/cw-rbac.yaml b/deploy/cw-rbac.yaml index 37081c9a0b..def6ca41b7 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: diff --git a/deploy/rbac.yaml b/deploy/rbac.yaml index 4d630e43c4..01db434099 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: diff --git a/e2e-tests/tests/demand-backup/18-create-restore-s3.yaml b/e2e-tests/tests/demand-backup/18-create-restore-s3.yaml index 9b29800edc..20ae096fed 100644 --- a/e2e-tests/tests/demand-backup/18-create-restore-s3.yaml +++ b/e2e-tests/tests/demand-backup/18-create-restore-s3.yaml @@ -7,7 +7,7 @@ commands: source ../../functions - primary=$(get_pod_by_role demand-backup master name) + primary=$(get_pod_by_role demand-backup primary name) latest_full_repo1_backup=$(kubectl -n ${NAMESPACE} exec ${primary} -- pgbackrest info --output json | jq '[.[] | .backup[] | select(.type == "full") | select(.database.["repo-key"] == 1)][-1].label') cat < 0 { for _, instance := range instances { - if instance.Labels[naming.LabelRole] == "master" { + // K8SPG-648: patroni v4.0.0 deprecated "master" role. + // We should use "primary" instead + if instance.Labels[naming.LabelRole] == "master" || instance.Labels[naming.LabelRole] == "primary" { keep = append(keep, instance) } } } for _, instance := range instances { - if instance.Labels[naming.LabelRole] != "master" && len(keep) < want { + // K8SPG-648: patroni v4.0.0 deprecated master role + // we should use primary instead + if instance.Labels[naming.LabelRole] != "master" && instance.Labels[naming.LabelRole] != "primary" && len(keep) < want { keep = append(keep, instance) } } @@ -1048,7 +1057,6 @@ func podsToKeep(instances []corev1.Pod, want map[string]int) []corev1.Pod { } return keepPodList - } // +kubebuilder:rbac:groups="apps",resources="statefulsets",verbs={list} @@ -1216,7 +1224,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/instance_rollout_test.go b/internal/controller/postgrescluster/instance_rollout_test.go index c6b3f0cbcc..d2cb1605de 100644 --- a/internal/controller/postgrescluster/instance_rollout_test.go +++ b/internal/controller/postgrescluster/instance_rollout_test.go @@ -25,12 +25,16 @@ import ( "github.com/percona/percona-postgresql-operator/internal/initialize" "github.com/percona/percona-postgresql-operator/internal/testing/cmp" + pNaming "github.com/percona/percona-postgresql-operator/percona/naming" "github.com/percona/percona-postgresql-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1" ) func TestReconcilerRolloutInstance(t *testing.T) { ctx := context.Background() cluster := new(v1beta1.PostgresCluster) + cluster.Annotations = map[string]string{ + pNaming.ToCrunchyAnnotation(pNaming.AnnotationPatroniVersion): "4.0.1", + } t.Run("Singleton", func(t *testing.T) { instances := []*Instance{ @@ -42,7 +46,7 @@ func TestReconcilerRolloutInstance(t *testing.T) { Name: "one-pod-bruh", Labels: map[string]string{ "controller-revision-hash": "gamma", - "postgres-operator.crunchydata.com/role": "master", + "postgres-operator.crunchydata.com/role": "primary", }, }, Status: corev1.PodStatus{ @@ -104,7 +108,10 @@ func TestReconcilerRolloutInstance(t *testing.T) { Name: "the-pod", Labels: map[string]string{ "controller-revision-hash": "gamma", - "postgres-operator.crunchydata.com/role": "master", + "postgres-operator.crunchydata.com/role": "primary", + }, + Annotations: map[string]string{ + "status": `{"version":"4.0.0"}`, }, }, }}, @@ -134,7 +141,7 @@ func TestReconcilerRolloutInstance(t *testing.T) { // A switchover to any viable candidate. assert.DeepEqual(t, command[:2], []string{"patronictl", "switchover"}) - assert.Assert(t, sets.NewString(command...).Has("--master=the-pod")) + assert.Assert(t, sets.NewString(command...).Has("--primary=the-pod")) assert.Assert(t, sets.NewString(command...).Has("--candidate=")) // Indicate success through stdout. @@ -214,7 +221,7 @@ func TestReconcilerRolloutInstances(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{ "controller-revision-hash": "gamma", - "postgres-operator.crunchydata.com/role": "master", + "postgres-operator.crunchydata.com/role": "primary", }, }, Status: corev1.PodStatus{ @@ -259,7 +266,7 @@ func TestReconcilerRolloutInstances(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{ "controller-revision-hash": "beta", - "postgres-operator.crunchydata.com/role": "master", + "postgres-operator.crunchydata.com/role": "primary", }, }, Status: corev1.PodStatus{ @@ -374,7 +381,7 @@ func TestReconcilerRolloutInstances(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Labels: map[string]string{ "controller-revision-hash": "beta", - "postgres-operator.crunchydata.com/role": "master", + "postgres-operator.crunchydata.com/role": "primary", }, }, Status: corev1.PodStatus{ diff --git a/internal/controller/postgrescluster/patroni.go b/internal/controller/postgrescluster/patroni.go index 09f74ca876..2f5456d05f 100644 --- a/internal/controller/postgrescluster/patroni.go +++ b/internal/controller/postgrescluster/patroni.go @@ -87,14 +87,26 @@ func (r *Reconciler) handlePatroniRestarts( // replicas here, replicas will typically restart first because we see them // first. if primaryNeedsRestart != nil { + pod := primaryNeedsRestart.Pods[0] exec := patroni.Executor(func( ctx context.Context, stdin io.Reader, stdout, stderr io.Writer, command ...string, ) error { - pod := primaryNeedsRestart.Pods[0] return r.PodExec(ctx, pod.Namespace, pod.Name, container, stdin, stdout, stderr, command...) }) - return errors.WithStack(exec.RestartPendingMembers(ctx, "master", naming.PatroniScope(cluster))) + patroniVer4, err := cluster.IsPatroniVer4() + if err != nil { + return errors.Wrap(err, "failed to check if patroni v4 is used") + } + + // K8SPG-648: patroni v4.0.0 deprecated "master" role. + // We should use "primary" instead + role := "primary" + if !patroniVer4 { + role = "master" + } + + return errors.WithStack(exec.RestartPendingMembers(ctx, role, naming.PatroniScope(cluster))) } // When the primary does not need to restart but a replica does, restart all @@ -355,7 +367,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{ @@ -449,7 +460,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 @@ -529,7 +541,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...) } @@ -540,7 +553,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/naming/labels.go b/internal/naming/labels.go index f51897749c..ab8f587eaa 100644 --- a/internal/naming/labels.go +++ b/internal/naming/labels.go @@ -96,7 +96,8 @@ const ( // RolePatroniLeader is the LabelRole that Patroni sets on the Pod that is // currently the leader. - RolePatroniLeader = "master" + RolePatroniLeader = "primary" + RolePatroniLeaderDeprecated = "master" // K8SPG-648: patroni v4.0.0 deprecated "master" role. // RolePatroniReplica is a LabelRole value that Patroni sets on Pods that are // following the leader. diff --git a/internal/patroni/api.go b/internal/patroni/api.go index c779f8b3af..af9d9739ec 100644 --- a/internal/patroni/api.go +++ b/internal/patroni/api.go @@ -20,7 +20,7 @@ type API interface { // ChangePrimaryAndWait tries to demote the current Patroni leader. It // returns true when an election completes successfully. When Patroni is // paused, next cannot be blank. - ChangePrimaryAndWait(ctx context.Context, current, next string) (bool, error) + ChangePrimaryAndWait(ctx context.Context, current, next string, patroniVer4 bool) (bool, error) // ReplaceConfiguration replaces Patroni's entire dynamic configuration. ReplaceConfiguration(ctx context.Context, configuration map[string]any) error @@ -39,13 +39,20 @@ var _ API = Executor(nil) // waits up to two "loop_wait" or until an error occurs. When Patroni is paused, // next cannot be blank. Similar to the "POST /switchover" REST endpoint. func (exec Executor) ChangePrimaryAndWait( - ctx context.Context, current, next string, + ctx context.Context, current, next string, patroniVer4 bool, ) (bool, error) { var stdout, stderr bytes.Buffer - err := exec(ctx, nil, &stdout, &stderr, - "patronictl", "switchover", "--scheduled=now", "--force", - "--master="+current, "--candidate="+next) + // K8SPG-648: patroni v4.0.0 deprecated "master" role. + // We should use "primary" instead + cmd := []string{"patronictl", "switchover", "--scheduled=now", "--force", "--candidate=" + next} + if patroniVer4 { + cmd = append(cmd, "--primary="+current) + } else { + cmd = append(cmd, "--master="+current) + } + + err := exec(ctx, nil, &stdout, &stderr, cmd...) log := logging.FromContext(ctx) log.V(1).Info("changed primary", diff --git a/internal/patroni/api_test.go b/internal/patroni/api_test.go index 1603d2fc75..f99eccc6bd 100644 --- a/internal/patroni/api_test.go +++ b/internal/patroni/api_test.go @@ -36,7 +36,7 @@ func TestExecutorChangePrimaryAndWait(t *testing.T) { ) error { called = true assert.DeepEqual(t, command, strings.Fields( - `patronictl switchover --scheduled=now --force --master=old --candidate=new`, + `patronictl switchover --scheduled=now --force --candidate=new --primary=old`, )) assert.Assert(t, stdin == nil, "expected no stdin, got %T", stdin) assert.Assert(t, stderr != nil, "should capture stderr") @@ -44,7 +44,7 @@ func TestExecutorChangePrimaryAndWait(t *testing.T) { return nil } - _, _ = Executor(exec).ChangePrimaryAndWait(context.Background(), "old", "new") + _, _ = Executor(exec).ChangePrimaryAndWait(context.Background(), "old", "new", true) assert.Assert(t, called) }) @@ -54,7 +54,7 @@ func TestExecutorChangePrimaryAndWait(t *testing.T) { context.Context, io.Reader, io.Writer, io.Writer, ...string, ) error { return expected - }).ChangePrimaryAndWait(context.Background(), "any", "thing") + }).ChangePrimaryAndWait(context.Background(), "any", "thing", true) assert.Equal(t, expected, actual) }) @@ -65,7 +65,7 @@ func TestExecutorChangePrimaryAndWait(t *testing.T) { ) error { _, _ = stdout.Write([]byte(`no luck`)) return nil - }).ChangePrimaryAndWait(context.Background(), "any", "thing") + }).ChangePrimaryAndWait(context.Background(), "any", "thing", true) assert.Assert(t, !success, "expected failure message to become false") @@ -74,7 +74,7 @@ func TestExecutorChangePrimaryAndWait(t *testing.T) { ) error { _, _ = stdout.Write([]byte(`Successfully switched over to something`)) return nil - }).ChangePrimaryAndWait(context.Background(), "any", "thing") + }).ChangePrimaryAndWait(context.Background(), "any", "thing", true) assert.Assert(t, success, "expected success message to become true") }) diff --git a/internal/patroni/config.go b/internal/patroni/config.go index 57a2c9c9e2..1a937f8d93 100644 --- a/internal/patroni/config.go +++ b/internal/patroni/config.go @@ -565,9 +565,23 @@ func instanceYAML( postgresql[pgBackRestCreateReplicaMethod] = map[string]any{ "command": strings.Join(quoted, " "), "keep_data": true, - "no_master": true, + "no_leader": true, "no_params": true, } + // K8SPG-648: patroni v4.0.0 deprecated "no_master". + // We should use "no_leader" instead + patroniVer4, err := cluster.IsPatroniVer4() + if err != nil { + return "", fmt.Errorf("failed to check if patroni v4 is used: %w", err) + } + if !patroniVer4 { + postgresql[pgBackRestCreateReplicaMethod] = map[string]any{ + "command": strings.Join(quoted, " "), + "keep_data": true, + "no_master": true, + "no_params": true, + } + } methods = append([]string{pgBackRestCreateReplicaMethod}, methods...) } diff --git a/internal/patroni/config_test.go b/internal/patroni/config_test.go index 7071cc974a..24a8cc6858 100644 --- a/internal/patroni/config_test.go +++ b/internal/patroni/config_test.go @@ -20,6 +20,7 @@ import ( "github.com/percona/percona-postgresql-operator/internal/postgres" "github.com/percona/percona-postgresql-operator/internal/testing/cmp" "github.com/percona/percona-postgresql-operator/internal/testing/require" + pNaming "github.com/percona/percona-postgresql-operator/percona/naming" "github.com/percona/percona-postgresql-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1" ) @@ -876,7 +877,14 @@ func TestInstanceEnvironment(t *testing.T) { func TestInstanceYAML(t *testing.T) { t.Parallel() - cluster := &v1beta1.PostgresCluster{Spec: v1beta1.PostgresClusterSpec{PostgresVersion: 12}} + cluster := &v1beta1.PostgresCluster{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + pNaming.ToCrunchyAnnotation(pNaming.AnnotationPatroniVersion): "4.0.1", + }, + }, + Spec: v1beta1.PostgresClusterSpec{PostgresVersion: 12}, + } instance := new(v1beta1.PostgresInstanceSetSpec) data, err := instanceYAML(cluster, instance, nil) @@ -924,7 +932,7 @@ postgresql: command: '''bash'' ''-ceu'' ''--'' ''install --directory --mode=0700 "${PGDATA?}" && exec "$@"'' ''-'' ''some'' ''backrest'' ''cmd''' keep_data: true - no_master: true + no_leader: true no_params: true pgpass: /tmp/.pgpass use_unix_socket: true @@ -965,14 +973,19 @@ postgresql: restapi: {} tags: {} `, "\t\n")+"\n") - } func TestPGBackRestCreateReplicaCommand(t *testing.T) { t.Parallel() shellcheck := require.ShellCheck(t) - cluster := new(v1beta1.PostgresCluster) + cluster := &v1beta1.PostgresCluster{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + pNaming.ToCrunchyAnnotation(pNaming.AnnotationPatroniVersion): "4.0.1", + }, + }, + } instance := new(v1beta1.PostgresInstanceSetSpec) data, err := instanceYAML(cluster, instance, []string{"some", "backrest", "cmd"}) diff --git a/internal/patroni/reconcile.go b/internal/patroni/reconcile.go index ed69ddd8bf..7a03019a64 100644 --- a/internal/patroni/reconcile.go +++ b/internal/patroni/reconcile.go @@ -185,7 +185,9 @@ func PodIsPrimary(pod metav1.Object) bool { // - https://github.com/zalando/patroni/blob/v3.1.1/patroni/ha.py#L782 // - https://github.com/zalando/patroni/blob/v3.1.1/patroni/ha.py#L1574 status := pod.GetAnnotations()["status"] - return strings.Contains(status, `"role":"master"`) + // K8SPG-648: patroni v4.0.0 deprecated "master" role. + // We should use "primary" instead + return strings.Contains(status, `"role":"master"`) || strings.Contains(status, `"role":"primary"`) } // PodIsStandbyLeader returns whether or not pod is currently acting as a "standby_leader". diff --git a/percona/controller/pgcluster/controller.go b/percona/controller/pgcluster/controller.go index fad89f8821..169ac254ba 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" @@ -9,6 +10,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" @@ -17,7 +19,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" @@ -34,6 +38,7 @@ import ( "github.com/percona/percona-postgresql-operator/internal/logging" "github.com/percona/percona-postgresql-operator/internal/naming" "github.com/percona/percona-postgresql-operator/internal/postgres" + "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" @@ -176,6 +181,7 @@ 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="",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) @@ -228,6 +234,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") } @@ -303,6 +318,101 @@ 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 { + if cr.Annotations == nil { + cr.Annotations = make(map[string]string) + } + + if cr.Status.Postgres.Version == cr.Spec.PostgresVersion && cr.Status.PatroniVersion != "" { + cr.Annotations[pNaming.AnnotationPatroniVersion] = cr.Status.PatroniVersion + return nil + } + + 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) { + 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 patroni version") + } + + return errPatroniVersionCheckWait + } + + 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/controller_test.go b/percona/controller/pgcluster/controller_test.go index 3c7100419e..6bafdb0659 100644 --- a/percona/controller/pgcluster/controller_test.go +++ b/percona/controller/pgcluster/controller_test.go @@ -68,7 +68,10 @@ var _ = Describe("PG Cluster", Ordered, func() { }) It("should create PerconaPGCluster", func() { + status := cr.Status Expect(k8sClient.Create(ctx, cr)).Should(Succeed()) + cr.Status = status + Expect(k8sClient.Status().Update(ctx, cr)).Should(Succeed()) }) Context("Reconcile controller", func() { @@ -114,7 +117,10 @@ var _ = Describe("Annotations", Ordered, func() { cr.Annotations["pgv2.percona.com/trigger-switchover"] = "true" cr.Annotations["egedemo.com/test"] = "true" + status := cr.Status Expect(k8sClient.Create(ctx, cr)).Should(Succeed()) + cr.Status = status + Expect(k8sClient.Status().Update(ctx, cr)).Should(Succeed()) }) Context("Reconcile controller", func() { @@ -179,7 +185,10 @@ var _ = Describe("PMM sidecar", Ordered, func() { It("should create PerconaPGCluster with pmm enabled", func() { cr.Spec.PMM.Enabled = true + status := cr.Status Expect(k8sClient.Create(ctx, cr)).Should(Succeed()) + cr.Status = status + Expect(k8sClient.Status().Update(ctx, cr)).Should(Succeed()) }) Context("Reconcile controller", func() { @@ -346,7 +355,10 @@ var _ = Describe("Monitor user password change", Ordered, func() { It("should create PerconaPGCluster with pmm enabled", func() { cr.Spec.PMM.Enabled = true + status := cr.Status Expect(k8sClient.Create(ctx, cr)).Should(Succeed()) + cr.Status = status + Expect(k8sClient.Status().Update(ctx, cr)).Should(Succeed()) }) It("controller should reconcile", func() { @@ -526,7 +538,10 @@ var _ = Describe("Watching secrets", Ordered, func() { reconcileCount := 0 Context("Create cluster and wait until Reconcile stops", func() { It("should create PerconaPGCluster and PostgresCluster", func() { + status := cr.Status Expect(k8sClient.Create(ctx, cr)).Should(Succeed()) + cr.Status = status + Expect(k8sClient.Status().Update(ctx, cr)).Should(Succeed()) Eventually(func() error { return k8sClient.Get(ctx, client.ObjectKeyFromObject(cr), new(v2.PerconaPGCluster)) @@ -683,7 +698,10 @@ var _ = Describe("Users", Ordered, func() { It("should read defautl cr.yaml and create PerconaPGCluster without PMM", func() { Expect(err).NotTo(HaveOccurred()) + status := cr.Status Expect(k8sClient.Create(ctx, cr)).Should(Succeed()) + cr.Status = status + Expect(k8sClient.Status().Update(ctx, cr)).Should(Succeed()) }) It("should add default user", func() { @@ -776,7 +794,10 @@ var _ = Describe("Users", Ordered, func() { Expect(err).NotTo(HaveOccurred()) cr.Spec.PMM.Enabled = true + status := cr.Status Expect(k8sClient.Create(ctx, cr)).Should(Succeed()) + cr.Status = status + Expect(k8sClient.Status().Update(ctx, cr)).Should(Succeed()) }) It("should create defaul and monitor user", func() { @@ -841,7 +862,10 @@ var _ = Describe("Version labels", Ordered, func() { }) It("should create PerconaPGCluster", func() { + status := cr.Status Expect(k8sClient.Create(ctx, cr)).Should(Succeed()) + cr.Status = status + Expect(k8sClient.Status().Update(ctx, cr)).Should(Succeed()) }) It("should reconcile", func() { @@ -939,7 +963,10 @@ var _ = Describe("Services with LoadBalancerSourceRanges", Ordered, func() { Type: "LoadBalancer", LoadBalancerSourceRanges: []string{"10.10.10.10/16"}, } + status := cr.Status Expect(k8sClient.Create(ctx, cr)).Should(Succeed()) + cr.Status = status + Expect(k8sClient.Status().Update(ctx, cr)).Should(Succeed()) }) It("should reconcile", func() { @@ -992,7 +1019,10 @@ var _ = Describe("Pause with backup", Ordered, func() { cr.Spec.Backups.PGBackRest.Manual = nil It("should create PerconaPGCluster", func() { + status := cr.Status Expect(k8sClient.Create(ctx, cr)).Should(Succeed()) + cr.Status = status + Expect(k8sClient.Status().Update(ctx, cr)).Should(Succeed()) }) pgBackup := &v2.PerconaPGBackup{ @@ -1146,7 +1176,10 @@ var _ = Describe("Security context", Ordered, func() { cr.Spec.Backups.PGBackRest.RepoHost = &v1beta1.PGBackRestRepoHost{ SecurityContext: podSecContext, } + status := cr.Status Expect(k8sClient.Create(ctx, cr)).Should(Succeed()) + cr.Status = status + Expect(k8sClient.Status().Update(ctx, cr)).Should(Succeed()) }) It("should reconcile", func() { @@ -1227,7 +1260,10 @@ var _ = Describe("Operator-created sidecar container resources", Ordered, func() }) It("should create PerconaPGCluster", func() { + status := cr.Status Expect(k8sClient.Create(ctx, cr)).Should(Succeed()) + cr.Status = status + Expect(k8sClient.Status().Update(ctx, cr)).Should(Succeed()) }) It("should reconcile", func() { @@ -1316,7 +1352,10 @@ var _ = Describe("Validate TLS", Ordered, func() { cr.Default() It("should create PerconaPGCluster", func() { + status := cr.Status Expect(k8sClient.Create(ctx, cr)).Should(Succeed()) + cr.Status = status + Expect(k8sClient.Status().Update(ctx, cr)).Should(Succeed()) }) checkSecretProjection := func(cr *v2.PerconaPGCluster, projection *corev1.SecretProjection, secretName string, neededKeys []string) { diff --git a/percona/controller/pgcluster/finalizer_test.go b/percona/controller/pgcluster/finalizer_test.go index 4f7d9ed58f..7c4a578a5c 100644 --- a/percona/controller/pgcluster/finalizer_test.go +++ b/percona/controller/pgcluster/finalizer_test.go @@ -60,7 +60,10 @@ var _ = Describe("Finalizers", Ordered, func() { controllerutil.AddFinalizer(cr, v2.FinalizerDeletePVC) It("should create PerconaPGCluster", func() { + status := cr.Status Expect(k8sClient.Create(ctx, cr)).Should(Succeed()) + cr.Status = status + Expect(k8sClient.Status().Update(ctx, cr)).Should(Succeed()) }) It("should create PVCs", func() { @@ -156,7 +159,10 @@ var _ = Describe("Finalizers", Ordered, func() { }) It("should create PerconaPGCluster", func() { + status := cr.Status Expect(k8sClient.Create(ctx, cr)).Should(Succeed()) + cr.Status = status + Expect(k8sClient.Status().Update(ctx, cr)).Should(Succeed()) }) It("should reconcile PerconaPGCluster", func() { @@ -236,7 +242,10 @@ var _ = Describe("Finalizers", Ordered, func() { controllerutil.AddFinalizer(cr, v2.FinalizerDeleteSSL) It("should create PerconaPGCluster", func() { + status := cr.Status Expect(k8sClient.Create(ctx, cr)).Should(Succeed()) + cr.Status = status + Expect(k8sClient.Status().Update(ctx, cr)).Should(Succeed()) }) It("should reconcile PerconaPGCluster", func() { @@ -304,7 +313,10 @@ var _ = Describe("Finalizers", Ordered, func() { controllerutil.RemoveFinalizer(cr, v2.FinalizerDeleteSSL) It("should create PerconaPGCluster", func() { + status := cr.Status Expect(k8sClient.Create(ctx, cr)).Should(Succeed()) + cr.Status = status + Expect(k8sClient.Status().Update(ctx, cr)).Should(Succeed()) }) It("should reconcile PerconaPGCluster", func() { @@ -354,7 +366,10 @@ var _ = Describe("Finalizers", Ordered, func() { controllerutil.RemoveFinalizer(cr, v2.FinalizerStopWatchers) It("should create PerconaPGCluster", func() { + status := cr.Status Expect(k8sClient.Create(ctx, cr)).Should(Succeed()) + cr.Status = status + Expect(k8sClient.Status().Update(ctx, cr)).Should(Succeed()) }) It("should reconcile PerconaPGCluster", func() { diff --git a/percona/controller/pgcluster/status.go b/percona/controller/pgcluster/status.go index 30123abd6a..0dd3893fdb 100644 --- a/percona/controller/pgcluster/status.go +++ b/percona/controller/pgcluster/status.go @@ -2,6 +2,7 @@ package pgcluster import ( "context" + "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" @@ -104,19 +105,16 @@ 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, - InstalledCustomExtensions: installedCustomExtensions, + 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.InstalledCustomExtensions = installedCustomExtensions cluster.Status.State = r.getState(cr, &cluster.Status, status) diff --git a/percona/controller/pgcluster/status_test.go b/percona/controller/pgcluster/status_test.go index 5c7473d564..b4f56f0f93 100644 --- a/percona/controller/pgcluster/status_test.go +++ b/percona/controller/pgcluster/status_test.go @@ -52,7 +52,10 @@ var _ = Describe("PG Cluster status", Ordered, func() { cr, err := readDefaultCR(crName, ns) It("should read defautl cr.yaml and create PerconaPGCluster", func() { Expect(err).NotTo(HaveOccurred()) + status := cr.Status Expect(k8sClient.Create(ctx, cr)).Should(Succeed()) + cr.Status = status + Expect(k8sClient.Status().Update(ctx, cr)).Should(Succeed()) }) It("should reconcile and create Crunchy PostgreCluster", func() { @@ -107,7 +110,10 @@ var _ = Describe("PG Cluster status", Ordered, func() { cr, err := readDefaultCR(crName, ns) It("should read defautl cr.yaml and create PerconaPGCluster", func() { Expect(err).NotTo(HaveOccurred()) + status := cr.Status Expect(k8sClient.Create(ctx, cr)).Should(Succeed()) + cr.Status = status + Expect(k8sClient.Status().Update(ctx, cr)).Should(Succeed()) }) It("should reconcile and create Crunchy PostgreCluster", func() { @@ -141,7 +147,10 @@ var _ = Describe("PG Cluster status", Ordered, func() { cr, err := readDefaultCR(crName, ns) It("should read defautl cr.yaml and create PerconaPGCluster", func() { Expect(err).NotTo(HaveOccurred()) + status := cr.Status Expect(k8sClient.Create(ctx, cr)).Should(Succeed()) + cr.Status = status + Expect(k8sClient.Status().Update(ctx, cr)).Should(Succeed()) }) It("should reconcile and create Crunchy PostgreCluster", func() { @@ -243,7 +252,10 @@ var _ = Describe("PG Cluster status", Ordered, func() { cr, err := readDefaultCR(crName, ns) It("should read defautl cr.yaml and create PerconaPGCluster", func() { Expect(err).NotTo(HaveOccurred()) + status := cr.Status Expect(k8sClient.Create(ctx, cr)).Should(Succeed()) + cr.Status = status + Expect(k8sClient.Status().Update(ctx, cr)).Should(Succeed()) }) It("should reconcile and create Crunchy PostgreCluster", func() { diff --git a/percona/controller/pgcluster/suite_test.go b/percona/controller/pgcluster/suite_test.go index 9f458bf98e..4316600709 100644 --- a/percona/controller/pgcluster/suite_test.go +++ b/percona/controller/pgcluster/suite_test.go @@ -22,7 +22,6 @@ import ( v2 "github.com/percona/percona-postgresql-operator/pkg/apis/pgv2.percona.com/v2" "github.com/percona/percona-postgresql-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1" - //+kubebuilder:scaffold:imports ) // These tests use Ginkgo (BDD-style Go testing framework). Refer to diff --git a/percona/controller/pgcluster/testutils_test.go b/percona/controller/pgcluster/testutils_test.go index 49076f2a66..3b04fe5c86 100644 --- a/percona/controller/pgcluster/testutils_test.go +++ b/percona/controller/pgcluster/testutils_test.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" + "github.com/pkg/errors" "go.opentelemetry.io/otel" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -90,6 +91,11 @@ func readTestCR(name, namespace, testFile string) (*v2.PerconaPGCluster, error) cr.Name = name cr.Namespace = namespace + if cr.Spec.PostgresVersion == 0 { + return nil, errors.New("postgresVersion should be specified") + } + cr.Status.Postgres.Version = cr.Spec.PostgresVersion + cr.Status.PatroniVersion = "4.0.0" return cr, nil } @@ -107,6 +113,8 @@ func readDefaultCR(name, namespace string) (*v2.PerconaPGCluster, error) { cr.Name = name cr.Namespace = namespace + cr.Status.Postgres.Version = cr.Spec.PostgresVersion + cr.Status.PatroniVersion = "4.0.0" return cr, nil } diff --git a/percona/controller/testdata/sidecar-resources-cr.yaml b/percona/controller/testdata/sidecar-resources-cr.yaml index 3cf523a664..e2487a0f05 100644 --- a/percona/controller/testdata/sidecar-resources-cr.yaml +++ b/percona/controller/testdata/sidecar-resources-cr.yaml @@ -6,9 +6,9 @@ metadata: spec: crVersion: 2.6.0 - image: perconalab/percona-postgresql-operator:main-ppg16-postgres + image: perconalab/percona-postgresql-operator:main-ppg17-postgres imagePullPolicy: Always - postgresVersion: 16 + postgresVersion: 17 instances: - name: instance1 @@ -39,7 +39,7 @@ spec: proxy: pgBouncer: replicas: 3 - image: perconalab/percona-postgresql-operator:main-ppg16-pgbouncer + image: perconalab/percona-postgresql-operator:main-ppg17-pgbouncer containers: pgbouncerConfig: resources: @@ -58,7 +58,7 @@ spec: backups: pgbackrest: - image: perconalab/percona-postgresql-operator:main-ppg16-pgbackrest + image: perconalab/percona-postgresql-operator:main-ppg17-pgbackrest # containers: pgbackrest: 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/postgres/common.go b/percona/postgres/common.go index 9997e05bdd..1316f4ae40 100644 --- a/percona/postgres/common.go +++ b/percona/postgres/common.go @@ -3,6 +3,7 @@ package perconaPG import ( "context" + gover "github.com/hashicorp/go-version" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/labels" @@ -13,11 +14,19 @@ import ( func GetPrimaryPod(ctx context.Context, cli client.Client, cr *v2.PerconaPGCluster) (*corev1.Pod, error) { podList := &corev1.PodList{} + // K8SPG-648: patroni v4.0.0 deprecated "master" role. + // We should use "primary" instead + role := "primary" + patroniVer := gover.Must(gover.NewVersion(cr.Status.PatroniVersion)) + patroniVer4 := patroniVer.Compare(gover.Must(gover.NewVersion("4.0.0"))) >= 0 + if !patroniVer4 { + role = "master" + } err := cli.List(ctx, podList, &client.ListOptions{ Namespace: cr.Namespace, LabelSelector: labels.SelectorFromSet(map[string]string{ "app.kubernetes.io/instance": cr.Name, - "postgres-operator.crunchydata.com/role": "master", + "postgres-operator.crunchydata.com/role": role, }), }) if err != nil { diff --git a/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types.go b/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types.go index 50e18ed1ea..ceea01d6b8 100644 --- a/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types.go +++ b/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types.go @@ -93,7 +93,7 @@ type PerconaPGClusterSpec struct { // The major version of PostgreSQL installed in the PostgreSQL image // +kubebuilder:validation:Required // +kubebuilder:validation:Minimum=12 - // +kubebuilder:validation:Maximum=16 + // +kubebuilder:validation:Maximum=17 // +operator-sdk:csv:customresourcedefinitions:type=spec PostgresVersion int `json:"postgresVersion"` @@ -378,42 +378,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"` diff --git a/pkg/apis/pgv2.percona.com/v2/perconapgupgrade_types.go b/pkg/apis/pgv2.percona.com/v2/perconapgupgrade_types.go index 2c33690138..9dc99076fe 100644 --- a/pkg/apis/pgv2.percona.com/v2/perconapgupgrade_types.go +++ b/pkg/apis/pgv2.percona.com/v2/perconapgupgrade_types.go @@ -65,7 +65,7 @@ type PerconaPGUpgradeSpec struct { // The major version of PostgreSQL to be upgraded to. // +kubebuilder:validation:Required // +kubebuilder:validation:Minimum=13 - // +kubebuilder:validation:Maximum=16 + // +kubebuilder:validation:Maximum=17 ToPostgresVersion int `json:"toPostgresVersion"` // The image to use for PostgreSQL containers after upgrade. diff --git a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/pgupgrade_types.go b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/pgupgrade_types.go index 90aca7b0b1..823a793174 100644 --- a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/pgupgrade_types.go +++ b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/pgupgrade_types.go @@ -11,7 +11,6 @@ import ( // PGUpgradeSpec defines the desired state of PGUpgrade type PGUpgradeSpec struct { - // +optional Metadata *Metadata `json:"metadata,omitempty"` @@ -48,7 +47,7 @@ type PGUpgradeSpec struct { // The major version of PostgreSQL before the upgrade. // +kubebuilder:validation:Required - // +kubebuilder:validation:Minimum=10 + // +kubebuilder:validation:Minimum=12 // +kubebuilder:validation:Maximum=17 FromPostgresVersion int `json:"fromPostgresVersion"` @@ -59,7 +58,7 @@ type PGUpgradeSpec struct { // The major version of PostgreSQL to be upgraded to. // +kubebuilder:validation:Required - // +kubebuilder:validation:Minimum=10 + // +kubebuilder:validation:Minimum=13 // +kubebuilder:validation:Maximum=17 ToPostgresVersion int `json:"toPostgresVersion"` diff --git a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/postgrescluster_types.go b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/postgrescluster_types.go index ec03ca74d2..27e8c063ef 100644 --- a/pkg/apis/postgres-operator.crunchydata.com/v1beta1/postgrescluster_types.go +++ b/pkg/apis/postgres-operator.crunchydata.com/v1beta1/postgrescluster_types.go @@ -8,9 +8,12 @@ import ( "fmt" gover "github.com/hashicorp/go-version" + "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" + + pNaming "github.com/percona/percona-postgresql-operator/percona/naming" ) // PostgresClusterSpec defines the desired state of PostgresCluster @@ -120,7 +123,7 @@ type PostgresClusterSpec struct { // The major version of PostgreSQL installed in the PostgreSQL image // +kubebuilder:validation:Required - // +kubebuilder:validation:Minimum=10 + // +kubebuilder:validation:Minimum=12 // +kubebuilder:validation:Maximum=17 // +operator-sdk:csv:customresourcedefinitions:type=spec,order=1 PostgresVersion int `json:"postgresVersion"` @@ -747,3 +750,16 @@ func (cr *PostgresCluster) CompareVersion(ver string) int { } return crVersion.Compare(gover.Must(gover.NewVersion(ver))) } + +// K8SPG-692 +func (cr *PostgresCluster) IsPatroniVer4() (bool, error) { + patroniVerStr, ok := cr.Annotations[pNaming.ToCrunchyAnnotation(pNaming.AnnotationPatroniVersion)] + if !ok { + return false, errors.New("patroni version annotation was not found") + } + patroniVer, err := gover.NewVersion(patroniVerStr) + if err != nil { + return false, errors.Wrap(err, "failed to get patroni ver") + } + return patroniVer.Compare(gover.Must(gover.NewVersion("4.0.0"))) >= 0, nil +}