Skip to content

Commit e779eab

Browse files
sdudoladovSergey Dudoladov
andauthored
Update e2e pipeline (zalando#1202)
* clean up after test_multi_namespace test * see the PR description for complete list of changes Co-authored-by: Sergey Dudoladov <[email protected]>
1 parent b379db2 commit e779eab

File tree

4 files changed

+70
-42
lines changed

4 files changed

+70
-42
lines changed

e2e/README.md

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,24 @@ NOCLEANUP=True ./run.sh main tests.test_e2e.EndToEndTestCase.test_lazy_spilo_upg
5656

5757
## Inspecting Kind
5858

59-
If you want to inspect Kind/Kubernetes cluster, use the following script to exec into the K8s setup and then use `kubectl`
59+
If you want to inspect Kind/Kubernetes cluster, switch `kubeconfig` file and context
60+
```bash
61+
# save the old config in case you have it
62+
export KUBECONFIG_SAVED=$KUBECONFIG
63+
64+
# use the one created by e2e tests
65+
export KUBECONFIG=/tmp/kind-config-postgres-operator-e2e-tests
66+
67+
# this kubeconfig defines a single context
68+
kubectl config use-context kind-postgres-operator-e2e-tests
69+
```
70+
71+
or use the following script to exec into the K8s setup and then use `kubectl`
6072

6173
```bash
6274
./exec_into_env.sh
6375

64-
# use kube ctl
76+
# use kubectl
6577
kubectl get pods
6678

6779
# watch relevant objects
@@ -71,6 +83,14 @@ kubectl get pods
7183
./scripts/get_logs.sh
7284
```
7385

86+
If you want to inspect the state of the `kind` cluster manually with a single command, add a `context` flag
87+
```bash
88+
kubectl get pods --context kind-kind
89+
```
90+
or set the context for a few commands at once
91+
92+
93+
7494
## Cleaning up Kind
7595

7696
To cleanup kind and start fresh
@@ -79,6 +99,12 @@ To cleanup kind and start fresh
7999
e2e/run.sh cleanup
80100
```
81101

102+
That also helps in case you see the
103+
```
104+
ERROR: no nodes found for cluster "postgres-operator-e2e-tests"
105+
```
106+
that happens when the `kind` cluster was deleted manually but its configuraiton file was not.
107+
82108
## Covered use cases
83109

84110
The current tests are all bundled in [`test_e2e.py`](tests/test_e2e.py):

e2e/tests/k8s_api.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111
from kubernetes import client, config
1212
from kubernetes.client.rest import ApiException
1313

14+
1415
def to_selector(labels):
1516
return ",".join(["=".join(l) for l in labels.items()])
1617

18+
1719
class K8sApi:
1820

1921
def __init__(self):
@@ -181,10 +183,10 @@ def count_deployments_with_label(self, labels, namespace='default'):
181183
def count_pdbs_with_label(self, labels, namespace='default'):
182184
return len(self.api.policy_v1_beta1.list_namespaced_pod_disruption_budget(
183185
namespace, label_selector=labels).items)
184-
186+
185187
def count_running_pods(self, labels='application=spilo,cluster-name=acid-minimal-cluster', namespace='default'):
186188
pods = self.api.core_v1.list_namespaced_pod(namespace, label_selector=labels).items
187-
return len(list(filter(lambda x: x.status.phase=='Running', pods)))
189+
return len(list(filter(lambda x: x.status.phase == 'Running', pods)))
188190

189191
def wait_for_pod_failover(self, failover_targets, labels, namespace='default'):
190192
pod_phase = 'Failing over'
@@ -210,9 +212,9 @@ def wait_for_logical_backup_job_deletion(self):
210212
def wait_for_logical_backup_job_creation(self):
211213
self.wait_for_logical_backup_job(expected_num_of_jobs=1)
212214

213-
def delete_operator_pod(self, step="Delete operator deplyment"):
214-
operator_pod = self.api.core_v1.list_namespaced_pod('default', label_selector="name=postgres-operator").items[0].metadata.name
215-
self.api.apps_v1.patch_namespaced_deployment("postgres-operator","default", {"spec":{"template":{"metadata":{"annotations":{"step":"{}-{}".format(step, time.time())}}}}})
215+
def delete_operator_pod(self, step="Delete operator pod"):
216+
# patching the pod template in the deployment restarts the operator pod
217+
self.api.apps_v1.patch_namespaced_deployment("postgres-operator","default", {"spec":{"template":{"metadata":{"annotations":{"step":"{}-{}".format(step, datetime.fromtimestamp(time.time()))}}}}})
216218
self.wait_for_operator_pod_start()
217219

218220
def update_config(self, config_map_patch, step="Updating operator deployment"):
@@ -241,7 +243,7 @@ def get_patroni_state(self, pod):
241243

242244
def get_operator_state(self):
243245
pod = self.get_operator_pod()
244-
if pod == None:
246+
if pod is None:
245247
return None
246248
pod = pod.metadata.name
247249

@@ -251,7 +253,6 @@ def get_operator_state(self):
251253

252254
return json.loads(r.stdout.decode())
253255

254-
255256
def get_patroni_running_members(self, pod="acid-minimal-cluster-0"):
256257
result = self.get_patroni_state(pod)
257258
return list(filter(lambda x: "State" in x and x["State"] == "running", result))
@@ -260,9 +261,9 @@ def get_deployment_replica_count(self, name="acid-minimal-cluster-pooler", names
260261
try:
261262
deployment = self.api.apps_v1.read_namespaced_deployment(name, namespace)
262263
return deployment.spec.replicas
263-
except ApiException as e:
264+
except ApiException:
264265
return None
265-
266+
266267
def get_statefulset_image(self, label_selector="application=spilo,cluster-name=acid-minimal-cluster", namespace='default'):
267268
ssets = self.api.apps_v1.list_namespaced_stateful_set(namespace, label_selector=label_selector, limit=1)
268269
if len(ssets.items) == 0:
@@ -463,7 +464,6 @@ def wait_for_logical_backup_job_creation(self):
463464
self.wait_for_logical_backup_job(expected_num_of_jobs=1)
464465

465466
def delete_operator_pod(self, step="Delete operator deplyment"):
466-
operator_pod = self.api.core_v1.list_namespaced_pod('default', label_selector="name=postgres-operator").items[0].metadata.name
467467
self.api.apps_v1.patch_namespaced_deployment("postgres-operator","default", {"spec":{"template":{"metadata":{"annotations":{"step":"{}-{}".format(step, time.time())}}}}})
468468
self.wait_for_operator_pod_start()
469469

@@ -521,7 +521,7 @@ def __init__(self, labels="name=postgres-operator", namespace="default"):
521521
class K8sPostgres(K8sBase):
522522
def __init__(self, labels="cluster-name=acid-minimal-cluster", namespace="default"):
523523
super().__init__(labels, namespace)
524-
524+
525525
def get_pg_nodes(self):
526526
master_pod_node = ''
527527
replica_pod_nodes = []
@@ -532,4 +532,4 @@ def get_pg_nodes(self):
532532
elif pod.metadata.labels.get('spilo-role') == 'replica':
533533
replica_pod_nodes.append(pod.spec.node_name)
534534

535-
return master_pod_node, replica_pod_nodes
535+
return master_pod_node, replica_pod_nodes

e2e/tests/test_e2e.py

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,14 @@
22
import unittest
33
import time
44
import timeout_decorator
5-
import subprocess
6-
import warnings
75
import os
86
import yaml
97

108
from datetime import datetime
11-
from kubernetes import client, config
9+
from kubernetes import client
1210

1311
from tests.k8s_api import K8s
12+
from kubernetes.client.rest import ApiException
1413

1514
SPILO_CURRENT = "registry.opensource.zalan.do/acid/spilo-12:1.6-p5"
1615
SPILO_LAZY = "registry.opensource.zalan.do/acid/spilo-cdp-12:1.6-p114"
@@ -89,17 +88,17 @@ def setUpClass(cls):
8988
# remove existing local storage class and create hostpath class
9089
try:
9190
k8s.api.storage_v1_api.delete_storage_class("standard")
92-
except:
93-
print("Storage class has already been remove")
91+
except ApiException as e:
92+
print("Failed to delete the 'standard' storage class: {0}".format(e))
9493

9594
# operator deploys pod service account there on start up
9695
# needed for test_multi_namespace_support()
97-
cls.namespace = "test"
96+
cls.test_namespace = "test"
9897
try:
99-
v1_namespace = client.V1Namespace(metadata=client.V1ObjectMeta(name=cls.namespace))
98+
v1_namespace = client.V1Namespace(metadata=client.V1ObjectMeta(name=cls.test_namespace))
10099
k8s.api.core_v1.create_namespace(v1_namespace)
101-
except:
102-
print("Namespace already present")
100+
except ApiException as e:
101+
print("Failed to create the '{0}' namespace: {1}".format(cls.test_namespace, e))
103102

104103
# submit the most recent operator image built on the Docker host
105104
with open("manifests/postgres-operator.yaml", 'r+') as f:
@@ -135,10 +134,8 @@ def setUpClass(cls):
135134

136135
# make sure we start a new operator on every new run,
137136
# this tackles the problem when kind is reused
138-
# and the Docker image is infact changed (dirty one)
137+
# and the Docker image is in fact changed (dirty one)
139138

140-
# patch resync period, this can catch some problems with hanging e2e tests
141-
# k8s.update_config({"data": {"resync_period":"30s"}},step="TestSuite setup")
142139
k8s.update_config({}, step="TestSuite Startup")
143140

144141
actual_operator_image = k8s.api.core_v1.list_namespaced_pod(
@@ -170,9 +167,6 @@ def test_enable_disable_connection_pooler(self):
170167
'connection-pooler': 'acid-minimal-cluster-pooler',
171168
})
172169

173-
pod_selector = to_selector(pod_labels)
174-
service_selector = to_selector(service_labels)
175-
176170
# enable connection pooler
177171
k8s.api.custom_objects_api.patch_namespaced_custom_object(
178172
'acid.zalan.do', 'v1', 'default',
@@ -347,7 +341,7 @@ def test_infrastructure_roles(self):
347341
},
348342
}
349343
k8s.update_config(patch_infrastructure_roles)
350-
self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0":"idle"}, "Operator does not get in sync")
344+
self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0": "idle"}, "Operator does not get in sync")
351345

352346
try:
353347
# check that new roles are represented in the config by requesting the
@@ -604,17 +598,25 @@ def test_multi_namespace_support(self):
604598

605599
with open("manifests/complete-postgres-manifest.yaml", 'r+') as f:
606600
pg_manifest = yaml.safe_load(f)
607-
pg_manifest["metadata"]["namespace"] = self.namespace
601+
pg_manifest["metadata"]["namespace"] = self.test_namespace
608602
yaml.dump(pg_manifest, f, Dumper=yaml.Dumper)
609603

610604
try:
611605
k8s.create_with_kubectl("manifests/complete-postgres-manifest.yaml")
612-
k8s.wait_for_pod_start("spilo-role=master", self.namespace)
613-
self.assert_master_is_unique(self.namespace, "acid-test-cluster")
606+
k8s.wait_for_pod_start("spilo-role=master", self.test_namespace)
607+
self.assert_master_is_unique(self.test_namespace, "acid-test-cluster")
614608

615609
except timeout_decorator.TimeoutError:
616610
print('Operator log: {}'.format(k8s.get_operator_log()))
617611
raise
612+
finally:
613+
# delete the new cluster so that the k8s_api.get_operator_state works correctly in subsequent tests
614+
# ideally we should delete the 'test' namespace here but
615+
# the pods inside the namespace stuck in the Terminating state making the test time out
616+
k8s.api.custom_objects_api.delete_namespaced_custom_object(
617+
"acid.zalan.do", "v1", self.test_namespace, "postgresqls", "acid-test-cluster")
618+
time.sleep(5)
619+
618620

619621
@timeout_decorator.timeout(TEST_TIMEOUT_SEC)
620622
def test_zz_node_readiness_label(self):
@@ -746,12 +748,12 @@ def test_statefulset_annotation_propagation(self):
746748
}
747749
k8s.api.custom_objects_api.patch_namespaced_custom_object(
748750
"acid.zalan.do", "v1", "default", "postgresqls", "acid-minimal-cluster", pg_crd_annotations)
749-
751+
750752
annotations = {
751753
"deployment-time": "2020-04-30 12:00:00",
752754
"downscaler/downtime_replicas": "0",
753755
}
754-
756+
755757
self.eventuallyTrue(lambda: k8s.check_statefulset_annotations(cluster_label, annotations), "Annotations missing")
756758

757759

@@ -823,14 +825,14 @@ def test_zzzz_cluster_deletion(self):
823825
}
824826
}
825827
k8s.update_config(patch_delete_annotations)
826-
self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0":"idle"}, "Operator does not get in sync")
828+
self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0": "idle"}, "Operator does not get in sync")
827829

828830
try:
829831
# this delete attempt should be omitted because of missing annotations
830832
k8s.api.custom_objects_api.delete_namespaced_custom_object(
831833
"acid.zalan.do", "v1", "default", "postgresqls", "acid-minimal-cluster")
832834
time.sleep(5)
833-
self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0":"idle"}, "Operator does not get in sync")
835+
self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0": "idle"}, "Operator does not get in sync")
834836

835837
# check that pods and services are still there
836838
k8s.wait_for_running_pods(cluster_label, 2)
@@ -841,7 +843,7 @@ def test_zzzz_cluster_deletion(self):
841843

842844
# wait a little before proceeding
843845
time.sleep(10)
844-
self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0":"idle"}, "Operator does not get in sync")
846+
self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0": "idle"}, "Operator does not get in sync")
845847

846848
# add annotations to manifest
847849
delete_date = datetime.today().strftime('%Y-%m-%d')
@@ -855,7 +857,7 @@ def test_zzzz_cluster_deletion(self):
855857
}
856858
k8s.api.custom_objects_api.patch_namespaced_custom_object(
857859
"acid.zalan.do", "v1", "default", "postgresqls", "acid-minimal-cluster", pg_patch_delete_annotations)
858-
self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0":"idle"}, "Operator does not get in sync")
860+
self.eventuallyEqual(lambda: k8s.get_operator_state(), {"0": "idle"}, "Operator does not get in sync")
859861

860862
# wait a little before proceeding
861863
time.sleep(20)
@@ -882,7 +884,7 @@ def test_zzzz_cluster_deletion(self):
882884
print('Operator log: {}'.format(k8s.get_operator_log()))
883885
raise
884886

885-
#reset configmap
887+
# reset configmap
886888
patch_delete_annotations = {
887889
"data": {
888890
"delete_annotation_date_key": "",

pkg/cluster/cluster.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -626,14 +626,14 @@ func (c *Cluster) Update(oldSpec, newSpec *acidv1.Postgresql) error {
626626
}
627627
}()
628628

629-
if oldSpec.Spec.PostgresqlParam.PgVersion >= newSpec.Spec.PostgresqlParam.PgVersion {
629+
if oldSpec.Spec.PostgresqlParam.PgVersion > newSpec.Spec.PostgresqlParam.PgVersion {
630630
c.logger.Warningf("postgresql version change(%q -> %q) has no effect",
631631
oldSpec.Spec.PostgresqlParam.PgVersion, newSpec.Spec.PostgresqlParam.PgVersion)
632632
c.eventRecorder.Eventf(c.GetReference(), v1.EventTypeWarning, "PostgreSQL", "postgresql version change(%q -> %q) has no effect",
633633
oldSpec.Spec.PostgresqlParam.PgVersion, newSpec.Spec.PostgresqlParam.PgVersion)
634634
// we need that hack to generate statefulset with the old version
635635
newSpec.Spec.PostgresqlParam.PgVersion = oldSpec.Spec.PostgresqlParam.PgVersion
636-
} else {
636+
} else if oldSpec.Spec.PostgresqlParam.PgVersion < newSpec.Spec.PostgresqlParam.PgVersion {
637637
c.logger.Infof("postgresql version increased (%q -> %q), major version upgrade can be done manually after StatefulSet Sync",
638638
oldSpec.Spec.PostgresqlParam.PgVersion, newSpec.Spec.PostgresqlParam.PgVersion)
639639
}

0 commit comments

Comments
 (0)