14
14
import time
15
15
from pathlib import Path
16
16
from typing import Literal , get_args
17
+ from urllib .parse import urlparse
17
18
18
19
# First platform-specific import, will fail on wrong architecture
19
20
try :
88
89
APP_SCOPE ,
89
90
BACKUP_USER ,
90
91
DATABASE_DEFAULT_NAME ,
92
+ DATABASE_PORT ,
91
93
METRICS_PORT ,
92
94
MONITORING_PASSWORD_KEY ,
93
95
MONITORING_USER ,
@@ -195,10 +197,11 @@ def __init__(self, *args):
195
197
deleted_label = SECRET_DELETED_LABEL ,
196
198
)
197
199
198
- self ._postgresql_service = "postgresql"
200
+ self .postgresql_service = "postgresql"
199
201
self .rotate_logs_service = "rotate-logs"
200
202
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"
202
205
self ._unit = self .model .unit .name
203
206
self ._name = self .model .app .name
204
207
self ._namespace = self .model .name
@@ -601,7 +604,7 @@ def _on_peer_relation_changed(self, event: HookEvent) -> None: # noqa: C901
601
604
logger .debug ("on_peer_relation_changed early exit: Unit in blocked status" )
602
605
return
603
606
604
- services = container .pebble .get_services (names = [self ._postgresql_service ])
607
+ services = container .pebble .get_services (names = [self .postgresql_service ])
605
608
if (
606
609
(self .is_cluster_restoring_backup or self .is_cluster_restoring_to_time )
607
610
and len (services ) > 0
@@ -1496,7 +1499,7 @@ def _on_update_status(self, _) -> None:
1496
1499
if not self ._on_update_status_early_exit_checks (container ):
1497
1500
return
1498
1501
1499
- services = container .pebble .get_services (names = [self ._postgresql_service ])
1502
+ services = container .pebble .get_services (names = [self .postgresql_service ])
1500
1503
if len (services ) == 0 :
1501
1504
# Service has not been added nor started yet, so don't try to check Patroni API.
1502
1505
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:
1509
1512
and services [0 ].current != ServiceStatus .ACTIVE
1510
1513
):
1511
1514
logger .warning (
1512
- f"{ self ._postgresql_service } pebble service inactive, restarting service"
1515
+ f"{ self .postgresql_service } pebble service inactive, restarting service"
1513
1516
)
1514
1517
try :
1515
- container .restart (self ._postgresql_service )
1518
+ container .restart (self .postgresql_service )
1516
1519
except ChangeError :
1517
1520
logger .exception ("Failed to restart patroni" )
1518
1521
# 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:
1609
1612
# https://github.com/canonical/pebble/issues/149 is resolved.
1610
1613
if not self ._patroni .member_started and self ._patroni .is_database_running :
1611
1614
try :
1612
- container .restart (self ._postgresql_service )
1615
+ container .restart (self .postgresql_service )
1613
1616
logger .info ("restarted Patroni because it was not running" )
1614
1617
except ChangeError :
1615
1618
logger .error ("failed to restart Patroni after checking that it was not running" )
@@ -1746,6 +1749,37 @@ def _update_endpoints(
1746
1749
endpoints .remove (endpoint )
1747
1750
self ._peers .data [self .app ]["endpoints" ] = json .dumps (endpoints )
1748
1751
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
+
1749
1783
def _generate_metrics_service (self ) -> dict :
1750
1784
"""Generate the metrics service definition."""
1751
1785
return {
@@ -1757,7 +1791,7 @@ def _generate_metrics_service(self) -> dict:
1757
1791
if self .get_secret ("app" , MONITORING_PASSWORD_KEY ) is not None
1758
1792
else "disabled"
1759
1793
),
1760
- "after" : [self ._postgresql_service ],
1794
+ "after" : [self .postgresql_service ],
1761
1795
"user" : WORKLOAD_OS_USER ,
1762
1796
"group" : WORKLOAD_OS_GROUP ,
1763
1797
"environment" : {
@@ -1776,7 +1810,7 @@ def _postgresql_layer(self) -> Layer:
1776
1810
"summary" : "postgresql + patroni layer" ,
1777
1811
"description" : "pebble config layer for postgresql + patroni" ,
1778
1812
"services" : {
1779
- self ._postgresql_service : {
1813
+ self .postgresql_service : {
1780
1814
"override" : "replace" ,
1781
1815
"summary" : "entrypoint of the postgresql + patroni image" ,
1782
1816
"command" : f"patroni { self ._storage_path } /patroni.yml" ,
@@ -1806,7 +1840,13 @@ def _postgresql_layer(self) -> Layer:
1806
1840
"user" : WORKLOAD_OS_USER ,
1807
1841
"group" : WORKLOAD_OS_GROUP ,
1808
1842
},
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 (),
1810
1850
self .rotate_logs_service : {
1811
1851
"override" : "replace" ,
1812
1852
"summary" : "rotate logs" ,
@@ -1815,7 +1855,7 @@ def _postgresql_layer(self) -> Layer:
1815
1855
},
1816
1856
},
1817
1857
"checks" : {
1818
- self ._postgresql_service : {
1858
+ self .postgresql_service : {
1819
1859
"override" : "replace" ,
1820
1860
"level" : "ready" ,
1821
1861
"http" : {
@@ -1918,14 +1958,59 @@ def _restart(self, event: RunWithLock) -> None:
1918
1958
# Start or stop the pgBackRest TLS server service when TLS certificate change.
1919
1959
self .backup .start_stop_pgbackrest_service ()
1920
1960
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
+
1921
2006
@property
1922
2007
def _is_workload_running (self ) -> bool :
1923
2008
"""Returns whether the workload is running (in an active state)."""
1924
2009
container = self .unit .get_container ("postgresql" )
1925
2010
if not container .can_connect ():
1926
2011
return False
1927
2012
1928
- services = container .pebble .get_services (names = [self ._postgresql_service ])
2013
+ services = container .pebble .get_services (names = [self .postgresql_service ])
1929
2014
if len (services ) == 0 :
1930
2015
return False
1931
2016
@@ -2009,21 +2094,12 @@ def update_config(self, is_creating_backup: bool = False) -> bool:
2009
2094
})
2010
2095
2011
2096
self ._handle_postgresql_restart_need ()
2097
+ self ._restart_metrics_service ()
2012
2098
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()
2027
2103
2028
2104
return True
2029
2105
@@ -2102,14 +2178,14 @@ def _update_pebble_layers(self, replan: bool = True) -> None:
2102
2178
# Check if there are any changes to layer services.
2103
2179
if current_layer .services != new_layer .services :
2104
2180
# 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 )
2106
2182
logging .info ("Added updated layer 'postgresql' to Pebble plan" )
2107
2183
if replan :
2108
2184
container .replan ()
2109
2185
logging .info ("Restarted postgresql service" )
2110
2186
if current_layer .checks != new_layer .checks :
2111
2187
# 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 )
2113
2189
logging .info ("Updated health checks" )
2114
2190
2115
2191
def _unit_name_to_pod_name (self , unit_name : str ) -> str :
0 commit comments