Skip to content

Commit 91ec95e

Browse files
[DPE-6344] LDAP IV: Define pebble service (#897)
1 parent 7083f18 commit 91ec95e

File tree

4 files changed

+144
-42
lines changed

4 files changed

+144
-42
lines changed

src/backups.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -989,7 +989,7 @@ def _on_restore_action(self, event): # noqa: C901
989989
# Stop the database service before performing the restore.
990990
logger.info("Stopping database service")
991991
try:
992-
self.container.stop(self.charm._postgresql_service)
992+
self.container.stop(self.charm.postgresql_service)
993993
except ChangeError as e:
994994
error_message = f"Failed to stop database service with error: {e!s}"
995995
logger.error(f"Restore failed: {error_message}")
@@ -1047,7 +1047,7 @@ def _on_restore_action(self, event): # noqa: C901
10471047

10481048
# Start the database to start the restore process.
10491049
logger.info("Configuring Patroni to restore the backup")
1050-
self.container.start(self.charm._postgresql_service)
1050+
self.container.start(self.charm.postgresql_service)
10511051

10521052
event.set_results({"restore-status": "restore started"})
10531053

@@ -1221,7 +1221,7 @@ def _restart_database(self) -> None:
12211221
"""Removes the restoring backup flag and restart the database."""
12221222
self.charm.app_peer_data.update({"restoring-backup": "", "restore-to-time": ""})
12231223
self.charm.update_config()
1224-
self.container.start(self.charm._postgresql_service)
1224+
self.container.start(self.charm.postgresql_service)
12251225

12261226
def _retrieve_s3_parameters(self) -> tuple[dict, list[str]]:
12271227
"""Retrieve S3 parameters from the S3 integrator relation."""

src/charm.py

Lines changed: 104 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import time
1515
from pathlib import Path
1616
from typing import Literal, get_args
17+
from urllib.parse import urlparse
1718

1819
# First platform-specific import, will fail on wrong architecture
1920
try:
@@ -88,6 +89,7 @@
8889
APP_SCOPE,
8990
BACKUP_USER,
9091
DATABASE_DEFAULT_NAME,
92+
DATABASE_PORT,
9193
METRICS_PORT,
9294
MONITORING_PASSWORD_KEY,
9395
MONITORING_USER,
@@ -195,10 +197,11 @@ def __init__(self, *args):
195197
deleted_label=SECRET_DELETED_LABEL,
196198
)
197199

198-
self._postgresql_service = "postgresql"
200+
self.postgresql_service = "postgresql"
199201
self.rotate_logs_service = "rotate-logs"
200202
self.pgbackrest_server_service = "pgbackrest server"
201-
self._metrics_service = "metrics_server"
203+
self.ldap_sync_service = "ldap-sync"
204+
self.metrics_service = "metrics_server"
202205
self._unit = self.model.unit.name
203206
self._name = self.model.app.name
204207
self._namespace = self.model.name
@@ -601,7 +604,7 @@ def _on_peer_relation_changed(self, event: HookEvent) -> None: # noqa: C901
601604
logger.debug("on_peer_relation_changed early exit: Unit in blocked status")
602605
return
603606

