Skip to content

Commit 327d491

Browse files
[DPE-6345] LDAP IV: Define snap service (#838)
1 parent 13b8279 commit 327d491

File tree

4 files changed

+130
-21
lines changed

4 files changed

+130
-21
lines changed

src/charm.py

Lines changed: 75 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from datetime import datetime
1717
from pathlib import Path
1818
from typing import Literal, get_args
19+
from urllib.parse import urlparse
1920

2021
import psycopg2
2122
from charms.data_platform_libs.v0.data_interfaces import DataPeerData, DataPeerUnitData
@@ -73,6 +74,7 @@
7374
APP_SCOPE,
7475
BACKUP_USER,
7576
DATABASE_DEFAULT_NAME,
77+
DATABASE_PORT,
7678
METRICS_PORT,
7779
MONITORING_PASSWORD_KEY,
7880
MONITORING_SNAP_SERVICE,
@@ -110,7 +112,7 @@
110112
from relations.postgresql_provider import PostgreSQLProvider
111113
from rotate_logs import RotateLogs
112114
from upgrade import PostgreSQLUpgrade, get_postgresql_dependencies_model
113-
from utils import new_password
115+
from utils import new_password, snap_refreshed
114116

115117
logger = logging.getLogger(__name__)
116118

@@ -1316,29 +1318,86 @@ def _restart_services_after_reboot(self):
13161318
self._patroni.start_patroni()
13171319
self.backup.start_stop_pgbackrest_service()
13181320

1319-
def _setup_exporter(self) -> None:
1320-
"""Set up postgresql_exporter options."""
1321-
cache = snap.SnapCache()
1322-
postgres_snap = cache[POSTGRESQL_SNAP_NAME]
1321+
def _restart_metrics_service(self, postgres_snap: snap.Snap) -> None:
1322+
"""Restart the monitoring service if the password was rotated."""
1323+
try:
1324+
snap_password = postgres_snap.get("exporter.password")
1325+
except snap.SnapError:
1326+
logger.warning("Early exit: Trying to reset metrics service with no configuration set")
1327+
return None
13231328

1324-
if postgres_snap.revision != next(
1325-
filter(lambda snap_package: snap_package[0] == POSTGRESQL_SNAP_NAME, SNAP_PACKAGES)
1326-
)[1]["revision"].get(platform.machine()):
1327-
logger.debug(
1328-
"Early exit _setup_exporter: snap was not refreshed to the right version yet"
1329-
)
1329+
if snap_password != self.get_secret(APP_SCOPE, MONITORING_PASSWORD_KEY):
1330+
self._setup_exporter(postgres_snap)
1331+
1332+
def _restart_ldap_sync_service(self, postgres_snap: snap.Snap) -> None:
1333+
"""Restart the LDAP sync service in case any configuration changed."""
1334+
if not self._patroni.member_started:
1335+
logger.debug("Restart LDAP sync early exit: Patroni has not started yet")
13301336
return
13311337

1338+
sync_service = postgres_snap.services["ldap-sync"]
1339+
1340+
if not self.is_primary and sync_service["active"]:
1341+
logger.debug("Stopping LDAP sync service. It must only run in the primary")
1342+
postgres_snap.stop(services=["ldap-sync"])
1343+
1344+
if self.is_primary and not self.is_ldap_enabled:
1345+
logger.debug("Stopping LDAP sync service")
1346+
postgres_snap.stop(services=["ldap-sync"])
1347+
return
1348+
1349+
if self.is_primary and self.is_ldap_enabled:
1350+
self._setup_ldap_sync(postgres_snap)
1351+
1352+
def _setup_exporter(self, postgres_snap: snap.Snap | None = None) -> None:
1353+
"""Set up postgresql_exporter options."""
1354+
if postgres_snap is None:
1355+
cache = snap.SnapCache()
1356+
postgres_snap = cache[POSTGRESQL_SNAP_NAME]
1357+
13321358
postgres_snap.set({
13331359
"exporter.user": MONITORING_USER,
13341360
"exporter.password": self.get_secret(APP_SCOPE, MONITORING_PASSWORD_KEY),
13351361
})
1362+
13361363
if postgres_snap.services[MONITORING_SNAP_SERVICE]["active"] is False:
13371364
postgres_snap.start(services=[MONITORING_SNAP_SERVICE], enable=True)
13381365
else:
13391366
postgres_snap.restart(services=[MONITORING_SNAP_SERVICE])
1367+
13401368
self.unit_peer_data.update({"exporter-started": "True"})
13411369

1370+
def _setup_ldap_sync(self, postgres_snap: snap.Snap | None = None) -> None:
1371+
"""Set up postgresql_ldap_sync options."""
1372+
if postgres_snap is None:
1373+
cache = snap.SnapCache()
1374+
postgres_snap = cache[POSTGRESQL_SNAP_NAME]
1375+
1376+
ldap_params = self.get_ldap_parameters()
1377+
ldap_url = urlparse(ldap_params["ldapurl"])
1378+
ldap_host = ldap_url.hostname
1379+
ldap_port = ldap_url.port
1380+
1381+
ldap_base_dn = ldap_params["ldapbasedn"]
1382+
ldap_bind_username = ldap_params["ldapbinddn"]
1383+
ldap_bind_password = ldap_params["ldapbindpasswd"]
1384+
1385+
postgres_snap.set({
1386+
"ldap-sync.ldap_host": ldap_host,
1387+
"ldap-sync.ldap_port": ldap_port,
1388+
"ldap-sync.ldap_base_dn": ldap_base_dn,
1389+
"ldap-sync.ldap_bind_username": ldap_bind_username,
1390+
"ldap-sync.ldap_bind_password": ldap_bind_password,
1391+
"ldap-sync.postgres_host": "127.0.0.1",
1392+
"ldap-sync.postgres_port": DATABASE_PORT,
1393+
"ldap-sync.postgres_database": DATABASE_DEFAULT_NAME,
1394+
"ldap-sync.postgres_username": USER,
1395+
"ldap-sync.postgres_password": self._get_password(),
1396+
})
1397+
1398+
logger.debug("Starting LDAP sync service")
1399+
postgres_snap.restart(services=["ldap-sync"])
1400+
13421401
def _start_primary(self, event: StartEvent) -> None:
13431402
"""Bootstrap the cluster."""
13441403
# Set some information needed by Patroni to bootstrap the cluster.
@@ -1986,19 +2045,15 @@ def update_config(self, is_creating_backup: bool = False, no_peers: bool = False
19862045

19872046
self._handle_postgresql_restart_need(enable_tls)
19882047

1989-
# Restart the monitoring service if the password was rotated
19902048
cache = snap.SnapCache()
19912049
postgres_snap = cache[POSTGRESQL_SNAP_NAME]
19922050

1993-
try:
1994-
snap_password = postgres_snap.get("exporter.password")
1995-
except snap.SnapError:
1996-
logger.warning(
1997-
"Early exit update_config: Trying to reset metrics service with no configuration set"
1998-
)
2051+
if not snap_refreshed(postgres_snap.revision):
2052+
logger.debug("Early exit: snap was not refreshed to the right version yet")
19992053
return True
2000-
if snap_password != self.get_secret(APP_SCOPE, MONITORING_PASSWORD_KEY):
2001-
self._setup_exporter()
2054+
2055+
self._restart_metrics_service(postgres_snap)
2056+
self._restart_ldap_sync_service(postgres_snap)
20022057

20032058
return True
20042059

src/utils.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,15 @@
33

44
"""A collection of utility functions that are used in the charm."""
55

6+
import platform
67
import secrets
78
import string
89

10+
from constants import (
11+
POSTGRESQL_SNAP_NAME,
12+
SNAP_PACKAGES,
13+
)
14+
915

1016
def new_password() -> str:
1117
"""Generate a random password string.
@@ -16,3 +22,16 @@ def new_password() -> str:
1622
choices = string.ascii_letters + string.digits
1723
password = "".join([secrets.choice(choices) for i in range(16)])
1824
return password
25+
26+
27+
def snap_refreshed(target_rev: str) -> bool:
28+
"""Whether the snap was refreshed to the target version."""
29+
arch = platform.machine()
30+
31+
for snap_package in SNAP_PACKAGES:
32+
snap_name = snap_package[0]
33+
snap_revs = snap_package[1]["revision"]
34+
if snap_name == POSTGRESQL_SNAP_NAME and target_rev != snap_revs.get(arch):
35+
return False
36+
37+
return True

tests/unit/test_charm.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1270,10 +1270,17 @@ def test_restart(harness):
12701270
def test_update_config(harness):
12711271
with (
12721272
patch("subprocess.check_output", return_value=b"C"),
1273+
patch("charm.snap_refreshed", return_value=True),
12731274
patch("charm.snap.SnapCache"),
12741275
patch(
12751276
"charm.PostgresqlOperatorCharm._handle_postgresql_restart_need"
12761277
) as _handle_postgresql_restart_need,
1278+
patch(
1279+
"charm.PostgresqlOperatorCharm._restart_metrics_service"
1280+
) as _restart_metrics_service,
1281+
patch(
1282+
"charm.PostgresqlOperatorCharm._restart_ldap_sync_service"
1283+
) as _restart_ldap_sync_service,
12771284
patch("charm.Patroni.bulk_update_parameters_controller_by_patroni"),
12781285
patch("charm.Patroni.member_started", new_callable=PropertyMock) as _member_started,
12791286
patch(
@@ -1313,10 +1320,14 @@ def test_update_config(harness):
13131320
no_peers=False,
13141321
)
13151322
_handle_postgresql_restart_need.assert_called_once_with(False)
1323+
_restart_ldap_sync_service.assert_called_once()
1324+
_restart_metrics_service.assert_called_once()
13161325
assert "tls" not in harness.get_relation_data(rel_id, harness.charm.unit.name)
13171326

13181327
# Test with TLS files available.
13191328
_handle_postgresql_restart_need.reset_mock()
1329+
_restart_ldap_sync_service.reset_mock()
1330+
_restart_metrics_service.reset_mock()
13201331
harness.update_relation_data(
13211332
rel_id, harness.charm.unit.name, {"tls": ""}
13221333
) # Mock some data in the relation to test that it change.
@@ -1338,6 +1349,8 @@ def test_update_config(harness):
13381349
no_peers=False,
13391350
)
13401351
_handle_postgresql_restart_need.assert_called_once()
1352+
_restart_ldap_sync_service.assert_called_once()
1353+
_restart_metrics_service.assert_called_once()
13411354
assert "tls" not in harness.get_relation_data(
13421355
rel_id, harness.charm.unit.name
13431356
) # The "tls" flag is set in handle_postgresql_restart_need.
@@ -1347,6 +1360,8 @@ def test_update_config(harness):
13471360
rel_id, harness.charm.unit.name, {"tls": ""}
13481361
) # Mock some data in the relation to test that it change.
13491362
_handle_postgresql_restart_need.reset_mock()
1363+
_restart_ldap_sync_service.reset_mock()
1364+
_restart_metrics_service.reset_mock()
13501365
harness.charm.update_config()
13511366
_handle_postgresql_restart_need.assert_not_called()
13521367
assert harness.get_relation_data(rel_id, harness.charm.unit.name)["tls"] == "enabled"
@@ -1357,6 +1372,8 @@ def test_update_config(harness):
13571372
) # Mock some data in the relation to test that it doesn't change.
13581373
harness.charm.update_config()
13591374
_handle_postgresql_restart_need.assert_not_called()
1375+
_restart_ldap_sync_service.assert_not_called()
1376+
_restart_metrics_service.assert_not_called()
13601377
assert "tls" not in harness.get_relation_data(rel_id, harness.charm.unit.name)
13611378

13621379

tests/unit/test_utils.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
# See LICENSE file for licensing details.
33

44
import re
5+
from unittest.mock import patch
56

6-
from utils import new_password
7+
from constants import POSTGRESQL_SNAP_NAME
8+
from utils import new_password, snap_refreshed
79

810

911
def test_new_password():
@@ -16,3 +18,19 @@ def test_new_password():
1618
second_password = new_password()
1719
assert re.fullmatch("[a-zA-Z0-9\b]{16}$", second_password) is not None
1820
assert second_password != first_password
21+
22+
23+
def test_snap_refreshed():
24+
with patch(
25+
"utils.SNAP_PACKAGES",
26+
[(POSTGRESQL_SNAP_NAME, {"revision": {"aarch64": "100", "x86_64": "100"}})],
27+
):
28+
assert snap_refreshed("100") is True
29+
assert snap_refreshed("200") is False
30+
31+
with patch(
32+
"utils.SNAP_PACKAGES",
33+
[(POSTGRESQL_SNAP_NAME, {"revision": {}})],
34+
):
35+
assert snap_refreshed("100") is False
36+
assert snap_refreshed("200") is False

0 commit comments

Comments
 (0)