From 172c71865ec6f0ed96d66f1e27f396e9f8b003b6 Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Mon, 16 Feb 2026 19:24:04 +0530 Subject: [PATCH 01/17] allow running withoug pgbouncer Signed-off-by: Mayank Shah --- percona/controller/pgcluster/controller.go | 2 +- percona/controller/pgcluster/status.go | 22 +++++++++++++++-- .../v2/perconapgcluster_types.go | 24 +++++++++---------- 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/percona/controller/pgcluster/controller.go b/percona/controller/pgcluster/controller.go index 1237ebf6dc..e1df3555b2 100644 --- a/percona/controller/pgcluster/controller.go +++ b/percona/controller/pgcluster/controller.go @@ -599,7 +599,7 @@ func (r *PGClusterReconciler) reconcileEnvFromSecrets(ctx context.Context, cr *v m[&set.EnvFrom] = set.Metadata } - if len(cr.Spec.Proxy.PGBouncer.EnvFrom) > 0 { + if cr.Spec.Proxy != nil && len(cr.Spec.Proxy.PGBouncer.EnvFrom) > 0 { if cr.Spec.Proxy.PGBouncer.Metadata == nil { cr.Spec.Proxy.PGBouncer.Metadata = new(v1beta1.Metadata) } diff --git a/percona/controller/pgcluster/status.go b/percona/controller/pgcluster/status.go index 1039e96fec..e558b275d8 100644 --- a/percona/controller/pgcluster/status.go +++ b/percona/controller/pgcluster/status.go @@ -2,6 +2,7 @@ package pgcluster import ( "context" + "fmt" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" @@ -11,16 +12,33 @@ import ( "k8s.io/client-go/util/retry" "github.com/percona/percona-postgresql-operator/v2/internal/controller/postgrescluster" + "github.com/percona/percona-postgresql-operator/v2/internal/naming" pNaming "github.com/percona/percona-postgresql-operator/v2/percona/naming" v2 "github.com/percona/percona-postgresql-operator/v2/pkg/apis/pgv2.percona.com/v2" "github.com/percona/percona-postgresql-operator/v2/pkg/apis/postgres-operator.crunchydata.com/v1beta1" ) func (r *PGClusterReconciler) getHost(ctx context.Context, cr *v2.PerconaPGCluster) (string, error) { - svcName := cr.Name + "-pgbouncer" + postgresCluster := &v1beta1.PostgresCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: cr.Name, + Namespace: cr.Namespace, + }, + } + + svcFQDN := func(svcName, ns string) string { + return fmt.Sprintf("%s.%s.svc", svcName, ns) + } + + // If proxy is not configured, use the pod service + if cr.Spec.Proxy == nil || cr.Spec.Proxy.PGBouncer == nil { + return svcFQDN(naming.ClusterPodService(postgresCluster).Name, postgresCluster.Namespace), nil + } + // PGBouncer is not exposed, use the service name + svcName := naming.ClusterPGBouncer(postgresCluster).Name if cr.Spec.Proxy.PGBouncer.ServiceExpose == nil || cr.Spec.Proxy.PGBouncer.ServiceExpose.Type != string(corev1.ServiceTypeLoadBalancer) { - return svcName + "." + cr.Namespace + ".svc", nil + return svcFQDN(svcName, postgresCluster.Namespace), nil } svc := &corev1.Service{} diff --git a/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types.go b/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types.go index 4384177529..a20db0ea32 100644 --- a/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types.go +++ b/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types.go @@ -212,21 +212,19 @@ func (cr *PerconaPGCluster) Default() { cr.Spec.InstanceSets[i].Metadata.Labels[LabelOperatorVersion] = cr.Spec.CRVersion } - if cr.Spec.Proxy == nil { - cr.Spec.Proxy = new(PGProxySpec) - } - - if cr.Spec.Proxy.PGBouncer == nil { - cr.Spec.Proxy.PGBouncer = new(PGBouncerSpec) - } + if cr.Spec.Proxy != nil { + if cr.Spec.Proxy.PGBouncer == nil { + cr.Spec.Proxy.PGBouncer = new(PGBouncerSpec) + } - if cr.Spec.Proxy.PGBouncer.Metadata == nil { - cr.Spec.Proxy.PGBouncer.Metadata = new(crunchyv1beta1.Metadata) - } - if cr.Spec.Proxy.PGBouncer.Metadata.Labels == nil { - cr.Spec.Proxy.PGBouncer.Metadata.Labels = make(map[string]string) + if cr.Spec.Proxy.PGBouncer.Metadata == nil { + cr.Spec.Proxy.PGBouncer.Metadata = new(crunchyv1beta1.Metadata) + } + if cr.Spec.Proxy.PGBouncer.Metadata.Labels == nil { + cr.Spec.Proxy.PGBouncer.Metadata.Labels = make(map[string]string) + } + cr.Spec.Proxy.PGBouncer.Metadata.Labels[LabelOperatorVersion] = cr.Spec.CRVersion } - cr.Spec.Proxy.PGBouncer.Metadata.Labels[LabelOperatorVersion] = cr.Spec.CRVersion t := true f := false From 3ff7fb3783e2723c82908bc07a044762a8ab8c26 Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Mon, 16 Feb 2026 19:28:25 +0530 Subject: [PATCH 02/17] use primary service Signed-off-by: Mayank Shah --- percona/controller/pgcluster/status.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/percona/controller/pgcluster/status.go b/percona/controller/pgcluster/status.go index e558b275d8..715884a9af 100644 --- a/percona/controller/pgcluster/status.go +++ b/percona/controller/pgcluster/status.go @@ -32,7 +32,7 @@ func (r *PGClusterReconciler) getHost(ctx context.Context, cr *v2.PerconaPGClust // If proxy is not configured, use the pod service if cr.Spec.Proxy == nil || cr.Spec.Proxy.PGBouncer == nil { - return svcFQDN(naming.ClusterPodService(postgresCluster).Name, postgresCluster.Namespace), nil + return svcFQDN(naming.ClusterPrimaryService(postgresCluster).Name, postgresCluster.Namespace), nil } // PGBouncer is not exposed, use the service name From 2ec2260403daf32c6c94f5f00b9ecc908059e096 Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Mon, 16 Feb 2026 19:28:52 +0530 Subject: [PATCH 03/17] update comment Signed-off-by: Mayank Shah --- percona/controller/pgcluster/status.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/percona/controller/pgcluster/status.go b/percona/controller/pgcluster/status.go index 715884a9af..b5eb45cb8b 100644 --- a/percona/controller/pgcluster/status.go +++ b/percona/controller/pgcluster/status.go @@ -30,7 +30,7 @@ func (r *PGClusterReconciler) getHost(ctx context.Context, cr *v2.PerconaPGClust return fmt.Sprintf("%s.%s.svc", svcName, ns) } - // If proxy is not configured, use the pod service + // If proxy is not configured, use the primary service if cr.Spec.Proxy == nil || cr.Spec.Proxy.PGBouncer == nil { return svcFQDN(naming.ClusterPrimaryService(postgresCluster).Name, postgresCluster.Namespace), nil } From 84bcbc575815331c544328eff2d3ea1e5f1fdbd5 Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Tue, 17 Feb 2026 10:07:01 +0530 Subject: [PATCH 04/17] add unit test Signed-off-by: Mayank Shah --- .../v2/perconapgcluster_types_test.go | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go b/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go index 5f9cfd68be..43d5dc8439 100644 --- a/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go +++ b/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go @@ -6,8 +6,8 @@ import ( "os" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "gotest.tools/v3/assert" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -55,6 +55,25 @@ func TestPerconaPGCluster_BackupsEnabled(t *testing.T) { } } +func TestPerconaPGCluster_Proxy(t *testing.T) { + t.Run("Proxy is nil", func(t *testing.T) { + cr := new(PerconaPGCluster) + cr.Default() + assert.Nil(t, cr.Spec.Proxy) + }) + t.Run("Proxy is not nil", func(t *testing.T) { + cr := new(PerconaPGCluster) + cr.Spec.Proxy = new(PGProxySpec) + cr.Spec.CRVersion = "2.9.0" + cr.Default() + assert.NotNil(t, cr.Spec.Proxy) + assert.NotNil(t, cr.Spec.Proxy.PGBouncer) + assert.NotNil(t, cr.Spec.Proxy.PGBouncer.Metadata) + assert.NotNil(t, cr.Spec.Proxy.PGBouncer.Metadata.Labels) + assert.Equal(t, cr.Spec.Proxy.PGBouncer.Metadata.Labels[LabelOperatorVersion], cr.Spec.CRVersion) + }) +} + func TestPerconaPGCluster_PostgresImage(t *testing.T) { cluster := new(PerconaPGCluster) cluster.Default() @@ -169,7 +188,7 @@ func TestPerconaPGCluster_ToCrunchy(t *testing.T) { assertClusterFunc: func(t *testing.T, actual *crunchyv1beta1.PostgresCluster, expected *PerconaPGCluster) { assert.Equal(t, actual.Name, expected.Name) assert.Equal(t, actual.Namespace, expected.Namespace) - assert.DeepEqual(t, actual.Finalizers, []string{naming.Finalizer}) + assert.Equal(t, actual.Finalizers, []string{naming.Finalizer}) assert.Equal(t, actual.Spec.PostgresVersion, expected.Spec.PostgresVersion) assert.Equal(t, actual.Labels[LabelOperatorVersion], expected.Spec.CRVersion) assert.Equal(t, len(actual.Spec.InstanceSets), 1) @@ -177,11 +196,11 @@ func TestPerconaPGCluster_ToCrunchy(t *testing.T) { assert.Equal(t, actual.Spec.InstanceSets[0].Name, expected.Spec.InstanceSets[0].Name) assert.Equal(t, actual.Spec.Service.Type, expected.Spec.Expose.Type) - assert.Assert(t, actual.Spec.Service.LoadBalancerClass != nil) + assert.NotNil(t, actual.Spec.Service.LoadBalancerClass) assert.Equal(t, actual.Spec.Service.LoadBalancerClass, expected.Spec.Expose.LoadBalancerClass) assert.Equal(t, actual.Spec.ReplicaService.Type, expected.Spec.ExposeReplicas.Type) - assert.Assert(t, actual.Spec.ReplicaService.LoadBalancerClass != nil) + assert.NotNil(t, actual.Spec.ReplicaService.LoadBalancerClass) assert.Equal(t, actual.Spec.ReplicaService.LoadBalancerClass, expected.Spec.ExposeReplicas.LoadBalancerClass) assert.Equal(t, *actual.Spec.Backups.TrackLatestRestorableTime, true) }, @@ -347,9 +366,9 @@ func TestPerconaPGCluster_ToCrunchy(t *testing.T) { for i, user := range result.Spec.Users { userNames[i] = string(user.Name) } - assert.Assert(t, contains(userNames, "regular-user")) - assert.Assert(t, contains(userNames, "another-user")) - assert.Assert(t, !contains(userNames, UserMonitoring)) + assert.True(t, contains(userNames, "regular-user")) + assert.True(t, contains(userNames, "another-user")) + assert.False(t, contains(userNames, UserMonitoring)) }, }, } @@ -361,12 +380,12 @@ func TestPerconaPGCluster_ToCrunchy(t *testing.T) { crunchyCluster, err := tt.expectedPerconaPGCluster.ToCrunchy(ctx, tt.inputPostgresCluster, scheme) if tt.expectedError { - assert.Assert(t, err != nil) + assert.NotNil(t, err) return } - assert.NilError(t, err) - assert.Assert(t, crunchyCluster != nil) + assert.Nil(t, err) + assert.NotNil(t, crunchyCluster) if tt.assertClusterFunc != nil { tt.assertClusterFunc(t, crunchyCluster, tt.expectedPerconaPGCluster) From 4cb911cad73a91c72a87620f991eec0ab47e1aec Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Tue, 17 Feb 2026 10:07:12 +0530 Subject: [PATCH 05/17] potential nil pointer deref Signed-off-by: Mayank Shah --- percona/controller/pgcluster/controller.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/percona/controller/pgcluster/controller.go b/percona/controller/pgcluster/controller.go index e1df3555b2..d3d796a4fa 100644 --- a/percona/controller/pgcluster/controller.go +++ b/percona/controller/pgcluster/controller.go @@ -599,7 +599,9 @@ func (r *PGClusterReconciler) reconcileEnvFromSecrets(ctx context.Context, cr *v m[&set.EnvFrom] = set.Metadata } - if cr.Spec.Proxy != nil && len(cr.Spec.Proxy.PGBouncer.EnvFrom) > 0 { + if cr.Spec.Proxy != nil && + cr.Spec.Proxy.PGBouncer != nil && + len(cr.Spec.Proxy.PGBouncer.EnvFrom) > 0 { if cr.Spec.Proxy.PGBouncer.Metadata == nil { cr.Spec.Proxy.PGBouncer.Metadata = new(v1beta1.Metadata) } From 188c2d45a451af83f3b7ea25c879c24e19c46a7c Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Tue, 17 Feb 2026 10:22:13 +0530 Subject: [PATCH 06/17] update ToCrunchy unit tests Signed-off-by: Mayank Shah --- .../v2/perconapgcluster_types_test.go | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go b/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go index 43d5dc8439..8307c566ab 100644 --- a/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go +++ b/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go @@ -371,6 +371,38 @@ func TestPerconaPGCluster_ToCrunchy(t *testing.T) { assert.False(t, contains(userNames, UserMonitoring)) }, }, + "handles nil proxy": { + expectedPerconaPGCluster: &PerconaPGCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + Namespace: "test-namespace", + }, + Spec: PerconaPGClusterSpec{ + CRVersion: "2.5.0", + PostgresVersion: 15, + Proxy: nil, + InstanceSets: PGInstanceSets{ + { + Name: "instance1", + Replicas: &[]int32{1}[0], + DataVolumeClaimSpec: corev1.PersistentVolumeClaimSpec{ + AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce}, + }, + }, + }, + Backups: Backups{ + PGBackRest: PGBackRestArchive{ + Repos: []crunchyv1beta1.PGBackRestRepo{ + {Name: "repo1"}, + }, + }, + }, + }, + }, + assertClusterFunc: func(t *testing.T, actual *crunchyv1beta1.PostgresCluster, _ *PerconaPGCluster) { + assert.Nil(t, actual.Spec.Proxy) + }, + }, } for testName, tt := range tests { From e28166af2a5167d1eca03b6801268fb382d3cf65 Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Tue, 17 Feb 2026 10:22:20 +0530 Subject: [PATCH 07/17] comments Signed-off-by: Mayank Shah --- percona/controller/pgcluster/status.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/percona/controller/pgcluster/status.go b/percona/controller/pgcluster/status.go index fbdbe28da0..ac6efcb418 100644 --- a/percona/controller/pgcluster/status.go +++ b/percona/controller/pgcluster/status.go @@ -30,17 +30,18 @@ func (r *PGClusterReconciler) getHost(ctx context.Context, cr *v2.PerconaPGClust return fmt.Sprintf("%s.%s.svc", svcName, ns) } - // If proxy is not configured, use the primary service + // If proxy is not configured, use the primary service as host. if cr.Spec.Proxy == nil || cr.Spec.Proxy.PGBouncer == nil { return svcFQDN(naming.ClusterPrimaryService(postgresCluster).Name, postgresCluster.Namespace), nil } - // PGBouncer is not exposed, use the service name + // Proxy is configured, but PGBouncer is not exposed, use the service name as host. svcName := naming.ClusterPGBouncer(postgresCluster).Name if cr.Spec.Proxy.PGBouncer.ServiceExpose == nil || cr.Spec.Proxy.PGBouncer.ServiceExpose.Type != string(corev1.ServiceTypeLoadBalancer) { return svcFQDN(svcName, postgresCluster.Namespace), nil } + // PGBouncer is exposed, find the IP/hostnames svc := &corev1.Service{} err := r.Client.Get(ctx, types.NamespacedName{Namespace: cr.Namespace, Name: svcName}, svc) if err != nil { @@ -54,7 +55,6 @@ func (r *PGClusterReconciler) getHost(ctx context.Context, cr *v2.PerconaPGClust host = i.Hostname } } - return host, nil } From c494bf77da134217f391601338834a70ec2a23a9 Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Tue, 17 Feb 2026 10:36:08 +0530 Subject: [PATCH 08/17] add test for getHost Signed-off-by: Mayank Shah --- percona/controller/pgcluster/gethost_test.go | 187 +++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 percona/controller/pgcluster/gethost_test.go diff --git a/percona/controller/pgcluster/gethost_test.go b/percona/controller/pgcluster/gethost_test.go new file mode 100644 index 0000000000..cf85e9c57c --- /dev/null +++ b/percona/controller/pgcluster/gethost_test.go @@ -0,0 +1,187 @@ +package pgcluster + +import ( + "context" + "testing" + + v2 "github.com/percona/percona-postgresql-operator/v2/pkg/apis/pgv2.percona.com/v2" + "github.com/percona/percona-postgresql-operator/v2/pkg/apis/postgres-operator.crunchydata.com/v1beta1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +func TestGetHost(t *testing.T) { + ctx := context.Background() + + const ( + clusterName = "test-cluster" + ns = "test-ns" + ) + + tests := []struct { + name string + proxy *v2.PGProxySpec + svc *corev1.Service + expectedHost string + expectErr bool + }{ + { + name: "proxy is nil", + proxy: nil, + expectedHost: clusterName + "-primary." + ns + ".svc", + }, + { + name: "proxy set but PGBouncer is nil", + proxy: &v2.PGProxySpec{PGBouncer: nil}, + expectedHost: clusterName + "-primary." + ns + ".svc", + }, + { + name: "PGBouncer configured, no ServiceExpose", + proxy: &v2.PGProxySpec{PGBouncer: &v2.PGBouncerSpec{}}, + expectedHost: clusterName + "-pgbouncer." + ns + ".svc", + }, + { + name: "PGBouncer configured, ServiceExpose is ClusterIP", + proxy: &v2.PGProxySpec{PGBouncer: &v2.PGBouncerSpec{ + ServiceExpose: &v2.ServiceExpose{Type: string(corev1.ServiceTypeClusterIP)}, + }}, + expectedHost: clusterName + "-pgbouncer." + ns + ".svc", + }, + { + name: "PGBouncer configured, ServiceExpose is NodePort", + proxy: &v2.PGProxySpec{PGBouncer: &v2.PGBouncerSpec{ + ServiceExpose: &v2.ServiceExpose{Type: string(corev1.ServiceTypeNodePort)}, + }}, + expectedHost: clusterName + "-pgbouncer." + ns + ".svc", + }, + { + name: "PGBouncer LoadBalancer with IP", + proxy: &v2.PGProxySpec{PGBouncer: &v2.PGBouncerSpec{ + ServiceExpose: &v2.ServiceExpose{Type: string(corev1.ServiceTypeLoadBalancer)}, + }}, + svc: &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: clusterName + "-pgbouncer", + Namespace: ns, + }, + Status: corev1.ServiceStatus{ + LoadBalancer: corev1.LoadBalancerStatus{ + Ingress: []corev1.LoadBalancerIngress{ + {IP: "10.0.0.1"}, + }, + }, + }, + }, + expectedHost: "10.0.0.1", + }, + { + name: "PGBouncer LoadBalancer with hostname", + proxy: &v2.PGProxySpec{PGBouncer: &v2.PGBouncerSpec{ + ServiceExpose: &v2.ServiceExpose{Type: string(corev1.ServiceTypeLoadBalancer)}, + }}, + svc: &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: clusterName + "-pgbouncer", + Namespace: ns, + }, + Status: corev1.ServiceStatus{ + LoadBalancer: corev1.LoadBalancerStatus{ + Ingress: []corev1.LoadBalancerIngress{ + {Hostname: "my-lb.example.com"}, + }, + }, + }, + }, + expectedHost: "my-lb.example.com", + }, + { + name: "PGBouncer LoadBalancer with both IP and hostname prefers hostname", + proxy: &v2.PGProxySpec{PGBouncer: &v2.PGBouncerSpec{ + ServiceExpose: &v2.ServiceExpose{Type: string(corev1.ServiceTypeLoadBalancer)}, + }}, + svc: &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: clusterName + "-pgbouncer", + Namespace: ns, + }, + Status: corev1.ServiceStatus{ + LoadBalancer: corev1.LoadBalancerStatus{ + Ingress: []corev1.LoadBalancerIngress{ + {IP: "10.0.0.1", Hostname: "my-lb.example.com"}, + }, + }, + }, + }, + expectedHost: "my-lb.example.com", + }, + { + name: "PGBouncer LoadBalancer with no ingress returns empty host", + proxy: &v2.PGProxySpec{PGBouncer: &v2.PGBouncerSpec{ + ServiceExpose: &v2.ServiceExpose{Type: string(corev1.ServiceTypeLoadBalancer)}, + }}, + svc: &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: clusterName + "-pgbouncer", + Namespace: ns, + }, + }, + expectedHost: "", + }, + { + name: "PGBouncer LoadBalancer service not found", + proxy: &v2.PGProxySpec{PGBouncer: &v2.PGBouncerSpec{ + ServiceExpose: &v2.ServiceExpose{Type: string(corev1.ServiceTypeLoadBalancer)}, + }}, + expectErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cr := &v2.PerconaPGCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: clusterName, + Namespace: ns, + }, + Spec: v2.PerconaPGClusterSpec{ + Proxy: tt.proxy, + }, + } + + var objs []client.Object + if tt.svc != nil { + objs = append(objs, tt.svc) + } + + s := scheme.Scheme + err := v1beta1.AddToScheme(s) + require.NoError(t, err) + err = v2.AddToScheme(s) + require.NoError(t, err) + + cl := fake.NewClientBuilder(). + WithScheme(s). + WithObjects(objs...). + WithStatusSubresource(objs...). + Build() + + r := &PGClusterReconciler{ + Client: cl, + } + + host, err := r.getHost(ctx, cr) + if tt.expectErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + assert.Equal(t, tt.expectedHost, host) + }) + } +} From a0474fe2c190f5f3afe4da769a91b9286afe5634 Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Tue, 17 Feb 2026 12:49:30 +0530 Subject: [PATCH 09/17] imports Signed-off-by: Mayank Shah --- percona/controller/pgcluster/gethost_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/percona/controller/pgcluster/gethost_test.go b/percona/controller/pgcluster/gethost_test.go index cf85e9c57c..4f6eea5406 100644 --- a/percona/controller/pgcluster/gethost_test.go +++ b/percona/controller/pgcluster/gethost_test.go @@ -4,8 +4,6 @@ import ( "context" "testing" - v2 "github.com/percona/percona-postgresql-operator/v2/pkg/apis/pgv2.percona.com/v2" - "github.com/percona/percona-postgresql-operator/v2/pkg/apis/postgres-operator.crunchydata.com/v1beta1" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" @@ -13,6 +11,9 @@ import ( "k8s.io/client-go/kubernetes/scheme" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" + + v2 "github.com/percona/percona-postgresql-operator/v2/pkg/apis/pgv2.percona.com/v2" + "github.com/percona/percona-postgresql-operator/v2/pkg/apis/postgres-operator.crunchydata.com/v1beta1" ) func TestGetHost(t *testing.T) { From d4a602c290d15b8ee1055952b9debc89540b8494 Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Tue, 17 Feb 2026 14:18:44 +0530 Subject: [PATCH 10/17] linting Signed-off-by: Mayank Shah --- .../v2/perconapgcluster_types_test.go | 49 ++++++++++--------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go b/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go index 8307c566ab..ef1f760840 100644 --- a/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go +++ b/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go @@ -70,7 +70,7 @@ func TestPerconaPGCluster_Proxy(t *testing.T) { assert.NotNil(t, cr.Spec.Proxy.PGBouncer) assert.NotNil(t, cr.Spec.Proxy.PGBouncer.Metadata) assert.NotNil(t, cr.Spec.Proxy.PGBouncer.Metadata.Labels) - assert.Equal(t, cr.Spec.Proxy.PGBouncer.Metadata.Labels[LabelOperatorVersion], cr.Spec.CRVersion) + assert.Equal(t, cr.Spec.CRVersion, cr.Spec.Proxy.PGBouncer.Metadata.Labels[LabelOperatorVersion]) }) } @@ -127,7 +127,7 @@ func TestPerconaPGCluster_PostgresImage(t *testing.T) { }() } - assert.Equal(t, cluster.PostgresImage(), tt.expectedImage) + assert.Equal(t, tt.expectedImage, cluster.PostgresImage()) }) } } @@ -186,23 +186,24 @@ func TestPerconaPGCluster_ToCrunchy(t *testing.T) { }, }, assertClusterFunc: func(t *testing.T, actual *crunchyv1beta1.PostgresCluster, expected *PerconaPGCluster) { - assert.Equal(t, actual.Name, expected.Name) - assert.Equal(t, actual.Namespace, expected.Namespace) - assert.Equal(t, actual.Finalizers, []string{naming.Finalizer}) - assert.Equal(t, actual.Spec.PostgresVersion, expected.Spec.PostgresVersion) - assert.Equal(t, actual.Labels[LabelOperatorVersion], expected.Spec.CRVersion) - assert.Equal(t, len(actual.Spec.InstanceSets), 1) - assert.Equal(t, len(actual.Spec.InstanceSets), len(expected.Spec.InstanceSets)) - assert.Equal(t, actual.Spec.InstanceSets[0].Name, expected.Spec.InstanceSets[0].Name) - - assert.Equal(t, actual.Spec.Service.Type, expected.Spec.Expose.Type) + assert.Equal(t, expected.Name, actual.Name) + assert.Equal(t, expected.Namespace, actual.Namespace) + assert.Equal(t, []string{naming.Finalizer}, actual.Finalizers) + assert.Equal(t, expected.Spec.PostgresVersion, actual.Spec.PostgresVersion) + assert.Equal(t, expected.Spec.CRVersion, actual.Labels[LabelOperatorVersion]) + assert.Len(t, actual.Spec.InstanceSets, 1) + assert.Len(t, len(expected.Spec.InstanceSets), len(actual.Spec.InstanceSets)) + assert.Equal(t, expected.Spec.InstanceSets[0].Name, actual.Spec.InstanceSets[0].Name) + + assert.Equal(t, expected.Spec.Expose.Type, actual.Spec.Service.Type) assert.NotNil(t, actual.Spec.Service.LoadBalancerClass) - assert.Equal(t, actual.Spec.Service.LoadBalancerClass, expected.Spec.Expose.LoadBalancerClass) + assert.Equal(t, expected.Spec.Expose.LoadBalancerClass, actual.Spec.Service.LoadBalancerClass) - assert.Equal(t, actual.Spec.ReplicaService.Type, expected.Spec.ExposeReplicas.Type) + assert.Equal(t, expected.Spec.ExposeReplicas.Type, actual.Spec.ReplicaService.Type) assert.NotNil(t, actual.Spec.ReplicaService.LoadBalancerClass) - assert.Equal(t, actual.Spec.ReplicaService.LoadBalancerClass, expected.Spec.ExposeReplicas.LoadBalancerClass) - assert.Equal(t, *actual.Spec.Backups.TrackLatestRestorableTime, true) + assert.Equal(t, expected.Spec.ExposeReplicas.LoadBalancerClass, actual.Spec.ReplicaService.LoadBalancerClass) + assert.NotNil(t, actual.Spec.Backups.TrackLatestRestorableTime) + assert.True(t, *actual.Spec.Backups.TrackLatestRestorableTime) }, }, "updates existing PostgresCluster": { @@ -247,11 +248,11 @@ func TestPerconaPGCluster_ToCrunchy(t *testing.T) { }, }, assertClusterFunc: func(t *testing.T, actual *crunchyv1beta1.PostgresCluster, expected *PerconaPGCluster) { - assert.Equal(t, actual.Spec.PostgresVersion, expected.Spec.PostgresVersion) - assert.Equal(t, actual.Spec.Port, expected.Spec.Port) - assert.Equal(t, actual.Spec.TLSOnly, expected.Spec.TLSOnly) - assert.Equal(t, actual.Labels["test-label"], "test-value") - assert.Equal(t, actual.Labels[LabelOperatorVersion], expected.Spec.CRVersion) + assert.Equal(t, expected.Spec.PostgresVersion, actual.Spec.PostgresVersion) + assert.Equal(t, expected.Spec.Port, actual.Spec.Port) + assert.Equal(t, expected.Spec.TLSOnly, actual.Spec.TLSOnly) + assert.Equal(t, "test-value", actual.Labels["test-label"]) + assert.Equal(t, expected.Spec.CRVersion, actual.Labels[LabelOperatorVersion]) }, }, "handles PMM enabled scenario": { @@ -293,7 +294,7 @@ func TestPerconaPGCluster_ToCrunchy(t *testing.T) { break } } - assert.Equal(t, hasMonitoringUser, true) + assert.Equal(t, true, hasMonitoringUser) }, }, "handles AutoCreateUserSchema annotation": { @@ -325,7 +326,7 @@ func TestPerconaPGCluster_ToCrunchy(t *testing.T) { }, }, assertClusterFunc: func(t *testing.T, actual *crunchyv1beta1.PostgresCluster, _ *PerconaPGCluster) { - assert.Equal(t, actual.Annotations[naming.AutoCreateUserSchemaAnnotation], "true") + assert.Equal(t, "true", actual.Annotations[naming.AutoCreateUserSchemaAnnotation]) }, }, "filters out reserved monitoring user": { @@ -361,7 +362,7 @@ func TestPerconaPGCluster_ToCrunchy(t *testing.T) { }, }, assertClusterFunc: func(t *testing.T, result *crunchyv1beta1.PostgresCluster, original *PerconaPGCluster) { - assert.Equal(t, len(result.Spec.Users), 2) + assert.Equal(t, 2, len(result.Spec.Users)) userNames := make([]string, len(result.Spec.Users)) for i, user := range result.Spec.Users { userNames[i] = string(user.Name) From fe4c5fc09ef16cfc77916c28116ac515bf359e46 Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Tue, 17 Feb 2026 14:23:37 +0530 Subject: [PATCH 11/17] fix tests Signed-off-by: Mayank Shah --- pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go b/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go index ef1f760840..fb999e71f2 100644 --- a/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go +++ b/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go @@ -192,7 +192,7 @@ func TestPerconaPGCluster_ToCrunchy(t *testing.T) { assert.Equal(t, expected.Spec.PostgresVersion, actual.Spec.PostgresVersion) assert.Equal(t, expected.Spec.CRVersion, actual.Labels[LabelOperatorVersion]) assert.Len(t, actual.Spec.InstanceSets, 1) - assert.Len(t, len(expected.Spec.InstanceSets), len(actual.Spec.InstanceSets)) + assert.Len(t, expected.Spec.InstanceSets, len(actual.Spec.InstanceSets)) assert.Equal(t, expected.Spec.InstanceSets[0].Name, actual.Spec.InstanceSets[0].Name) assert.Equal(t, expected.Spec.Expose.Type, actual.Spec.Service.Type) From a0a87acc6641aa087bc878073df5c88efe3e2d97 Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Tue, 17 Feb 2026 14:40:51 +0530 Subject: [PATCH 12/17] backward compatibility Signed-off-by: Mayank Shah --- .../v2/perconapgcluster_types.go | 10 +++++++--- .../v2/perconapgcluster_types_test.go | 17 +++++++++++++++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types.go b/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types.go index a20db0ea32..e58c58f81a 100644 --- a/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types.go +++ b/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types.go @@ -212,13 +212,17 @@ func (cr *PerconaPGCluster) Default() { cr.Spec.InstanceSets[i].Metadata.Labels[LabelOperatorVersion] = cr.Spec.CRVersion } - if cr.Spec.Proxy != nil { + if cr.CompareVersion("2.9.0") < 0 || (cr.Spec.Proxy != nil && cr.Spec.Proxy.PGBouncer != nil) { + if cr.Spec.Proxy == nil { + cr.Spec.Proxy = &PGProxySpec{} + } + if cr.Spec.Proxy.PGBouncer == nil { - cr.Spec.Proxy.PGBouncer = new(PGBouncerSpec) + cr.Spec.Proxy.PGBouncer = &PGBouncerSpec{} } if cr.Spec.Proxy.PGBouncer.Metadata == nil { - cr.Spec.Proxy.PGBouncer.Metadata = new(crunchyv1beta1.Metadata) + cr.Spec.Proxy.PGBouncer.Metadata = &crunchyv1beta1.Metadata{} } if cr.Spec.Proxy.PGBouncer.Metadata.Labels == nil { cr.Spec.Proxy.PGBouncer.Metadata.Labels = make(map[string]string) diff --git a/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go b/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go index fb999e71f2..1d836cde35 100644 --- a/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go +++ b/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go @@ -56,14 +56,27 @@ func TestPerconaPGCluster_BackupsEnabled(t *testing.T) { } func TestPerconaPGCluster_Proxy(t *testing.T) { - t.Run("Proxy is nil", func(t *testing.T) { + t.Run("Proxy is nil, CRVersion < 2.9.0", func(t *testing.T) { cr := new(PerconaPGCluster) + cr.Spec.CRVersion = "2.8.0" + cr.Default() + assert.NotNil(t, cr.Spec.Proxy) + assert.NotNil(t, cr.Spec.Proxy.PGBouncer) + assert.NotNil(t, cr.Spec.Proxy.PGBouncer.Metadata) + assert.NotNil(t, cr.Spec.Proxy.PGBouncer.Metadata.Labels) + assert.Equal(t, cr.Spec.CRVersion, cr.Spec.Proxy.PGBouncer.Metadata.Labels[LabelOperatorVersion]) + }) + t.Run("Proxy is nil, CRVersion >= 2.9.0", func(t *testing.T) { + cr := new(PerconaPGCluster) + cr.Spec.CRVersion = "2.9.0" cr.Default() assert.Nil(t, cr.Spec.Proxy) }) t.Run("Proxy is not nil", func(t *testing.T) { cr := new(PerconaPGCluster) - cr.Spec.Proxy = new(PGProxySpec) + cr.Spec.Proxy = &PGProxySpec{ + PGBouncer: &PGBouncerSpec{}, + } cr.Spec.CRVersion = "2.9.0" cr.Default() assert.NotNil(t, cr.Spec.Proxy) From 67ecb0f62681bb5e4afab444a126d971f500653c Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Tue, 17 Feb 2026 14:46:40 +0530 Subject: [PATCH 13/17] improvements Signed-off-by: Mayank Shah --- pkg/apis/pgv2.percona.com/v2/perconapgcluster_types.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types.go b/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types.go index e58c58f81a..1efa51fc2d 100644 --- a/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types.go +++ b/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types.go @@ -212,7 +212,7 @@ func (cr *PerconaPGCluster) Default() { cr.Spec.InstanceSets[i].Metadata.Labels[LabelOperatorVersion] = cr.Spec.CRVersion } - if cr.CompareVersion("2.9.0") < 0 || (cr.Spec.Proxy != nil && cr.Spec.Proxy.PGBouncer != nil) { + if cr.CompareVersion("2.9.0") < 0 || cr.Spec.Proxy.IsSet() { if cr.Spec.Proxy == nil { cr.Spec.Proxy = &PGProxySpec{} } @@ -1042,6 +1042,10 @@ type PGProxySpec struct { PGBouncer *PGBouncerSpec `json:"pgBouncer"` } +func (p *PGProxySpec) IsSet() bool { + return p != nil && p.PGBouncer != nil +} + func (p *PGProxySpec) ToCrunchy(version string) *crunchyv1beta1.PostgresProxySpec { if p == nil { return nil From e1cb385c46a70cec3bb65e7926d700b7eccc3701 Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Tue, 17 Feb 2026 14:46:44 +0530 Subject: [PATCH 14/17] bug fixes Signed-off-by: Mayank Shah --- percona/controller/pgcluster/controller.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/percona/controller/pgcluster/controller.go b/percona/controller/pgcluster/controller.go index d3d796a4fa..4598d95981 100644 --- a/percona/controller/pgcluster/controller.go +++ b/percona/controller/pgcluster/controller.go @@ -599,9 +599,7 @@ func (r *PGClusterReconciler) reconcileEnvFromSecrets(ctx context.Context, cr *v m[&set.EnvFrom] = set.Metadata } - if cr.Spec.Proxy != nil && - cr.Spec.Proxy.PGBouncer != nil && - len(cr.Spec.Proxy.PGBouncer.EnvFrom) > 0 { + if cr.Spec.Proxy.IsSet() && len(cr.Spec.Proxy.PGBouncer.EnvFrom) > 0 { if cr.Spec.Proxy.PGBouncer.Metadata == nil { cr.Spec.Proxy.PGBouncer.Metadata = new(v1beta1.Metadata) } @@ -610,7 +608,7 @@ func (r *PGClusterReconciler) reconcileEnvFromSecrets(ctx context.Context, cr *v if len(cr.Spec.Backups.PGBackRest.EnvFrom) > 0 { if cr.Spec.Backups.PGBackRest.Metadata == nil { - cr.Spec.Proxy.PGBouncer.Metadata = new(v1beta1.Metadata) + cr.Spec.Backups.PGBackRest.Metadata = new(v1beta1.Metadata) } m[&cr.Spec.Backups.PGBackRest.EnvFrom] = cr.Spec.Backups.PGBackRest.Metadata } From 82b4dd10d379b45b3125b05cd1c735ed3196c1d8 Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Tue, 17 Feb 2026 14:50:18 +0530 Subject: [PATCH 15/17] linting Signed-off-by: Mayank Shah --- pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go b/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go index 1d836cde35..9390b9ffa8 100644 --- a/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go +++ b/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go @@ -307,7 +307,7 @@ func TestPerconaPGCluster_ToCrunchy(t *testing.T) { break } } - assert.Equal(t, true, hasMonitoringUser) + assert.True(t, hasMonitoringUser) }, }, "handles AutoCreateUserSchema annotation": { @@ -375,7 +375,7 @@ func TestPerconaPGCluster_ToCrunchy(t *testing.T) { }, }, assertClusterFunc: func(t *testing.T, result *crunchyv1beta1.PostgresCluster, original *PerconaPGCluster) { - assert.Equal(t, 2, len(result.Spec.Users)) + assert.Len(t, result.Spec.Users, 2) userNames := make([]string, len(result.Spec.Users)) for i, user := range result.Spec.Users { userNames[i] = string(user.Name) @@ -426,7 +426,7 @@ func TestPerconaPGCluster_ToCrunchy(t *testing.T) { crunchyCluster, err := tt.expectedPerconaPGCluster.ToCrunchy(ctx, tt.inputPostgresCluster, scheme) if tt.expectedError { - assert.NotNil(t, err) + require.Error(t, err) return } From 384bda1ab0655e8bc1abd3a29cadcf7979025ae5 Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Tue, 17 Feb 2026 14:58:26 +0530 Subject: [PATCH 16/17] linting Signed-off-by: Mayank Shah --- pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go b/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go index 9390b9ffa8..537d3ca34e 100644 --- a/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go +++ b/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go @@ -430,7 +430,7 @@ func TestPerconaPGCluster_ToCrunchy(t *testing.T) { return } - assert.Nil(t, err) + require.NoError(t, err) assert.NotNil(t, crunchyCluster) if tt.assertClusterFunc != nil { From 4e20c0e5dcd10cfe3e6c27a777caefd3beb8891a Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Tue, 17 Feb 2026 14:59:33 +0530 Subject: [PATCH 17/17] test fix Signed-off-by: Mayank Shah --- pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go b/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go index 537d3ca34e..7e663f3ad2 100644 --- a/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go +++ b/pkg/apis/pgv2.percona.com/v2/perconapgcluster_types_test.go @@ -392,7 +392,7 @@ func TestPerconaPGCluster_ToCrunchy(t *testing.T) { Namespace: "test-namespace", }, Spec: PerconaPGClusterSpec{ - CRVersion: "2.5.0", + CRVersion: "2.9.0", PostgresVersion: 15, Proxy: nil, InstanceSets: PGInstanceSets{