Skip to content

Commit 85dfbb4

Browse files
authored
[DPE-10062] Unit tests and tweaks (#176)
* Redo * Conditional password * Port changes WIP * Comment out raft logic * Attr loop * Don't pull psycopg2 * None lists and touching wrong path * Wrong bootstrap paths * Versioned volume paths * Fix unit tests * Wait for switchover (K8s) * Snaplib conditional import * K8s paths * Move exceptions * Don't pull client class in managers * Spaces ips * Exception * Running members diff * Ensure slots retval * Workload status check * Lazy load container * Reduce update sync nodes retry timeout * Revert container change * Reduce timeout * [DPE-10062] Single kernel changes * [DPE-10062] Single kernel changes * K8s passes unit names * Dead code * Unit tests and tweaks * VM tests * K8s test
1 parent 3277dbf commit 85dfbb4

5 files changed

Lines changed: 1187 additions & 4 deletions

File tree

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
[project]
55
name = "postgresql-charms-single-kernel"
66
description = "Shared and reusable code for PostgreSQL-related charms"
7-
version = "16.3.1"
7+
version = "16.3.2"
88
readme = "README.md"
99
license = {file = "LICENSE"}
1010
authors = [
@@ -38,6 +38,7 @@ vm = [
3838
"pysyncobj>=0.3.15; python_version >= '3.12'",
3939
"psutil>=7.2.2; python_version >= '3.12'",
4040
"charmlibs-snap>=1.0.1; python_version >= '3.12'",
41+
"charmlibs-systemd>=1.0.0.post0; python_version >= '3.12'"
4142
]
4243
db-driver = [
4344
"psycopg2>=2.9.10",

single_kernel_postgresql/managers/patroni.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@
1212
import logging
1313
import os
1414
import re
15-
import subprocess
1615
from contextlib import suppress
1716
from functools import cached_property
1817
from typing import Any, Literal, TypedDict
1918

2019
# Platform specific imports
2120
with suppress(ImportError):
2221
from charmlibs import snap
22+
from charmlibs.systemd import daemon_reload
2323
import psycopg2
2424
import psycopg2.extras
2525
import requests
@@ -412,6 +412,8 @@ def member_inactive(self) -> bool:
412412
True if services is not running, starting or restarting. Retries over a period of 60
413413
seconds times to allow server time to start up.
414414
"""
415+
if not self.workload.is_patroni_running():
416+
return True
415417
try:
416418
response = self.cached_patroni_health
417419
except RetryError:
@@ -828,7 +830,7 @@ def update_patroni_restart_condition(self, new_condition: str) -> None:
828830
logger.debug(f"new patroni service file: {new_patroni_service}")
829831
with open(VM_PATRONI_SERVICE_DEFAULT_PATH, "w") as patroni_service_file:
830832
patroni_service_file.write(new_patroni_service)
831-
subprocess.run(["/bin/systemctl", "daemon-reload"])
833+
daemon_reload()
832834

833835
@property
834836
def primary_endpoint_ready(self) -> bool:

tests/unit/test_config_manager.py

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
# Copyright 2021 Canonical Ltd.
2+
# See LICENSE file for licensing details.
3+
from unittest.mock import Mock, PropertyMock, mock_open, patch, sentinel
4+
5+
import pytest
6+
from single_kernel_postgresql.config.enums import Substrates
7+
from single_kernel_postgresql.core.state import CharmState
8+
from single_kernel_postgresql.managers.config import ConfigManager
9+
from single_kernel_postgresql.workload.k8s import K8sWorkload
10+
from single_kernel_postgresql.workload.vm import VMWorkload
11+
12+
13+
@pytest.fixture(autouse=True)
14+
def config(substrate):
15+
mock_charm = Mock()
16+
mock_container = Mock()
17+
workload = VMWorkload(".") if substrate == Substrates.VM else K8sWorkload(".", mock_container)
18+
with patch(
19+
"single_kernel_postgresql.workload.base.BaseWorkload.get_postgresql_version",
20+
return_value="16.6",
21+
):
22+
config = ConfigManager(
23+
state=CharmState(charm=mock_charm, substrate=substrate), workload=workload
24+
)
25+
yield config
26+
27+
28+
def test_dict_to_hba_string(config):
29+
mock_data = {
30+
"ldapbasedn": "dc=example,dc=net",
31+
"ldapbinddn": "cn=serviceuser,dc=example,dc=net",
32+
"ldapbindpasswd": "password",
33+
"ldaptls": False,
34+
"ldapurl": "ldap://0.0.0.0:3893",
35+
}
36+
37+
assert config._dict_to_hba_string(mock_data) == (
38+
'ldapbasedn="dc=example,dc=net" '
39+
'ldapbinddn="cn=serviceuser,dc=example,dc=net" '
40+
'ldapbindpasswd="password" '
41+
"ldaptls=0 "
42+
'ldapurl="ldap://0.0.0.0:3893"'
43+
)
44+
45+
46+
def test_render_patroni_yml_file(substrate, config):
47+
with (
48+
patch(
49+
"single_kernel_postgresql.workload.base.BaseWorkload.get_postgresql_version",
50+
return_value="16.6",
51+
),
52+
patch("single_kernel_postgresql.managers.config.render_file") as _render_file,
53+
patch("single_kernel_postgresql.managers.config.Template") as _template,
54+
patch(
55+
"single_kernel_postgresql.core.state.CharmState.config", new_callable=PropertyMock
56+
) as _config,
57+
patch(
58+
"single_kernel_postgresql.core.state.CharmState.endpoint",
59+
new_callable=PropertyMock,
60+
return_value=sentinel.endpoint,
61+
),
62+
patch(
63+
"single_kernel_postgresql.core.state.CharmState.model_name",
64+
new_callable=PropertyMock,
65+
return_value=sentinel.model_name,
66+
),
67+
patch(
68+
"single_kernel_postgresql.core.state.CharmState.listen_ips",
69+
new_callable=PropertyMock,
70+
return_value=sentinel.listen_ips,
71+
),
72+
patch(
73+
"single_kernel_postgresql.core.state.CharmState.unit_ip",
74+
new_callable=PropertyMock,
75+
return_value=sentinel.unit_ip,
76+
),
77+
patch(
78+
"single_kernel_postgresql.core.peer_relation.PostgreSQLApplication.patroni_password",
79+
new_callable=PropertyMock,
80+
return_value=sentinel.patroni_pass,
81+
),
82+
patch(
83+
"single_kernel_postgresql.core.peer_relation.PostgreSQLApplication.user_password",
84+
new_callable=PropertyMock,
85+
return_value=sentinel.user_pass,
86+
),
87+
patch(
88+
"single_kernel_postgresql.core.peer_relation.PostgreSQLApplication.replication_password",
89+
new_callable=PropertyMock,
90+
return_value=sentinel.replication_pass,
91+
),
92+
patch(
93+
"single_kernel_postgresql.core.peer_relation.PostgreSQLApplication.rewind_password",
94+
new_callable=PropertyMock,
95+
return_value=sentinel.rewind_pass,
96+
),
97+
patch(
98+
"single_kernel_postgresql.core.peer_relation.PostgreSQLApplication.raft_password",
99+
new_callable=PropertyMock,
100+
return_value=sentinel.raft_pass,
101+
),
102+
patch(
103+
"single_kernel_postgresql.core.peer_relation.PostgreSQLApplication.planned_units",
104+
new_callable=PropertyMock,
105+
return_value=1,
106+
),
107+
patch(
108+
"single_kernel_postgresql.core.peer_relation.PostgreSQLApplication.cluster_name",
109+
new_callable=PropertyMock,
110+
return_value=sentinel.cluster_name,
111+
),
112+
patch(
113+
"single_kernel_postgresql.core.peer_relation.PostgreSQLApplication.endpoints",
114+
new_callable=PropertyMock,
115+
return_value=["endpoint1", "endpoint2", "endpoint3"],
116+
),
117+
patch(
118+
"single_kernel_postgresql.core.peer_relation.PostgreSQLApplication.members_ips",
119+
new_callable=PropertyMock,
120+
return_value=["ip1", "ip2", "ip3"],
121+
),
122+
patch(
123+
"single_kernel_postgresql.core.peer_relation.PostgreSQLPeer.member_name",
124+
new_callable=PropertyMock,
125+
return_value=sentinel.member_name,
126+
),
127+
patch("builtins.open", mock_open(read_data="template")),
128+
):
129+
_config.return_value.synchronous_node_count = 1
130+
_config.return_value.durability_maximum_lag_on_failover = (
131+
sentinel.durability_maximum_lag_on_failover
132+
)
133+
_config.return_value.instance_password_encryption = sentinel.instance_password_encryption
134+
_template.return_value.render.return_value = sentinel.template_output
135+
136+
config.render_patroni_yml_file()
137+
138+
_template.assert_called_once_with("template")
139+
if substrate == Substrates.K8S:
140+
_template.return_value.render.assert_called_once_with(
141+
connectivity=False,
142+
enable_ldap=False,
143+
enable_tls=False,
144+
member_name=sentinel.member_name,
145+
superuser="operator",
146+
superuser_password=sentinel.user_pass,
147+
rewind_user="rewind",
148+
rewind_password=sentinel.rewind_pass,
149+
replication_password=sentinel.replication_pass,
150+
enable_pgbackrest_archiving=False,
151+
stanza=None,
152+
restore_stanza=None,
153+
restoring_backup=False,
154+
backup_id=None,
155+
pitr_target=None,
156+
restore_timeline=None,
157+
restore_to_latest=False,
158+
is_creating_backup=False,
159+
version="16",
160+
synchronous_node_count=0,
161+
maximum_lag_on_failover=sentinel.durability_maximum_lag_on_failover,
162+
pg_parameters=None,
163+
primary_cluster_endpoint=None,
164+
ldap_parameters="",
165+
patroni_password=sentinel.patroni_pass,
166+
user_databases_map=None,
167+
slots={},
168+
instance_password_encryption=sentinel.instance_password_encryption,
169+
extra_replication_endpoints=[],
170+
endpoint=sentinel.endpoint,
171+
endpoints=["endpoint1", "endpoint2", "endpoint3"],
172+
is_no_sync_member=False,
173+
namespace=sentinel.model_name,
174+
storage_path="/var/lib/pg/data",
175+
logs_storage_path="/var/lib/pg/logs",
176+
pgdata_path="/var/lib/pg/data/16/main",
177+
)
178+
_render_file.assert_called_once_with(
179+
substrate, "/var/lib/pg/data/patroni.yaml", sentinel.template_output, 0o644
180+
)
181+
else:
182+
_template.return_value.render.assert_called_once_with(
183+
connectivity=False,
184+
enable_ldap=False,
185+
enable_tls=False,
186+
member_name=sentinel.member_name,
187+
superuser="operator",
188+
superuser_password=sentinel.user_pass,
189+
rewind_user="rewind",
190+
rewind_password=sentinel.rewind_pass,
191+
replication_password=sentinel.replication_pass,
192+
enable_pgbackrest_archiving=False,
193+
stanza=None,
194+
restore_stanza=None,
195+
restoring_backup=False,
196+
backup_id=None,
197+
pitr_target=None,
198+
restore_timeline=None,
199+
restore_to_latest=False,
200+
is_creating_backup=False,
201+
version="16",
202+
synchronous_node_count=0,
203+
maximum_lag_on_failover=sentinel.durability_maximum_lag_on_failover,
204+
pg_parameters=None,
205+
primary_cluster_endpoint=None,
206+
ldap_parameters="",
207+
patroni_password=sentinel.patroni_pass,
208+
user_databases_map=None,
209+
slots={},
210+
instance_password_encryption=sentinel.instance_password_encryption,
211+
extra_replication_endpoints=[],
212+
conf_path="/var/snap/charmed-postgresql/current/etc/patroni",
213+
log_path="/var/snap/charmed-postgresql/common/var/log/patroni",
214+
postgresql_log_path="/var/snap/charmed-postgresql/common/var/log/postgresql",
215+
data_path="/var/snap/charmed-postgresql/common/var/lib/postgresql/16/main",
216+
wal_dir="/var/snap/charmed-postgresql/common/data/logs/16/main",
217+
partner_addrs=[],
218+
peers_ips=["ip1", "ip2", "ip3"],
219+
pgbackrest_configuration_file="--config=/var/snap/charmed-postgresql/current/etc/pgbackrest/pgbackrest.conf",
220+
scope=sentinel.cluster_name,
221+
self_ip=sentinel.unit_ip,
222+
listen_ips=sentinel.listen_ips,
223+
raft_password=sentinel.raft_pass,
224+
watcher=None,
225+
)
226+
_render_file.assert_called_once_with(
227+
substrate,
228+
"/var/snap/charmed-postgresql/current/etc/patroni/patroni.yaml",
229+
sentinel.template_output,
230+
0o600,
231+
)

0 commit comments

Comments
 (0)