604-
services = container.pebble.get_services(names=[self._postgresql_service])
607+
services = container.pebble.get_services(names=[self.postgresql_service])
605608
if (
606609
(self.is_cluster_restoring_backup or self.is_cluster_restoring_to_time)
607610
and len(services) > 0
@@ -1496,7 +1499,7 @@ def _on_update_status(self, _) -> None:
14961499
if not self._on_update_status_early_exit_checks(container):
14971500
return
14981501

1499-
services = container.pebble.get_services(names=[self._postgresql_service])
1502+
services = container.pebble.get_services(names=[self.postgresql_service])
15001503
if len(services) == 0:
15011504
# Service has not been added nor started yet, so don't try to check Patroni API.
15021505
logger.debug("on_update_status early exit: Service has not been added nor started yet")
@@ -1509,10 +1512,10 @@ def _on_update_status(self, _) -> None:
15091512
and services[0].current != ServiceStatus.ACTIVE
15101513
):
15111514
logger.warning(
1512-
f"{self._postgresql_service} pebble service inactive, restarting service"
1515+
f"{self.postgresql_service} pebble service inactive, restarting service"
15131516
)
15141517
try:
1515-
container.restart(self._postgresql_service)
1518+
container.restart(self.postgresql_service)
15161519
except ChangeError:
15171520
logger.exception("Failed to restart patroni")
15181521
# If service doesn't recover fast, exit and wait for next hook run to re-check
@@ -1609,7 +1612,7 @@ def _handle_processes_failures(self) -> bool:
16091612
# https://github.com/canonical/pebble/issues/149 is resolved.
16101613
if not self._patroni.member_started and self._patroni.is_database_running:
16111614
try:
1612-
container.restart(self._postgresql_service)
1615+
container.restart(self.postgresql_service)
16131616
logger.info("restarted Patroni because it was not running")
16141617
except ChangeError:
16151618
logger.error("failed to restart Patroni after checking that it was not running")
@@ -1746,6 +1749,37 @@ def _update_endpoints(
17461749
endpoints.remove(endpoint)
17471750
self._peers.data[self.app]["endpoints"] = json.dumps(endpoints)
17481751

1752+
def _generate_ldap_service(self) -> dict:
1753+
"""Generate the LDAP service definition."""
1754+
ldap_params = self.get_ldap_parameters()
1755+
1756+
ldap_url = urlparse(ldap_params["ldapurl"])
1757+
ldap_host = ldap_url.hostname
1758+
ldap_port = ldap_url.port
1759+
1760+
ldap_base_dn = ldap_params["ldapbasedn"]
1761+
ldap_bind_username = ldap_params["ldapbinddn"]
1762+
ldap_bing_password = ldap_params["ldapbindpasswd"]
1763+
1764+
return {
1765+
"override": "replace",
1766+
"summary": "synchronize LDAP users",
1767+
"command": "/start-ldap-synchronizer.sh",
1768+
"startup": "enabled",
1769+
"environment": {
1770+
"LDAP_HOST": ldap_host,
1771+
"LDAP_PORT": ldap_port,
1772+
"LDAP_BASE_DN": ldap_base_dn,
1773+
"LDAP_BIND_USERNAME": ldap_bind_username,
1774+
"LDAP_BIND_PASSWORD": ldap_bing_password,
1775+
"POSTGRES_HOST": "127.0.0.1",
1776+
"POSTGRES_PORT": DATABASE_PORT,
1777+
"POSTGRES_DATABASE": DATABASE_DEFAULT_NAME,
1778+
"POSTGRES_USERNAME": USER,
1779+
"POSTGRES_PASSWORD": self.get_secret(APP_SCOPE, USER_PASSWORD_KEY),
1780+
},
1781+
}
1782+
17491783
def _generate_metrics_service(self) -> dict:
17501784
"""Generate the metrics service definition."""
17511785
return {
@@ -1757,7 +1791,7 @@ def _generate_metrics_service(self) -> dict:
17571791
if self.get_secret("app", MONITORING_PASSWORD_KEY) is not None
17581792
else "disabled"
17591793
),
1760-
"after": [self._postgresql_service],
1794+
"after": [self.postgresql_service],
17611795
"user": WORKLOAD_OS_USER,
17621796
"group": WORKLOAD_OS_GROUP,
17631797
"environment": {
@@ -1776,7 +1810,7 @@ def _postgresql_layer(self) -> Layer:
17761810
"summary": "postgresql + patroni layer",
17771811
"description": "pebble config layer for postgresql + patroni",
17781812
"services": {
1779-
self._postgresql_service: {
1813+
self.postgresql_service: {
17801814
"override": "replace",
17811815
"summary": "entrypoint of the postgresql + patroni image",
17821816
"command": f"patroni {self._storage_path}/patroni.yml",
@@ -1806,7 +1840,13 @@ def _postgresql_layer(self) -> Layer:
18061840
"user": WORKLOAD_OS_USER,
18071841
"group": WORKLOAD_OS_GROUP,
18081842
},
1809-
self._metrics_service: self._generate_metrics_service(),
1843+
self.ldap_sync_service: {
1844+
"override": "replace",
1845+
"summary": "synchronize LDAP users",
1846+
"command": "/start-ldap-synchronizer.sh",
1847+
"startup": "disabled",
1848+
},
1849+
self.metrics_service: self._generate_metrics_service(),
18101850
self.rotate_logs_service: {
18111851
"override": "replace",
18121852
"summary": "rotate logs",
@@ -1815,7 +1855,7 @@ def _postgresql_layer(self) -> Layer:
18151855
},
18161856
},
18171857
"checks": {
1818-
self._postgresql_service: {
1858+
self.postgresql_service: {
18191859
"override": "replace",
18201860
"level": "ready",
18211861
"http": {
@@ -1918,14 +1958,59 @@ def _restart(self, event: RunWithLock) -> None:
19181958
# Start or stop the pgBackRest TLS server service when TLS certificate change.
19191959
self.backup.start_stop_pgbackrest_service()
19201960

1961+
def _restart_metrics_service(self) -> None:
1962+
"""Restart the monitoring service if the password was rotated."""
1963+
container = self.unit.get_container("postgresql")
1964+
current_layer = container.get_plan()
1965+
1966+
metrics_service = current_layer.services[self.metrics_service]
1967+
data_source_name = metrics_service.environment.get("DATA_SOURCE_NAME", "")
1968+
1969+
if metrics_service and not data_source_name.startswith(
1970+
f"user={MONITORING_USER} password={self.get_secret('app', MONITORING_PASSWORD_KEY)} "
1971+
):
1972+
container.add_layer(
1973+
self.metrics_service,
1974+
Layer({"services": {self.metrics_service: self._generate_metrics_service()}}),
1975+
combine=True,
1976+
)
1977+
container.restart(self.metrics_service)
1978+
1979+
def _restart_ldap_sync_service(self) -> None:
1980+
"""Restart the LDAP sync service in case any configuration changed."""
1981+
if not self._patroni.member_started:
1982+
logger.debug("Restart LDAP sync early exit: Patroni has not started yet")
1983+
return
1984+
1985+
container = self.unit.get_container("postgresql")
1986+
sync_service = container.pebble.get_services(names=[self.ldap_sync_service])
1987+
1988+
if not self.is_primary and sync_service[0].is_running():
1989+
logger.debug("Stopping LDAP sync service. It must only run in the primary")
1990+
container.stop(self.pg_ldap_sync_service)
1991+
1992+
if self.is_primary and not self.is_ldap_enabled:
1993+
logger.debug("Stopping LDAP sync service")
1994+
container.stop(self.ldap_sync_service)
1995+
return
1996+
1997+
if self.is_primary and self.is_ldap_enabled:
1998+
container.add_layer(
1999+
self.ldap_sync_service,
2000+
Layer({"services": {self.ldap_sync_service: self._generate_ldap_service()}}),
2001+
combine=True,
2002+
)
2003+
logger.debug("Starting LDAP sync service")
2004+
container.restart(self.ldap_sync_service)
2005+
19212006
@property
19222007
def _is_workload_running(self) -> bool:
19232008
"""Returns whether the workload is running (in an active state)."""
19242009
container = self.unit.get_container("postgresql")
19252010
if not container.can_connect():
19262011
return False
19272012

1928-
services = container.pebble.get_services(names=[self._postgresql_service])
2013+
services = container.pebble.get_services(names=[self.postgresql_service])
19292014
if len(services) == 0:
19302015
return False
19312016

@@ -2009,21 +2094,12 @@ def update_config(self, is_creating_backup: bool = False) -> bool:
20092094
})
20102095

20112096
self._handle_postgresql_restart_need()
2097+
self._restart_metrics_service()
20122098

2013-
# Restart the monitoring service if the password was rotated
2014-
container = self.unit.get_container("postgresql")
2015-
current_layer = container.get_plan()
2016-
if (
2017-
metrics_service := current_layer.services[self._metrics_service]
2018-
) and not metrics_service.environment.get("DATA_SOURCE_NAME", "").startswith(
2019-
f"user={MONITORING_USER} password={self.get_secret('app', MONITORING_PASSWORD_KEY)} "
2020-
):
2021-
container.add_layer(
2022-
self._metrics_service,
2023-
Layer({"services": {self._metrics_service: self._generate_metrics_service()}}),
2024-
combine=True,
2025-
)
2026-
container.restart(self._metrics_service)
2099+
# TODO: Un-comment
2100+
# When PostgreSQL-rock wrapping PostgreSQL-snap versions 162 / 163 gets published
2101+
# (i.e. snap contains https://github.com/canonical/charmed-postgresql-snap/pull/88)
2102+
# self._restart_ldap_sync_service()
20272103

20282104
return True
20292105

@@ -2102,14 +2178,14 @@ def _update_pebble_layers(self, replan: bool = True) -> None:
21022178
# Check if there are any changes to layer services.
21032179
if current_layer.services != new_layer.services:
21042180
# Changes were made, add the new layer.
2105-
container.add_layer(self._postgresql_service, new_layer, combine=True)
2181+
container.add_layer(self.postgresql_service, new_layer, combine=True)
21062182
logging.info("Added updated layer 'postgresql' to Pebble plan")
21072183
if replan:
21082184
container.replan()
21092185
logging.info("Restarted postgresql service")
21102186
if current_layer.checks != new_layer.checks:
21112187
# Changes were made, add the new layer.
2112-
container.add_layer(self._postgresql_service, new_layer, combine=True)
2188+
container.add_layer(self.postgresql_service, new_layer, combine=True)
21132189
logging.info("Updated health checks")
21142190

21152191
def _unit_name_to_pod_name(self, unit_name: str) -> str:

src/relations/async_replication.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -524,15 +524,15 @@ def _on_async_relation_changed(self, event: RelationChangedEvent) -> None:
524524

525525
if (
526526
not self.container.can_connect()
527-
or len(self.container.pebble.get_services(names=[self.charm._postgresql_service])) == 0
527+
or len(self.container.pebble.get_services(names=[self.charm.postgresql_service])) == 0
528528
):
529529
logger.debug("Early exit on_async_relation_changed: container hasn't started yet.")
530530
event.defer()
531531
return
532532

533533
# Update the asynchronous replication configuration and start the database.
534534
self.charm.update_config()
535-
self.container.start(self.charm._postgresql_service)
535+
self.container.start(self.charm.postgresql_service)
536536

537537
self._handle_database_start(event)
538538

@@ -694,7 +694,7 @@ def _stop_database(self, event: RelationChangedEvent) -> bool:
694694
logger.debug("Early exit on_async_relation_changed: following promoted cluster.")
695695
return False
696696

697-
self.container.stop(self.charm._postgresql_service)
697+
self.container.stop(self.charm.postgresql_service)
698698

699699
if self.charm.unit.is_leader():
700700
# Remove the "cluster_initialised" flag to avoid self-healing in the update status hook.

0 commit comments

Comments
 (0)