Repository: cloudnative-pg/cloudnative-pg

Issue:
[Feature]: Configure `tcp_user_timeout` setting for replicas
### Is there an existing issue already for this feature request/idea?

- [x] I have searched for an existing issue, and could not find anything. I believe this is a new feature request to be evaluated.

### What problem is this feature going to solve? Why should it be added?

This issue is related to #7035 and aims to solve the same problems.

### Describe the solution you'd like

Instead of configuring the connection info for the replicas on a case-by-case scenario, we just rely on an operator-level configuration option to set the `TCP_USER_TIMEOUT` for all replicas managed by the operator.

### Describe alternatives you've considered

See #7035 initial implementation.

### Additional context

_No response_

### Backport?

Yes

### Are you willing to actively contribute to this feature?

Yes

### Code of Conduct

- [x] I agree to follow this project's Code of Conduct

Discussion:

Solution patch:
diff --git a/.wordlist-en-custom.txt b/.wordlist-en-custom.txt
index a59c43ae..35211d21 100644
--- a/.wordlist-en-custom.txt
+++ b/.wordlist-en-custom.txt
@@ -903,6 +903,7 @@ json
 jsonpath
 kb
 kbytes
+keepalive
 kms
 kube
 kubebuilder
diff --git a/Makefile b/Makefile
index d6ccdb2d..8410a26c 100644
--- a/Makefile
+++ b/Makefile
@@ -215,7 +215,8 @@ generate-manifest: manifests kustomize ## Generate manifest used for deployment.
 		$(KUSTOMIZE) edit set image controller="${CONTROLLER_IMG}" ;\
 		$(KUSTOMIZE) edit add patch --path env_override.yaml ;\
 		$(KUSTOMIZE) edit add configmap controller-manager-env \
-			--from-literal="POSTGRES_IMAGE_NAME=${POSTGRES_IMAGE_NAME}" ;\
+			--from-literal="POSTGRES_IMAGE_NAME=${POSTGRES_IMAGE_NAME}" \
+			--from-literal="STANDBY_TCP_USER_TIMEOUT=5000" ;\
 	} ;\
 	mkdir -p ${DIST_PATH} ;\
 	$(KUSTOMIZE) build $$CONFIG_TMP_DIR/default > ${OPERATOR_MANIFEST_PATH} ;\
diff --git a/docs/src/operator_conf.md b/docs/src/operator_conf.md
index 2e6dadb7..70c47c84 100644
--- a/docs/src/operator_conf.md
+++ b/docs/src/operator_conf.md
@@ -48,6 +48,7 @@ Name | Description
 `MONITORING_QUERIES_CONFIGMAP` | The name of a ConfigMap in the operator's namespace with a set of default queries (to be specified under the key `queries`) to be applied to all created Clusters
 `MONITORING_QUERIES_SECRET` | The name of a Secret in the operator's namespace with a set of default queries (to be specified under the key `queries`) to be applied to all created Clusters
 `PULL_SECRET_NAME` | Name of an additional pull secret to be defined in the operator's namespace and to be used to download images
+`STANDBY_TCP_USER_TIMEOUT` | Defines the [`TCP_USER_TIMEOUT` socket option](https://www.postgresql.org/docs/current/runtime-config-connection.html#GUC-TCP-USER-TIMEOUT) for replication connections from standby instances to the primary. Default is 0 (system's default).
 
 Values in `INHERITED_ANNOTATIONS` and `INHERITED_LABELS` support path-like wildcards. For example, the value `example.com/*` will match
 both the value `example.com/one` and `example.com/two`.
diff --git a/docs/src/postgresql_conf.md b/docs/src/postgresql_conf.md
index 4bc740ef..a62dfd13 100644
--- a/docs/src/postgresql_conf.md
+++ b/docs/src/postgresql_conf.md
@@ -127,17 +127,28 @@ Since the fixed parameters are added at the end, they can't be overridden by the
 user via the YAML configuration. Those parameters are required for correct WAL
 archiving and replication.
 
-### Replication settings
+### Replication Settings
 
-The `primary_conninfo`, `restore_command`,  and `recovery_target_timeline`
-parameters are managed automatically by the operator according to the state of
-the instance in the cluster.
+The `primary_conninfo`, `restore_command`, and `recovery_target_timeline`
+parameters are automatically managed by the operator based on the instance's
+role within the cluster. These parameters are effectively applied only when the
+instance is operating as a replica.
 
 ```text
-primary_conninfo = 'host=cluster-example-rw user=postgres dbname=postgres'
+primary_conninfo = 'host=<PRIMARY> user=postgres dbname=postgres'
 recovery_target_timeline = 'latest'
 ```
 
+The [`STANDBY_TCP_USER_TIMEOUT` operator configuration setting](operator_conf.md#available-options),
+if specified, sets the `tcp_user_timeout` parameter on all standby instances
+managed by the operator.
+
+The `tcp_user_timeout` parameter determines how long transmitted data can
+remain unacknowledged before the TCP connection is forcibly closed. Adjusting
+this value allows you to fine-tune the responsiveness of standby instances to
+network disruptions. For more details, refer to the
+[PostgreSQL documentation](https://www.postgresql.org/docs/current/runtime-config-connection.html#GUC-TCP-USER-TIMEOUT).
+
 ### Log control settings
 
 The operator requires PostgreSQL to output its log in CSV format, and the
@@ -648,4 +659,3 @@ Users are not allowed to set the following configuration parameters in the
 - `unix_socket_directories`
 - `unix_socket_group`
 - `unix_socket_permissions`
-
diff --git a/docs/src/release_notes/v1.26.md b/docs/src/release_notes/v1.26.md
index 926cb21c..ca52d5e2 100644
--- a/docs/src/release_notes/v1.26.md
+++ b/docs/src/release_notes/v1.26.md
@@ -30,6 +30,10 @@ on the release branch in GitHub.
 
 ### Enhancements:
 
+- Introduced the `STANDBY_TCP_USER_TIMEOUT` operator configuration setting,
+  which, if specified, sets the `tcp_user_timeout` parameter on all standby
+  instances managed by the operator.
+
 - feat: support customizable pod patches via annotations (#6323)
 
 - `cnpg` plugin updates:
diff --git a/docs/src/ssl_connections.md b/docs/src/ssl_connections.md
index 1fde48ee..c7324443 100644
--- a/docs/src/ssl_connections.md
+++ b/docs/src/ssl_connections.md
@@ -90,7 +90,7 @@ spec:
         app: webtest
     spec:
       containers:
-        - image: ghcr.io/cloudnative-pg/webtest:1.6.0
+        - image: ghcr.io/cloudnative-pg/webtest:1.7.0
           name: cert-test
           volumeMounts:
             - name: secret-volume-root-ca
@@ -188,4 +188,3 @@ This assumes that the PostgreSQL operand images include an OpenSSL library that
 supports the `TLSv1.3` version. If not, or if your client applications need a
 lower version number, you need to manually configure it in the PostgreSQL
 configuration as any other Postgres GUC.
-
diff --git a/docs/src/troubleshooting.md b/docs/src/troubleshooting.md
index 664bdc02..67cfe25c 100644
--- a/docs/src/troubleshooting.md
+++ b/docs/src/troubleshooting.md
@@ -812,3 +812,9 @@ establish the connection for approximately 127 seconds before giving up. This
 prolonged retry period can significantly delay the reconnection process.
 For more details, consult the
 [tcp_syn_retries documentation](https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt).
+
+You can work around this issue by setting `STANDBY_TCP_USER_TIMEOUT` in the
+[operator configuration](operator_conf.md#available-options). This will cause
+the standby instances to close the TCP connection if the initial `SYN` packet
+is not acknowledged within the specified timeout, allowing them to retry the
+connection more quickly.
diff --git a/hack/e2e/run-e2e-ocp.sh b/hack/e2e/run-e2e-ocp.sh
index d6a8a0d8..bddb9404 100755
--- a/hack/e2e/run-e2e-ocp.sh
+++ b/hack/e2e/run-e2e-ocp.sh
@@ -50,6 +50,9 @@ oc apply -f cloudnative-pg-catalog.yaml
 # create the secret for the index to be pulled in the marketplace
 oc create secret docker-registry -n openshift-marketplace --docker-server="${REGISTRY}" --docker-username="${REGISTRY_USER}" --docker-password="${REGISTRY_PASSWORD}" cnpg-pull-secret || true
 
+# Create the default configmap to set global keepalives on all the tests
+oc create configmap -n openshift-operators --from-literal=STANDBY_TCP_USER_TIMEOUT=5000 cnpg-controller-manager-config
+
 # Install the operator
 oc apply -f - <<EOF
 apiVersion: operators.coreos.com/v1alpha1
diff --git a/hack/setup-cluster.sh b/hack/setup-cluster.sh
index 3d739065..709e79dd 100755
--- a/hack/setup-cluster.sh
+++ b/hack/setup-cluster.sh
@@ -116,7 +116,6 @@ kind: Cluster
 apiVersion: kind.x-k8s.io/v1alpha4
 networking:
   apiServerAddress: "0.0.0.0"
-  kubeProxyMode: "ipvs"
 
 # add to the apiServer certSANs the name of the docker (dind) service in order to be able to reach the cluster through it
 kubeadmConfigPatchesJSON6902:
diff --git a/internal/configuration/configuration.go b/internal/configuration/configuration.go
index bd0be935..6e2bc703 100644
--- a/internal/configuration/configuration.go
+++ b/internal/configuration/configuration.go
@@ -125,6 +125,14 @@ type Data struct {
 	// IncludePlugins is a comma-separated list of plugins to always be
 	// included in the Cluster reconciliation
 	IncludePlugins string `json:"includePlugins" env:"INCLUDE_PLUGINS"`
+
+	// StandbyTCPUserTimeout configuration parameter allows you to
+	// specify a custom TCP user timeout for the standby PostgreSQL
+	// server's connection to the primary server. This timeout is
+	// added as a tcp_user_timeout option to the primary_conninfo
+	// string, which is used by the standby server to connect to the
+	// primary server in CloudNativePG.
+	StandbyTCPUserTimeout int `json:"standbyTcpUserTimeout" env:"STANDBY_TCP_USER_TIMEOUT"`
 }
 
 // Current is the configuration used by the operator
@@ -140,6 +148,7 @@ func newDefaultConfig() *Data {
 		CreateAnyService:       false,
 		CertificateDuration:    CertificateDuration,
 		ExpiringCheckThreshold: ExpiringCheckThreshold,
+		StandbyTCPUserTimeout:  0,
 	}
 }
 
diff --git a/internal/webhook/v1/cluster_webhook.go b/internal/webhook/v1/cluster_webhook.go
index 06945b4e..c8640d76 100644
--- a/internal/webhook/v1/cluster_webhook.go
+++ b/internal/webhook/v1/cluster_webhook.go
@@ -299,6 +299,9 @@ func isReservedEnvironmentVariable(name string) bool {
 	name = strings.ToUpper(name)
 
 	switch {
+	case strings.HasPrefix(name, "CNPG_"):
+		return true
+
 	case strings.HasPrefix(name, "PG"):
 		return true
 
diff --git a/pkg/management/postgres/instance.go b/pkg/management/postgres/instance.go
index 5bd3ae3c..ced61cff 100644
--- a/pkg/management/postgres/instance.go
+++ b/pkg/management/postgres/instance.go
@@ -30,6 +30,7 @@ import (
 	"path/filepath"
 	"runtime"
 	"strconv"
+	"strings"
 	"time"
 
 	"github.com/blang/semver"
@@ -1284,7 +1285,15 @@ func (instance *Instance) DropConnections() error {
 
 // GetPrimaryConnInfo returns the DSN to reach the primary
 func (instance *Instance) GetPrimaryConnInfo() string {
-	return buildPrimaryConnInfo(instance.GetClusterName()+"-rw", instance.GetPodName())
+	result := buildPrimaryConnInfo(instance.GetClusterName()+"-rw", instance.GetPodName())
+
+	standbyTCPUserTimeout := os.Getenv("CNPG_STANDBY_TCP_USER_TIMEOUT")
+	if len(standbyTCPUserTimeout) > 0 {
+		result = fmt.Sprintf("%s tcp_user_timeout='%s'", result,
+			strings.ReplaceAll(strings.ReplaceAll(standbyTCPUserTimeout, `\`, `\\`), `'`, `\'`))
+	}
+
+	return result
 }
 
 // HandleInstanceCommandRequests execute a command requested by the reconciliation
diff --git a/pkg/specs/pods.go b/pkg/specs/pods.go
index d059818f..ada3575d 100644
--- a/pkg/specs/pods.go
+++ b/pkg/specs/pods.go
@@ -154,6 +154,16 @@ func CreatePodEnvConfig(cluster apiv1.Cluster, podName string) EnvConfig {
 	}
 	config.EnvVars = append(config.EnvVars, cluster.Spec.Env...)
 
+	if configuration.Current.StandbyTCPUserTimeout != 0 {
+		config.EnvVars = append(
+			config.EnvVars,
+			corev1.EnvVar{
+				Name:  "CNPG_STANDBY_TCP_USER_TIMEOUT",
+				Value: strconv.Itoa(configuration.Current.StandbyTCPUserTimeout),
+			},
+		)
+	}
+
 	hashValue, _ := hash.ComputeHash(config)
 	config.Hash = hashValue
 	return config



Test patch:
diff --git a/tests/e2e/asserts_test.go b/tests/e2e/asserts_test.go
index 941dc034..45847935 100644
--- a/tests/e2e/asserts_test.go
+++ b/tests/e2e/asserts_test.go
@@ -134,11 +134,11 @@ func AssertSwitchoverWithHistory(
 	})
 
 	By("waiting that the TargetPrimary become also CurrentPrimary", func() {
-		Eventually(func() (string, error) {
+		Eventually(func(g Gomega) {
 			cluster, err := clusterutils.Get(env.Ctx, env.Client, namespace, clusterName)
-			Expect(err).ToNot(HaveOccurred())
-			return cluster.Status.CurrentPrimary, err
-		}, testTimeouts[timeouts.NewPrimaryAfterSwitchover]).Should(BeEquivalentTo(targetPrimary))
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(cluster.Status.CurrentPrimary).To(BeEquivalentTo(targetPrimary))
+		}, testTimeouts[timeouts.NewPrimaryAfterSwitchover]).Should(Succeed())
 	})
 
 	By("waiting that the old primary become ready", func() {
@@ -331,7 +331,6 @@ func AssertClusterDefault(
 		Eventually(func(g Gomega) {
 			var err error
 			cluster, err = clusterutils.Get(env.Ctx, env.Client, namespace, clusterName)
-			Expect(err).ToNot(HaveOccurred())
 			g.Expect(err).ToNot(HaveOccurred())
 		}).Should(Succeed())
 
@@ -767,12 +766,15 @@ func AssertNewPrimary(namespace string, clusterName string, oldPrimary string) {
 		timeout := 120
 		// Wait for the operator to set a new TargetPrimary
 		var cluster *apiv1.Cluster
-		Eventually(func() (string, error) {
+		Eventually(func(g Gomega) {
 			var err error
 			cluster, err = clusterutils.Get(env.Ctx, env.Client, namespace, clusterName)
-			Expect(err).ToNot(HaveOccurred())
-			return cluster.Status.TargetPrimary, err
-		}, timeout).ShouldNot(Or(BeEquivalentTo(oldPrimary), BeEquivalentTo(apiv1.PendingFailoverMarker)))
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(cluster.Status.TargetPrimary).ToNot(Or(
+				BeEquivalentTo(oldPrimary),
+				BeEquivalentTo(apiv1.PendingFailoverMarker),
+			))
+		}, timeout).Should(Succeed())
 		newPrimary := cluster.Status.TargetPrimary
 
 		// Expect the chosen pod to eventually become a primary
@@ -1292,6 +1294,9 @@ func AssertFastFailOver(
 			" -f " + webTestFile)
 		Expect(err).ToNot(HaveOccurred())
 
+		webtestDeploy := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "webtest", Namespace: namespace}}
+		Expect(deployments.WaitForReady(env.Ctx, env.Client, webtestDeploy, 60)).To(Succeed())
+
 		_, _, err = run.Run("kubectl create -n " + namespace +
 			" -f " + webTestJob)
 		Expect(err).ToNot(HaveOccurred())
@@ -2040,15 +2045,15 @@ func switchWalAndGetLatestArchive(namespace, podName string) string {
 
 func createAndAssertPgBouncerPoolerIsSetUp(namespace, poolerYamlFilePath string, expectedInstanceCount int) {
 	CreateResourceFromFile(namespace, poolerYamlFilePath)
-	Eventually(func() (int32, error) {
+	Eventually(func(g Gomega) {
 		poolerName, err := yaml.GetResourceNameFromYAML(env.Scheme, poolerYamlFilePath)
-		Expect(err).ToNot(HaveOccurred())
+		g.Expect(err).ToNot(HaveOccurred())
 		// Wait for the deployment to be ready
 		deployment := &appsv1.Deployment{}
 		err = env.Client.Get(env.Ctx, types.NamespacedName{Namespace: namespace, Name: poolerName}, deployment)
-
-		return deployment.Status.ReadyReplicas, err
-	}, 300).Should(BeEquivalentTo(expectedInstanceCount))
+		g.Expect(err).ToNot(HaveOccurred())
+		g.Expect(deployment.Status.ReadyReplicas).To(BeEquivalentTo(expectedInstanceCount))
+	}, 300).Should(Succeed())
 
 	// check pooler pod is up and running
 	assertPGBouncerPodsAreReady(namespace, poolerYamlFilePath, expectedInstanceCount)
@@ -2059,41 +2064,31 @@ func assertPgBouncerPoolerDeploymentStrategy(
 	expectedMaxSurge, expectedMaxUnavailable string,
 ) {
 	By("verify pooler deployment has expected rolling update configuration", func() {
-		Eventually(func() bool {
+		Eventually(func(g Gomega) {
 			poolerName, err := yaml.GetResourceNameFromYAML(env.Scheme, poolerYamlFilePath)
-			Expect(err).ToNot(HaveOccurred())
+			g.Expect(err).ToNot(HaveOccurred())
 			// Wait for the deployment to be ready
 			deployment := &appsv1.Deployment{}
 			err = env.Client.Get(env.Ctx, types.NamespacedName{Namespace: namespace, Name: poolerName}, deployment)
-			if err != nil {
-				return false
-			}
-			if expectedMaxSurge == deployment.Spec.Strategy.RollingUpdate.MaxSurge.String() &&
-				expectedMaxUnavailable == deployment.Spec.Strategy.RollingUpdate.MaxUnavailable.String() {
-				return true
-			}
-			return false
-		}, 300).Should(BeTrue())
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(deployment.Spec.Strategy.RollingUpdate.MaxSurge.String()).To(BeEquivalentTo(expectedMaxSurge))
+			g.Expect(deployment.Spec.Strategy.RollingUpdate.MaxUnavailable.String()).To(BeEquivalentTo(expectedMaxUnavailable))
+		}, 300).Should(Succeed())
 	})
 }
 
 // assertPGBouncerPodsAreReady verifies if PGBouncer pooler pods are ready
 func assertPGBouncerPodsAreReady(namespace, poolerYamlFilePath string, expectedPodCount int) {
-	Eventually(func() (bool, error) {
+	Eventually(func(g Gomega) {
 		poolerName, err := yaml.GetResourceNameFromYAML(env.Scheme, poolerYamlFilePath)
-		Expect(err).ToNot(HaveOccurred())
+		g.Expect(err).ToNot(HaveOccurred())
 		podList := &corev1.PodList{}
 		err = env.Client.List(env.Ctx, podList, ctrlclient.InNamespace(namespace),
 			ctrlclient.MatchingLabels{utils.PgbouncerNameLabel: poolerName})
-		if err != nil {
-			return false, err
-		}
+		g.Expect(err).ToNot(HaveOccurred())
 
 		podItemsCount := len(podList.Items)
-		if podItemsCount != expectedPodCount {
-			return false, fmt.Errorf("expected pgBouncer pods count match passed expected instance count. "+
-				"Got: %v, Expected: %v", podItemsCount, expectedPodCount)
-		}
+		g.Expect(podItemsCount).To(BeEquivalentTo(expectedPodCount))
 
 		activeAndReadyPodCount := 0
 		for _, item := range podList.Items {
@@ -2102,14 +2097,8 @@ func assertPGBouncerPodsAreReady(namespace, poolerYamlFilePath string, expectedP
 			}
 			continue
 		}
-
-		if activeAndReadyPodCount != expectedPodCount {
-			return false, fmt.Errorf("expected pgBouncer pods to be all active and ready. Got: %v, Expected: %v",
-				activeAndReadyPodCount, expectedPodCount)
-		}
-
-		return true, nil
-	}, 90).Should(BeTrue())
+		g.Expect(activeAndReadyPodCount).To(BeEquivalentTo(expectedPodCount))
+	}, 90).Should(Succeed())
 }
 
 func assertReadWriteConnectionUsingPgBouncerService(
@@ -2331,12 +2320,12 @@ func OnlineResizePVC(namespace, clusterName string) {
 		if walStorageEnabled {
 			expectedCount = 6
 		}
-		Eventually(func() int {
+		Eventually(func(g Gomega) {
 			// Variable counter to store the updated total of expanded PVCs. It should be equal to three
 			updateCount := 0
 			// Gathering PVC list
 			err := env.Client.List(env.Ctx, pvc, ctrlclient.InNamespace(namespace))
-			Expect(err).ToNot(HaveOccurred())
+			g.Expect(err).ToNot(HaveOccurred())
 			// Iterating through PVC list to compare with expanded size
 			for _, pvClaim := range pvc.Items {
 				// Size comparison
@@ -2344,8 +2333,8 @@ func OnlineResizePVC(namespace, clusterName string) {
 					updateCount++
 				}
 			}
-			return updateCount
-		}, 300).Should(BeEquivalentTo(expectedCount))
+			g.Expect(updateCount).To(BeEquivalentTo(expectedCount))
+		}, 300).Should(Succeed())
 	})
 }
 
diff --git a/tests/e2e/backup_restore_azure_test.go b/tests/e2e/backup_restore_azure_test.go
index 6b335da4..d83e2c6a 100644
--- a/tests/e2e/backup_restore_azure_test.go
+++ b/tests/e2e/backup_restore_azure_test.go
@@ -479,11 +479,11 @@ func prepareClusterForPITROnAzureBlob(
 		Eventually(func() (int, error) {
 			return backups.CountFilesOnAzureBlobStorage(azureConfig, clusterName, "data.tar")
 		}, 30).Should(BeEquivalentTo(expectedVal))
-		Eventually(func() (string, error) {
+		Eventually(func(g Gomega) {
 			cluster, err := clusterutils.Get(env.Ctx, env.Client, namespace, clusterName)
-			Expect(err).ToNot(HaveOccurred())
-			return cluster.Status.FirstRecoverabilityPoint, err
-		}, 30).ShouldNot(BeEmpty())
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(cluster.Status.FirstRecoverabilityPoint).ToNot(BeEmpty())
+		}, 30).Should(Succeed())
 	})
 
 	// Write a table and insert 2 entries on the "app" database
diff --git a/tests/e2e/backup_restore_azurite_test.go b/tests/e2e/backup_restore_azurite_test.go
index 8b4e8f47..c0f8281e 100644
--- a/tests/e2e/backup_restore_azurite_test.go
+++ b/tests/e2e/backup_restore_azurite_test.go
@@ -292,11 +292,11 @@ func prepareClusterBackupOnAzurite(
 		Eventually(func() (int, error) {
 			return backups.CountFilesOnAzuriteBlobStorage(namespace, clusterName, "data.tar")
 		}, 30).Should(BeNumerically(">=", 1))
-		Eventually(func() (string, error) {
+		Eventually(func(g Gomega) {
 			cluster, err := clusterutils.Get(env.Ctx, env.Client, namespace, clusterName)
-			Expect(err).ToNot(HaveOccurred())
-			return cluster.Status.FirstRecoverabilityPoint, err
-		}, 30).ShouldNot(BeEmpty())
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(cluster.Status.FirstRecoverabilityPoint).ToNot(BeEmpty())
+		}, 30).Should(Succeed())
 	})
 	backups.AssertBackupConditionInClusterStatus(env.Ctx, env.Client, namespace, clusterName)
 }
@@ -318,11 +318,11 @@ func prepareClusterForPITROnAzurite(
 		Eventually(func() (int, error) {
 			return backups.CountFilesOnAzuriteBlobStorage(namespace, clusterName, "data.tar")
 		}, 30).Should(BeNumerically(">=", 1))
-		Eventually(func() (string, error) {
+		Eventually(func(g Gomega) {
 			cluster, err := clusterutils.Get(env.Ctx, env.Client, namespace, clusterName)
-			Expect(err).ToNot(HaveOccurred())
-			return cluster.Status.FirstRecoverabilityPoint, err
-		}, 30).ShouldNot(BeEmpty())
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(cluster.Status.FirstRecoverabilityPoint).ToNot(BeEmpty())
+		}, 30).Should(Succeed())
 	})
 
 	// Write a table and insert 2 entries on the "app" database
diff --git a/tests/e2e/backup_restore_minio_test.go b/tests/e2e/backup_restore_minio_test.go
index 4f4a611d..9e065f32 100644
--- a/tests/e2e/backup_restore_minio_test.go
+++ b/tests/e2e/backup_restore_minio_test.go
@@ -800,11 +800,11 @@ func prepareClusterForPITROnMinio(
 		}, 60).Should(BeNumerically(">=", expectedVal),
 			fmt.Sprintf("verify the number of backups %v is greater than or equal to %v", latestTar,
 				expectedVal))
-		Eventually(func() (string, error) {
+		Eventually(func(g Gomega) {
 			cluster, err := clusterutils.Get(env.Ctx, env.Client, namespace, clusterName)
-			Expect(err).ToNot(HaveOccurred())
-			return cluster.Status.FirstRecoverabilityPoint, err
-		}, 30).ShouldNot(BeEmpty())
+			g.Expect(err).ToNot(HaveOccurred())
+			g.Expect(cluster.Status.FirstRecoverabilityPoint).ToNot(BeEmpty())
+		}, 30).Should(Succeed())
 	})
 
 	// Write a table and insert 2 entries on the "app" database
diff --git a/tests/e2e/certificates_test.go b/tests/e2e/certificates_test.go
index a12c8850..26a82aca 100644
--- a/tests/e2e/certificates_test.go
+++ b/tests/e2e/certificates_test.go
@@ -110,7 +110,7 @@ var _ = Describe("Certificates", func() {
 				Containers: []corev1.Container{
 					{
 						Name:  name,
-						Image: "ghcr.io/cloudnative-pg/webtest:1.6.0",
+						Image: "ghcr.io/cloudnative-pg/webtest:1.7.0",
 						Ports: []corev1.ContainerPort{
 							{
 								ContainerPort: 8080,
diff --git a/tests/e2e/config_support_test.go b/tests/e2e/config_support_test.go
index 8477e3a0..4f02ba0d 100644
--- a/tests/e2e/config_support_test.go
+++ b/tests/e2e/config_support_test.go
@@ -21,6 +21,8 @@ import (
 
 	"github.com/onsi/ginkgo/v2/types"
 	corev1 "k8s.io/api/core/v1"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
 
 	"github.com/cloudnative-pg/cloudnative-pg/tests"
@@ -45,16 +47,47 @@ var _ = Describe("Config support", Serial, Ordered, Label(tests.LabelDisruptive,
 		level                          = tests.Low
 	)
 	var operatorNamespace, namespace string
+	var initialConfigMap *corev1.ConfigMap
+	var initialSecret *corev1.Secret
 
 	BeforeEach(func() {
 		if testLevelEnv.Depth < int(level) {
 			Skip("Test depth is lower than the amount requested for this test")
 		}
+	})
 
+	BeforeAll(func() {
 		operatorDeployment, err := operator.GetDeployment(env.Ctx, env.Client)
 		Expect(err).ToNot(HaveOccurred())
-
 		operatorNamespace = operatorDeployment.GetNamespace()
+
+		// Save the initial configMap
+		initialConfigMap = &corev1.ConfigMap{}
+		err = env.Client.Get(env.Ctx, ctrlclient.ObjectKey{Namespace: operatorNamespace, Name: configName},
+			initialConfigMap)
+		if !apierrors.IsNotFound(err) {
+			Expect(err).ToNot(HaveOccurred())
+		}
+		initialConfigMap.SetResourceVersion("")
+		initialConfigMap.SetUID("")
+		initialConfigMap.SetCreationTimestamp(metav1.Time{})
+		initialConfigMap.SetSelfLink("")
+		initialConfigMap.SetGeneration(0)
+		initialConfigMap.SetManagedFields(nil)
+
+		// Save the initial secret
+		initialSecret = &corev1.Secret{}
+		err = env.Client.Get(env.Ctx, ctrlclient.ObjectKey{Namespace: operatorNamespace, Name: configName},
+			initialSecret)
+		if !apierrors.IsNotFound(err) {
+			Expect(err).ToNot(HaveOccurred())
+		}
+		initialSecret.SetResourceVersion("")
+		initialSecret.SetUID("")
+		initialSecret.SetCreationTimestamp(metav1.Time{})
+		initialSecret.SetSelfLink("")
+		initialSecret.SetGeneration(0)
+		initialSecret.SetManagedFields(nil)
 	})
 
 	AfterAll(func() {
@@ -76,6 +109,16 @@ var _ = Describe("Config support", Serial, Ordered, Label(tests.LabelDisruptive,
 		err = env.Client.Delete(env.Ctx, secret)
 		Expect(err).NotTo(HaveOccurred())
 
+		// Create preexisting ConfigMap and Secret
+		if initialConfigMap.Name != "" {
+			err = env.Client.Create(env.Ctx, initialConfigMap)
+			Expect(err).ToNot(HaveOccurred())
+		}
+		if initialSecret.Name != "" {
+			err = env.Client.Create(env.Ctx, initialSecret)
+			Expect(err).ToNot(HaveOccurred())
+		}
+
 		err = operator.ReloadDeployment(env.Ctx, env.Client, env.Interface, 120)
 		Expect(err).ToNot(HaveOccurred())
 	})
diff --git a/tests/e2e/eviction_test.go b/tests/e2e/eviction_test.go
index 121f4f14..54b9a6f4 100644
--- a/tests/e2e/eviction_test.go
+++ b/tests/e2e/eviction_test.go
@@ -278,15 +278,17 @@ var _ = Describe("Pod eviction", Serial, Label(tests.LabelDisruptive), func() {
 			})
 
 			By("checking switchover happens", func() {
-				Eventually(func() bool {
+				Eventually(func() (bool, error) {
 					podList, err := clusterutils.ListPods(env.Ctx, env.Client, namespace, clusterName)
-					Expect(err).ToNot(HaveOccurred())
+					if err != nil {
+						return false, err
+					}
 					for _, p := range podList.Items {
 						if specs.IsPodPrimary(p) && primaryPod.GetName() != p.GetName() {
-							return true
+							return true, nil
 						}
 					}
-					return false
+					return false, nil
 				}, 60).Should(BeTrue())
 			})
 
diff --git a/tests/e2e/failover_test.go b/tests/e2e/failover_test.go
index 657321c7..23f922fc 100644
--- a/tests/e2e/failover_test.go
+++ b/tests/e2e/failover_test.go
@@ -120,13 +120,13 @@ var _ = Describe("Failover", Label(tests.LabelSelfHealing), func() {
 			Expect(err).ToNot(HaveOccurred())
 
 			// Expect the primary to have lost connection with the stopped standby
-			Eventually(func() (int, error) {
+			Eventually(func(g Gomega) {
 				primaryPod, err = podutils.Get(env.Ctx, env.Client, namespace, currentPrimary)
-				Expect(err).ToNot(HaveOccurred())
-				return postgres.CountReplicas(
+				g.Expect(err).ToNot(HaveOccurred())
+				g.Expect(postgres.CountReplicas(
 					env.Ctx, env.Client, env.Interface, env.RestClientConfig,
-					primaryPod, RetryTimeout)
-			}, RetryTimeout).Should(BeEquivalentTo(1))
+					primaryPod, RetryTimeout)).To(BeEquivalentTo(1))
+			}, RetryTimeout).Should(Succeed())
 		})
 
 		// Perform a CHECKPOINT on the primary and wait for the working standby
@@ -166,9 +166,9 @@ var _ = Describe("Failover", Label(tests.LabelSelfHealing), func() {
 				targetPrimary, strings.Trim(initialLSN, "\n"))
 			// The replay_lsn of the targetPrimary should be ahead
 			// of the one before the checkpoint
-			Eventually(func() (string, error) {
+			Eventually(func(g Gomega) {
 				primaryPod, err = podutils.Get(env.Ctx, env.Client, namespace, currentPrimary)
-				Expect(err).ToNot(HaveOccurred())
+				g.Expect(err).ToNot(HaveOccurred())
 				out, _, err := exec.EventuallyExecQueryInInstancePod(
 					env.Ctx, env.Client, env.Interface, env.RestClientConfig,
 					exec.PodLocator{
@@ -179,8 +179,9 @@ var _ = Describe("Failover", Label(tests.LabelSelfHealing), func() {
 					RetryTimeout,
 					PollingTime,
 				)
-				return strings.TrimSpace(out), err
-			}, RetryTimeout).Should(BeEquivalentTo("t"))
+				g.Expect(err).ToNot(HaveOccurred())
+				g.Expect(strings.TrimSpace(out)).To(BeEquivalentTo("t"))
+			}, RetryTimeout).Should(Succeed())
 		})
 
 		// Force-delete the primary. Eventually the cluster should elect a
diff --git a/tests/e2e/fastfailover_test.go b/tests/e2e/fastfailover_test.go
index f49921ce..7b356c74 100644
--- a/tests/e2e/fastfailover_test.go
+++ b/tests/e2e/fastfailover_test.go
@@ -45,14 +45,7 @@ var _ = Describe("Fast failover", Serial, Label(tests.LabelPerformance, tests.La
 			Skip("Test depth is lower than the amount requested for this test")
 		}
 
-		// The walreceiver of a standby that wasn't promoted may try to reconnect
-		// before the rw service endpoints are updated. In this case, the walreceiver
-		// can be stuck for waiting for the connection to be established for a time that
-		// depends on the tcp_syn_retries sysctl. Since by default
-		// net.ipv4.tcp_syn_retries=6, PostgreSQL can wait 2^7-1=127 seconds before
-		// restarting the walreceiver.
 		if !IsLocal() {
-			maxReattachTime = 180
 			maxFailoverTime = 30
 		}
 	})
diff --git a/tests/e2e/fastswitchover_test.go b/tests/e2e/fastswitchover_test.go
index 99c8017b..9b96bfc6 100644
--- a/tests/e2e/fastswitchover_test.go
+++ b/tests/e2e/fastswitchover_test.go
@@ -20,7 +20,9 @@ import (
 	"fmt"
 	"strings"
 
+	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/client-go/util/retry"
 
@@ -28,6 +30,7 @@ import (
 	"github.com/cloudnative-pg/cloudnative-pg/tests"
 	"github.com/cloudnative-pg/cloudnative-pg/tests/utils"
 	"github.com/cloudnative-pg/cloudnative-pg/tests/utils/clusterutils"
+	"github.com/cloudnative-pg/cloudnative-pg/tests/utils/deployments"
 	"github.com/cloudnative-pg/cloudnative-pg/tests/utils/exec"
 	"github.com/cloudnative-pg/cloudnative-pg/tests/utils/postgres"
 	"github.com/cloudnative-pg/cloudnative-pg/tests/utils/run"
@@ -156,6 +159,10 @@ func assertFastSwitchover(namespace, sampleFile, clusterName, webTestFile, webTe
 		_, _, err := run.Run("kubectl create -n " + namespace +
 			" -f " + webTestFile)
 		Expect(err).ToNot(HaveOccurred())
+
+		webtestDeploy := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "webtest", Namespace: namespace}}
+		Expect(deployments.WaitForReady(env.Ctx, env.Client, webtestDeploy, 60)).To(Succeed())
+
 		_, _, err = run.Run("kubectl create -n " + namespace +
 			" -f " + webTestJob)
 		Expect(err).ToNot(HaveOccurred())
@@ -197,16 +204,6 @@ func assertFastSwitchover(namespace, sampleFile, clusterName, webTestFile, webTe
 	var maxReattachTime int32 = 60
 	var maxSwitchoverTime int32 = 20
 
-	// The walreceiver of a standby that wasn't promoted may try to reconnect
-	// before the rw service endpoints are updated. In this case, the walreceiver
-	// can be stuck for waiting for the connection to be established for a time that
-	// depends on the tcp_syn_retries sysctl. Since by default
-	// net.ipv4.tcp_syn_retries=6, PostgreSQL can wait 2^7-1=127 seconds before
-	// restarting the walreceiver.
-	if !IsLocal() {
-		maxReattachTime = 180
-	}
-
 	AssertStandbysFollowPromotion(namespace, clusterName, maxReattachTime)
 
 	AssertWritesResumedBeforeTimeout(namespace, clusterName, maxSwitchoverTime)
diff --git a/tests/e2e/fixtures/fastfailover/apache-benchmark-webtest.yaml b/tests/e2e/fixtures/fastfailover/apache-benchmark-webtest.yaml
index 8af0fe34..347e76dc 100644
--- a/tests/e2e/fixtures/fastfailover/apache-benchmark-webtest.yaml
+++ b/tests/e2e/fixtures/fastfailover/apache-benchmark-webtest.yaml
@@ -14,5 +14,9 @@ spec:
          - "120"
          - "-c"
          - "5"
+         - "-v"
+         - "2"
+         - "-s"
+         - "3"
          - "http://webtest:8080/tx"
       restartPolicy: Never
diff --git a/tests/e2e/fixtures/fastfailover/webtest-syncreplicas.yaml b/tests/e2e/fixtures/fastfailover/webtest-syncreplicas.yaml
index 8de28f9e..541f9bf8 100644
--- a/tests/e2e/fixtures/fastfailover/webtest-syncreplicas.yaml
+++ b/tests/e2e/fixtures/fastfailover/webtest-syncreplicas.yaml
@@ -16,7 +16,7 @@ spec:
         app: webtest
     spec:
       containers:
-        - image: ghcr.io/cloudnative-pg/webtest:1.6.0
+        - image: ghcr.io/cloudnative-pg/webtest:1.7.0
           name: webtest
           env:
             - name: PASSWORD
@@ -33,6 +33,12 @@ spec:
               value: "postgres://$(USER):$(PASSWORD)@cluster-syncreplicas-fast-failover-rw/app?sslmode=require&connect_timeout=2"
             - name: SQL_QUERY
               value: "insert into tps.tl(source) values ('hey');"
+          livenessProbe:
+            tcpSocket:
+              port: 8080
+          readinessProbe:
+            tcpSocket:
+              port: 8080
           ports:
             - containerPort: 8080
 ---
diff --git a/tests/e2e/fixtures/fastfailover/webtest.yaml b/tests/e2e/fixtures/fastfailover/webtest.yaml
index 3865c4df..e3464cfa 100644
--- a/tests/e2e/fixtures/fastfailover/webtest.yaml
+++ b/tests/e2e/fixtures/fastfailover/webtest.yaml
@@ -16,7 +16,7 @@ spec:
         app: webtest
     spec:
       containers:
-        - image: ghcr.io/cloudnative-pg/webtest:1.6.0
+        - image: ghcr.io/cloudnative-pg/webtest:1.7.0
           name: webtest
           env:
             - name: PASSWORD
@@ -33,6 +33,12 @@ spec:
               value: "postgres://$(USER):$(PASSWORD)@cluster-fast-failover-rw/app?sslmode=require&connect_timeout=2"
             - name: SQL_QUERY
               value: "insert into tps.tl(source) values ('hey');"
+          livenessProbe:
+            tcpSocket:
+              port: 8080
+          readinessProbe:
+            tcpSocket:
+              port: 8080
           ports:
             - containerPort: 8080
 ---
diff --git a/tests/e2e/fixtures/fastswitchover/apache-benchmark-webtest.yaml b/tests/e2e/fixtures/fastswitchover/apache-benchmark-webtest.yaml
index 8af0fe34..82192f24 100644
--- a/tests/e2e/fixtures/fastswitchover/apache-benchmark-webtest.yaml
+++ b/tests/e2e/fixtures/fastswitchover/apache-benchmark-webtest.yaml
@@ -9,10 +9,14 @@ spec:
       - name: apache-benchmark
         image: httpd
         command:
-         - "/usr/local/apache2/bin/ab"
-         - "-t"
-         - "120"
-         - "-c"
-         - "5"
-         - "http://webtest:8080/tx"
+          - "/usr/local/apache2/bin/ab"
+          - "-t"
+          - "120"
+          - "-c"
+          - "5"
+          - "-v"
+          - "2"
+          - "-s"
+          - "3"
+          - "http://webtest:8080/tx"
       restartPolicy: Never
diff --git a/tests/e2e/fixtures/fastswitchover/webtest.yaml b/tests/e2e/fixtures/fastswitchover/webtest.yaml
index f94effba..8ef5df71 100644
--- a/tests/e2e/fixtures/fastswitchover/webtest.yaml
+++ b/tests/e2e/fixtures/fastswitchover/webtest.yaml
@@ -16,7 +16,7 @@ spec:
         app: webtest
     spec:
       containers:
-        - image: ghcr.io/cloudnative-pg/webtest:1.6.0
+        - image: ghcr.io/cloudnative-pg/webtest:1.7.0
           name: webtest
           env:
             - name: PASSWORD
@@ -33,6 +33,12 @@ spec:
               value: "postgres://$(USER):$(PASSWORD)@cluster-fast-switchover-rw/app?sslmode=require&connect_timeout=2"
             - name: SQL_QUERY
               value: "insert into tps.tl(source) values ('hey');"
+          livenessProbe:
+            tcpSocket:
+              port: 8080
+          readinessProbe:
+            tcpSocket:
+              port: 8080
           ports:
             - containerPort: 8080
 ---
diff --git a/tests/e2e/fixtures/sync_replicas/cluster-pgstatstatements.yaml.template b/tests/e2e/fixtures/sync_replicas/cluster-pgstatstatements.yaml.template
index d773df1b..20cbcd7b 100644
--- a/tests/e2e/fixtures/sync_replicas/cluster-pgstatstatements.yaml.template
+++ b/tests/e2e/fixtures/sync_replicas/cluster-pgstatstatements.yaml.template
@@ -18,6 +18,10 @@ spec:
       log_autovacuum_min_duration: '1s'
       'pg_stat_statements.max': '10000'
       log_replication_commands: 'on'
+  probes:
+    readiness:
+      failureThreshold: 10
+      periodSeconds: 1
 
   storage:
     storageClass: ${E2E_DEFAULT_STORAGE_CLASS}
diff --git a/tests/e2e/fixtures/sync_replicas/cluster-sync-replica-legacy.yaml.template b/tests/e2e/fixtures/sync_replicas/cluster-sync-replica-legacy.yaml.template
index 5a9dc71f..973e8ef6 100644
--- a/tests/e2e/fixtures/sync_replicas/cluster-sync-replica-legacy.yaml.template
+++ b/tests/e2e/fixtures/sync_replicas/cluster-sync-replica-legacy.yaml.template
@@ -17,6 +17,10 @@ spec:
       log_temp_files: '1024'
       log_autovacuum_min_duration: '1s'
       log_replication_commands: 'on'
+  probes:
+    readiness:
+      failureThreshold: 10
+      periodSeconds: 1
 
   storage:
     storageClass: ${E2E_DEFAULT_STORAGE_CLASS}
diff --git a/tests/e2e/fixtures/sync_replicas/cluster-sync-replica.yaml.template b/tests/e2e/fixtures/sync_replicas/cluster-sync-replica.yaml.template
index 8bb4e439..bbdccd10 100644
--- a/tests/e2e/fixtures/sync_replicas/cluster-sync-replica.yaml.template
+++ b/tests/e2e/fixtures/sync_replicas/cluster-sync-replica.yaml.template
@@ -17,6 +17,10 @@ spec:
       log_temp_files: '1024'
       log_autovacuum_min_duration: '1s'
       log_replication_commands: 'on'
+  probes:
+    readiness:
+      failureThreshold: 10
+      periodSeconds: 1
 
   storage:
     storageClass: ${E2E_DEFAULT_STORAGE_CLASS}
diff --git a/tests/e2e/fixtures/sync_replicas/preferred.yaml.template b/tests/e2e/fixtures/sync_replicas/preferred.yaml.template
index 86aee124..737ae75d 100644
--- a/tests/e2e/fixtures/sync_replicas/preferred.yaml.template
+++ b/tests/e2e/fixtures/sync_replicas/preferred.yaml.template
@@ -18,6 +18,10 @@ spec:
       log_temp_files: '1024'
       log_autovacuum_min_duration: '1s'
       log_replication_commands: 'on'
+  probes:
+    readiness:
+      failureThreshold: 10
+      periodSeconds: 1
 
   storage:
     storageClass: ${E2E_DEFAULT_STORAGE_CLASS}
diff --git a/tests/e2e/fixtures/sync_replicas/readiness-probe-lag-control.yaml.template b/tests/e2e/fixtures/sync_replicas/readiness-probe-lag-control.yaml.template
index af46bd51..33075f2e 100644
--- a/tests/e2e/fixtures/sync_replicas/readiness-probe-lag-control.yaml.template
+++ b/tests/e2e/fixtures/sync_replicas/readiness-probe-lag-control.yaml.template
@@ -23,7 +23,7 @@ spec:
     readiness:
       type: streaming
       maximumLag: 16Mi
-      failureThreshold: 30
+      failureThreshold: 15
       periodSeconds: 1
 
   storage:
diff --git a/tests/e2e/fixtures/sync_replicas/startup-probe-lag-control.yaml.template b/tests/e2e/fixtures/sync_replicas/startup-probe-lag-control.yaml.template
index adbefa38..5e6f5bfc 100644
--- a/tests/e2e/fixtures/sync_replicas/startup-probe-lag-control.yaml.template
+++ b/tests/e2e/fixtures/sync_replicas/startup-probe-lag-control.yaml.template
@@ -23,7 +23,7 @@ spec:
     startup:
       type: streaming
       maximumLag: 16Mi
-      failureThreshold: 30
+      failureThreshold: 60
       periodSeconds: 1
 
   storage:
diff --git a/tests/e2e/managed_roles_test.go b/tests/e2e/managed_roles_test.go
index b1e671fd..748b7a02 100644
--- a/tests/e2e/managed_roles_test.go
+++ b/tests/e2e/managed_roles_test.go
@@ -116,7 +116,7 @@ var _ = Describe("Managed roles tests", Label(tests.LabelSmoke, tests.LabelBasic
 		assertRoleStatus := func(namespace, clusterName, query, expectedResult string) {
 			primaryPod, err := clusterutils.GetPrimary(env.Ctx, env.Client, namespace, clusterName)
 			Expect(err).ToNot(HaveOccurred())
-			Eventually(func() string {
+			Eventually(func(g Gomega) {
 				stdout, _, err := exec.QueryInInstancePod(
 					env.Ctx, env.Client, env.Interface, env.RestClientConfig,
 					exec.PodLocator{
@@ -125,11 +125,9 @@ var _ = Describe("Managed roles tests", Label(tests.LabelSmoke, tests.LabelBasic
 					},
 					postgres.PostgresDBName,
 					query)
-				if err != nil {
-					return ""
-				}
-				return strings.TrimSpace(stdout)
-			}, 30).Should(Equal(expectedResult))
+				g.Expect(err).ToNot(HaveOccurred())
+				g.Expect(strings.TrimSpace(stdout)).To(Equal(expectedResult))
+			}, 30).Should(Succeed())
 		}
 
 		It("can create roles specified in the managed roles stanza", func() {
@@ -405,11 +403,11 @@ var _ = Describe("Managed roles tests", Label(tests.LabelSmoke, tests.LabelBasic
 				}
 				err = env.Client.Patch(env.Ctx, updated, client.MergeFrom(cluster))
 				Expect(err).ToNot(HaveOccurred())
-				Eventually(func() int {
+				Eventually(func(g Gomega) {
 					cluster, err := clusterutils.Get(env.Ctx, env.Client, namespace, clusterName)
-					Expect(err).ToNot(HaveOccurred())
-					return len(cluster.Status.ManagedRolesStatus.CannotReconcile)
-				}, 30).Should(Equal(0))
+					g.Expect(err).ToNot(HaveOccurred())
+					g.Expect(cluster.Status.ManagedRolesStatus.CannotReconcile).To(BeEmpty())
+				}, 30).Should(Succeed())
 				assertInRoles(namespace, primaryPod.Name, newUserName, []string{"postgres", username})
 			})
 
@@ -427,11 +425,11 @@ var _ = Describe("Managed roles tests", Label(tests.LabelSmoke, tests.LabelBasic
 				}
 				err = env.Client.Patch(env.Ctx, updated, client.MergeFrom(cluster))
 				Expect(err).ToNot(HaveOccurred())
-				Eventually(func() int {
+				Eventually(func(g Gomega) {
 					cluster, err := clusterutils.Get(env.Ctx, env.Client, namespace, clusterName)
-					Expect(err).ToNot(HaveOccurred())
-					return len(cluster.Status.ManagedRolesStatus.CannotReconcile)
-				}, 30).Should(Equal(0))
+					g.Expect(err).ToNot(HaveOccurred())
+					g.Expect(cluster.Status.ManagedRolesStatus.CannotReconcile).To(BeEmpty())
+				}, 30).Should(Succeed())
 				assertInRoles(namespace, primaryPod.Name, newUserName, []string{username})
 			})
 
@@ -450,22 +448,23 @@ var _ = Describe("Managed roles tests", Label(tests.LabelSmoke, tests.LabelBasic
 				// user not changed
 				Eventually(QueryMatchExpectationPredicate(primaryPod, postgres.PostgresDBName,
 					roleExistsQuery(unrealizableUser), "t"), 30).Should(Succeed())
-				Eventually(func() int {
+				Eventually(func(g Gomega) {
 					cluster, err := clusterutils.Get(env.Ctx, env.Client, namespace, clusterName)
-					Expect(err).ToNot(HaveOccurred())
-					return len(cluster.Status.ManagedRolesStatus.CannotReconcile)
-				}, 30).Should(Equal(1))
-				Eventually(func() int {
+					g.Expect(err).ToNot(HaveOccurred())
+					g.Expect(cluster.Status.ManagedRolesStatus.CannotReconcile).To(HaveLen(1))
+				}, 30).Should(Succeed())
+				Eventually(func(g Gomega) {
 					cluster, err := clusterutils.Get(env.Ctx, env.Client, namespace, clusterName)
-					Expect(err).ToNot(HaveOccurred())
-					return len(cluster.Status.ManagedRolesStatus.CannotReconcile[unrealizableUser])
-				}, 30).Should(Equal(1))
-				Eventually(func() string {
+					g.Expect(err).ToNot(HaveOccurred())
+					g.Expect(cluster.Status.ManagedRolesStatus.CannotReconcile[unrealizableUser]).To(HaveLen(1))
+				}, 30).Should(Succeed())
+				Eventually(func(g Gomega) {
 					cluster, err := clusterutils.Get(env.Ctx, env.Client, namespace, clusterName)
-					Expect(err).ToNot(HaveOccurred())
-					return cluster.Status.ManagedRolesStatus.CannotReconcile[unrealizableUser][0]
-				}, 30).Should(ContainSubstring(fmt.Sprintf("role \"%s\" is a member of role \"%s\"",
-					unrealizableUser, unrealizableUser)))
+					g.Expect(err).ToNot(HaveOccurred())
+					g.Expect(cluster.Status.ManagedRolesStatus.CannotReconcile[unrealizableUser][0]).To(
+						ContainSubstring(fmt.Sprintf("role \"%s\" is a member of role \"%s\"",
+							unrealizableUser, unrealizableUser)))
+				}, 30).Should(Succeed())
 			})
 		})
 
diff --git a/tests/e2e/metrics_test.go b/tests/e2e/metrics_test.go
index 00675f9b..71890aa8 100644
--- a/tests/e2e/metrics_test.go
+++ b/tests/e2e/metrics_test.go
@@ -349,7 +349,7 @@ var _ = Describe("Metrics", Label(tests.LabelObservability), func() {
 					g.Expect(strings.TrimSpace(out)).To(BeEquivalentTo("t"))
 				}).WithTimeout(
 					time.Duration(timeouts.DefaultTestTimeouts[timeouts.Short])*time.Second,
-				).WithPolling(time.Second).Should(Succeed(), fmt.Sprintf("on pod %v", pod.Name))
+				).Should(Succeed(), fmt.Sprintf("on pod %v", pod.Name))
 
 				out, err := proxy.RetrieveMetricsFromInstance(env.Ctx, env.Interface, pod,
 					replicaCluster.IsMetricsTLSEnabled())
diff --git a/tests/e2e/nodeselector_test.go b/tests/e2e/nodeselector_test.go
index a9394583..e7b3d1a0 100644
--- a/tests/e2e/nodeselector_test.go
+++ b/tests/e2e/nodeselector_test.go
@@ -76,10 +76,10 @@ var _ = Describe("nodeSelector", Label(tests.LabelPodScheduling), func() {
 			// We check the error to verify that's the case
 			By("verifying that the pods can't be scheduled", func() {
 				timeout := 120
-				Eventually(func() bool {
+				Eventually(func(g Gomega) {
 					isPending := false
 					podList, err := pods.List(env.Ctx, env.Client, namespace)
-					Expect(err).ToNot(HaveOccurred())
+					g.Expect(err).ToNot(HaveOccurred())
 					if len(podList.Items) > 0 {
 						if len(podList.Items[0].Status.Conditions) > 0 {
 							if podList.Items[0].Status.Phase == "Pending" && strings.Contains(podList.Items[0].Status.Conditions[0].Message,
@@ -93,8 +93,8 @@ var _ = Describe("nodeSelector", Label(tests.LabelPodScheduling), func() {
 							}
 						}
 					}
-					return isPending
-				}, timeout).Should(BeEquivalentTo(true))
+					g.Expect(isPending).To(BeTrue())
+				}, timeout).Should(Succeed())
 			})
 		})
 	})
diff --git a/tests/e2e/operator_unavailable_test.go b/tests/e2e/operator_unavailable_test.go
index 4a38e29a..a573de9a 100644
--- a/tests/e2e/operator_unavailable_test.go
+++ b/tests/e2e/operator_unavailable_test.go
@@ -84,16 +84,16 @@ var _ = Describe("Operator unavailable", Serial, Label(tests.LabelDisruptive, te
 				Expect(err).ToNot(HaveOccurred())
 
 				// Expect only 2 instances to be up and running
-				Eventually(func() int32 {
+				Eventually(func(g Gomega) {
 					podList := &corev1.PodList{}
 					err := env.Client.List(
 						env.Ctx, podList,
 						ctrlclient.InNamespace(namespace),
 						ctrlclient.MatchingLabels{utils.ClusterLabelName: clusterName},
 					)
-					Expect(err).ToNot(HaveOccurred())
-					return int32(len(podList.Items))
-				}, 120).Should(BeEquivalentTo(2))
+					g.Expect(err).ToNot(HaveOccurred())
+					g.Expect(podList.Items).To(HaveLen(2))
+				}, 120).Should(Succeed())
 
 				// And to stay like that
 				Consistently(func() int32 {
@@ -183,15 +183,15 @@ var _ = Describe("Operator unavailable", Serial, Label(tests.LabelDisruptive, te
 				wg.Wait()
 
 				// Expect only 2 instances to be up and running
-				Eventually(func() int32 {
+				Eventually(func(g Gomega) {
 					podList := &corev1.PodList{}
 					err := env.Client.List(
 						env.Ctx, podList, ctrlclient.InNamespace(namespace),
 						ctrlclient.MatchingLabels{utils.ClusterLabelName: "operator-unavailable"},
 					)
-					Expect(err).ToNot(HaveOccurred())
-					return int32(len(utils.FilterActivePods(podList.Items)))
-				}, 120).Should(BeEquivalentTo(2))
+					g.Expect(err).ToNot(HaveOccurred())
+					g.Expect(utils.FilterActivePods(podList.Items)).To(HaveLen(2))
+				}, 120).Should(Succeed())
 			})
 
 			By("verifying a new operator pod is now back", func() {
diff --git a/tests/e2e/publication_subscription_test.go b/tests/e2e/publication_subscription_test.go
index e2537d56..197b4b28 100644
--- a/tests/e2e/publication_subscription_test.go
+++ b/tests/e2e/publication_subscription_test.go
@@ -154,7 +154,7 @@ var _ = Describe("Publication and Subscription", Label(tests.LabelPublicationSub
 
 				Eventually(func(g Gomega) {
 					err := env.Client.Get(env.Ctx, databaseNamespacedName, databaseObject)
-					Expect(err).ToNot(HaveOccurred())
+					g.Expect(err).ToNot(HaveOccurred())
 					g.Expect(databaseObject.Status.Applied).Should(HaveValue(BeTrue()))
 				}, 300).WithPolling(10 * time.Second).Should(Succeed())
 			})
@@ -187,7 +187,7 @@ var _ = Describe("Publication and Subscription", Label(tests.LabelPublicationSub
 				Eventually(func(g Gomega) {
 					pub := &apiv1.Publication{}
 					err := env.Client.Get(env.Ctx, pubNamespacedName, pub)
-					Expect(err).ToNot(HaveOccurred())
+					g.Expect(err).ToNot(HaveOccurred())
 					g.Expect(pub.Status.Applied).Should(HaveValue(BeTrue()))
 				}, 300).WithPolling(10 * time.Second).Should(Succeed())
 			})
@@ -220,7 +220,7 @@ var _ = Describe("Publication and Subscription", Label(tests.LabelPublicationSub
 				Eventually(func(g Gomega) {
 					sub := &apiv1.Subscription{}
 					err := env.Client.Get(env.Ctx, pubNamespacedName, sub)
-					Expect(err).ToNot(HaveOccurred())
+					g.Expect(err).ToNot(HaveOccurred())
 					g.Expect(sub.Status.Applied).Should(HaveValue(BeTrue()))
 				}, 300).WithPolling(10 * time.Second).Should(Succeed())
 			})
diff --git a/tests/e2e/syncreplicas_test.go b/tests/e2e/syncreplicas_test.go
index 80982e6d..221d2c42 100644
--- a/tests/e2e/syncreplicas_test.go
+++ b/tests/e2e/syncreplicas_test.go
@@ -50,9 +50,9 @@ var _ = Describe("Synchronous Replicas", Label(tests.LabelReplication), func() {
 	})
 
 	getSyncReplicationCount := func(namespace, clusterName, syncState string, expectedCount int) {
-		Eventually(func() (int, error, error) {
+		Eventually(func(g Gomega) int {
 			primaryPod, err := clusterutils.GetPrimary(env.Ctx, env.Client, namespace, clusterName)
-			Expect(err).ToNot(HaveOccurred())
+			g.Expect(err).ToNot(HaveOccurred())
 
 			out, stdErr, err := exec.QueryInInstancePod(
 				env.Ctx, env.Client, env.Interface, env.RestClientConfig,
@@ -62,18 +62,19 @@ var _ = Describe("Synchronous Replicas", Label(tests.LabelReplication), func() {
 				},
 				"postgres",
 				fmt.Sprintf("SELECT count(*) from pg_catalog.pg_stat_replication WHERE sync_state = '%s'", syncState))
-			Expect(stdErr).To(BeEmpty())
-			Expect(err).ShouldNot(HaveOccurred())
+			g.Expect(stdErr).To(BeEmpty())
+			g.Expect(err).ToNot(HaveOccurred())
 
 			value, atoiErr := strconv.Atoi(strings.Trim(out, "\n"))
-			return value, err, atoiErr
+			g.Expect(atoiErr).ToNot(HaveOccurred())
+			return value
 		}, RetryTimeout).Should(BeEquivalentTo(expectedCount))
 	}
 
 	compareSynchronousStandbyNames := func(namespace, clusterName, element string) {
-		Eventually(func() string {
+		Eventually(func(g Gomega) {
 			primaryPod, err := clusterutils.GetPrimary(env.Ctx, env.Client, namespace, clusterName)
-			Expect(err).ToNot(HaveOccurred())
+			g.Expect(err).ToNot(HaveOccurred())
 
 			out, stdErr, err := exec.QueryInInstancePod(
 				env.Ctx, env.Client, env.Interface, env.RestClientConfig,
@@ -83,11 +84,11 @@ var _ = Describe("Synchronous Replicas", Label(tests.LabelReplication), func() {
 				},
 				"postgres",
 				"select setting from pg_catalog.pg_settings where name = 'synchronous_standby_names'")
-			Expect(stdErr).To(BeEmpty())
-			Expect(err).ShouldNot(HaveOccurred())
+			g.Expect(stdErr).To(BeEmpty())
+			g.Expect(err).ShouldNot(HaveOccurred())
 
-			return strings.Trim(out, "\n")
-		}, 60).Should(ContainSubstring(element))
+			g.Expect(strings.Trim(out, "\n")).To(ContainSubstring(element))
+		}, 30).Should(Succeed())
 	}
 
 	assertProbeRespectsReplicaLag := func(namespace, replicaName, probeType string) {
@@ -177,12 +178,12 @@ var _ = Describe("Synchronous Replicas", Label(tests.LabelReplication), func() {
 			})
 			By("checking that synchronous_standby_names reflects cluster's changes", func() {
 				// Set MaxSyncReplicas to 1
-				Eventually(func(g Gomega) error {
+				Eventually(func(g Gomega) {
 					cluster, err := clusterutils.Get(env.Ctx, env.Client, namespace, clusterName)
 					g.Expect(err).ToNot(HaveOccurred())
 
 					cluster.Spec.MaxSyncReplicas = 1
-					return env.Client.Update(env.Ctx, cluster)
+					g.Expect(env.Client.Update(env.Ctx, cluster)).To(Succeed())
 				}, RetryTimeout, 5).Should(Succeed())
 
 				// Scale the cluster down to 2 pods
@@ -267,12 +268,12 @@ var _ = Describe("Synchronous Replicas", Label(tests.LabelReplication), func() {
 			})
 
 			By("setting MaxStandbyNamesFromCluster to 1 and decreasing to 1 the sync replicas required", func() {
-				Eventually(func(g Gomega) error {
+				Eventually(func(g Gomega) {
 					cluster, err := clusterutils.Get(env.Ctx, env.Client, namespace, clusterName)
 					g.Expect(err).ToNot(HaveOccurred())
 					cluster.Spec.PostgresConfiguration.Synchronous.MaxStandbyNamesFromCluster = ptr.To(1)
 					cluster.Spec.PostgresConfiguration.Synchronous.Number = 1
-					return env.Client.Update(env.Ctx, cluster)
+					g.Expect(env.Client.Update(env.Ctx, cluster)).To(Succeed())
 				}, RetryTimeout, 5).Should(Succeed())
 
 				getSyncReplicationCount(namespace, clusterName, "quorum", 1)
@@ -280,11 +281,11 @@ var _ = Describe("Synchronous Replicas", Label(tests.LabelReplication), func() {
 			})
 
 			By("switching to MethodFirst (priority-based)", func() {
-				Eventually(func(g Gomega) error {
+				Eventually(func(g Gomega) {
 					cluster, err := clusterutils.Get(env.Ctx, env.Client, namespace, clusterName)
 					g.Expect(err).ToNot(HaveOccurred())
 					cluster.Spec.PostgresConfiguration.Synchronous.Method = apiv1.SynchronousReplicaConfigurationMethodFirst
-					return env.Client.Update(env.Ctx, cluster)
+					g.Expect(env.Client.Update(env.Ctx, cluster)).To(Succeed())
 				}, RetryTimeout, 5).Should(Succeed())
 
 				getSyncReplicationCount(namespace, clusterName, "sync", 1)
@@ -292,13 +293,13 @@ var _ = Describe("Synchronous Replicas", Label(tests.LabelReplication), func() {
 			})
 
 			By("by properly setting standbyNamesPre and standbyNamesPost", func() {
-				Eventually(func(g Gomega) error {
+				Eventually(func(g Gomega) {
 					cluster, err := clusterutils.Get(env.Ctx, env.Client, namespace, clusterName)
 					g.Expect(err).ToNot(HaveOccurred())
 					cluster.Spec.PostgresConfiguration.Synchronous.MaxStandbyNamesFromCluster = nil
 					cluster.Spec.PostgresConfiguration.Synchronous.StandbyNamesPre = []string{"preSyncReplica"}
 					cluster.Spec.PostgresConfiguration.Synchronous.StandbyNamesPost = []string{"postSyncReplica"}
-					return env.Client.Update(env.Ctx, cluster)
+					g.Expect(env.Client.Update(env.Ctx, cluster)).To(Succeed())
 				}, RetryTimeout, 5).Should(Succeed())
 				compareSynchronousStandbyNames(namespace, clusterName, "FIRST 1 (\"preSyncReplica\"")
 				compareSynchronousStandbyNames(namespace, clusterName, "\"postSyncReplica\")")
@@ -333,19 +334,19 @@ var _ = Describe("Synchronous Replicas", Label(tests.LabelReplication), func() {
 				By("fencing the second replica and verifying we unset synchronous_standby_names", func() {
 					Expect(fencing.On(env.Ctx, env.Client, fmt.Sprintf("%v-2", clusterName),
 						namespace, clusterName, fencing.UsingAnnotation)).Should(Succeed())
-					Eventually(func() string {
+					Eventually(func(g Gomega) {
 						commandTimeout := time.Second * 10
 						primary, err := clusterutils.GetPrimary(env.Ctx, env.Client, namespace, clusterName)
-						Expect(err).ToNot(HaveOccurred())
+						g.Expect(err).ToNot(HaveOccurred())
 
 						stdout, _, err := exec.Command(
 							env.Ctx, env.Interface, env.RestClientConfig,
 							*primary, specs.PostgresContainerName, &commandTimeout,
 							"psql", "-U", "postgres", "-tAc", "show synchronous_standby_names",
 						)
-						Expect(err).ToNot(HaveOccurred())
-						return strings.Trim(stdout, "\n")
-					}, 160).Should(BeEmpty())
+						g.Expect(err).ToNot(HaveOccurred())
+						g.Expect(strings.Trim(stdout, "\n")).To(BeEmpty())
+					}, 160).Should(Succeed())
 				})
 				By("unfencing the replicas and verifying we have 2 quorum-based replicas", func() {
 					Expect(fencing.Off(env.Ctx, env.Client, fmt.Sprintf("%v-3", clusterName),
diff --git a/tests/e2e/tablespaces_test.go b/tests/e2e/tablespaces_test.go
index 650270be..7a202db7 100644
--- a/tests/e2e/tablespaces_test.go
+++ b/tests/e2e/tablespaces_test.go
@@ -1029,7 +1029,7 @@ func AssertClusterHasPvcsAndDataDirsForTablespaces(cluster *apiv1.Cluster, timeo
 					)
 
 					targetContainer := getPostgresContainer(pod)
-					Expect(targetContainer).NotTo(BeNil())
+					g.Expect(targetContainer).NotTo(BeNil())
 					dbUser := getDatabasUserUID(cluster, targetContainer)
 
 					g.Expect(stdErr).To(BeEmpty())
diff --git a/tests/e2e/update_user_test.go b/tests/e2e/update_user_test.go
index 424e6adf..aec3dc1b 100644
--- a/tests/e2e/update_user_test.go
+++ b/tests/e2e/update_user_test.go
@@ -97,12 +97,12 @@ var _ = Describe("Update user and superuser password", Label(tests.LabelServiceC
 
 		By("update superuser password", func() {
 			// Setting EnableSuperuserAccess to true
-			Eventually(func() error {
+			Eventually(func(g Gomega) {
 				cluster, err := clusterutils.Get(env.Ctx, env.Client, namespace, clusterName)
-				Expect(err).NotTo(HaveOccurred())
+				g.Expect(err).NotTo(HaveOccurred())
 				cluster.Spec.EnableSuperuserAccess = ptr.To(true)
-				return env.Client.Update(env.Ctx, cluster)
-			}, 60, 5).Should(Not(HaveOccurred()))
+				g.Expect(env.Client.Update(env.Ctx, cluster)).To(Succeed())
+			}, 60, 5).Should(Succeed())
 
 			// We should now have a secret
 			var secret corev1.Secret
@@ -184,18 +184,18 @@ var _ = Describe("Enable superuser password", Label(tests.LabelServiceConnectivi
 
 		By("enable superuser access", func() {
 			// Setting EnableSuperuserAccess to true
-			Eventually(func() error {
+			Eventually(func(g Gomega) {
 				cluster, err := clusterutils.Get(env.Ctx, env.Client, namespace, clusterName)
-				Expect(err).NotTo(HaveOccurred())
+				g.Expect(err).NotTo(HaveOccurred())
 				cluster.Spec.EnableSuperuserAccess = ptr.To(true)
-				return env.Client.Update(env.Ctx, cluster)
-			}, 60, 5).Should(Not(HaveOccurred()))
+				g.Expect(env.Client.Update(env.Ctx, cluster)).To(Succeed())
+			}, 60, 5).Should(Succeed())
 
 			// We should now have a secret
 			Eventually(func(g Gomega) {
 				err = env.Client.Get(env.Ctx, namespacedName, &secret)
 				g.Expect(err).ToNot(HaveOccurred())
-			}, 90).WithPolling(time.Second).Should(Succeed())
+			}, 90).Should(Succeed())
 
 			superUser, superUserPass, err := secrets.GetCredentials(
 				env.Ctx, env.Client,
@@ -207,12 +207,12 @@ var _ = Describe("Enable superuser password", Label(tests.LabelServiceConnectivi
 
 		By("disable superuser access", func() {
 			// Setting EnableSuperuserAccess to false
-			Eventually(func() error {
+			Eventually(func(g Gomega) {
 				cluster, err := clusterutils.Get(env.Ctx, env.Client, namespace, clusterName)
-				Expect(err).NotTo(HaveOccurred())
+				g.Expect(err).NotTo(HaveOccurred())
 				cluster.Spec.EnableSuperuserAccess = ptr.To(false)
-				return env.Client.Update(env.Ctx, cluster)
-			}, 60, 5).Should(Not(HaveOccurred()))
+				g.Expect(env.Client.Update(env.Ctx, cluster)).To(Succeed())
+			}, 60, 5).Should(Succeed())
 
 			// We expect the secret to eventually be deleted
 			Eventually(func(g Gomega) {
diff --git a/tests/e2e/upgrade_test.go b/tests/e2e/upgrade_test.go
index aceaaa27..bebd6368 100644
--- a/tests/e2e/upgrade_test.go
+++ b/tests/e2e/upgrade_test.go
@@ -233,20 +233,20 @@ var _ = Describe("Upgrade", Label(tests.LabelUpgrade, tests.LabelNoOpenshift), O
 					"Pod %v should have updated its config", pod.Name)
 			}
 			// Check that a switchover happened
-			Eventually(func() (bool, error) {
+			Eventually(func(g Gomega) bool {
 				c, err := clusterutils.Get(env.Ctx, env.Client, upgradeNamespace, clusterName)
-				Expect(err).ToNot(HaveOccurred())
+				g.Expect(err).ToNot(HaveOccurred())
 
 				GinkgoWriter.Printf("Current Primary: %s, Current Primary timestamp: %s\n",
 					c.Status.CurrentPrimary, c.Status.CurrentPrimaryTimestamp)
 
 				if c.Status.CurrentPrimary != oldPrimary {
-					return true, nil
+					return true
 				} else if c.Status.CurrentPrimaryTimestamp != oldPrimaryTimestamp {
-					return true, nil
+					return true
 				}
 
-				return false, nil
+				return false
 			}, timeout, "1s").Should(BeTrue())
 		})