Skip to content

Commit 9bc9be2

Browse files
committed
Put container in a cached property, and update pdate lib to handle new invalid database name and to handle storage ownership and permissions
Signed-off-by: Marcelo Henrique Neppel <marcelo.neppel@canonical.com>
1 parent 4e3c6c6 commit 9bc9be2

File tree

5 files changed

+57
-56
lines changed

5 files changed

+57
-56
lines changed

poetry.lock

Lines changed: 9 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jinja2 = "^3.1.6"
1717
lightkube = "^0.17.2"
1818
lightkube-models = "^1.28.1.4"
1919
psycopg2 = "^2.9.10"
20-
postgresql-charms-single-kernel = "0.0.1"
20+
postgresql-charms-single-kernel = {url = "https://github.com/canonical/postgresql-single-kernel-library/archive/d0c4ddcf83d5147bb8f147b47ccf642839175e09.zip"}
2121

2222
[tool.poetry.group.charm-libs.dependencies]
2323
# data_platform_libs/v0/data_interfaces.py

src/charm.py

Lines changed: 44 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
ServiceStatus,
8585
)
8686
from requests import ConnectionError as RequestsConnectionError
87+
from single_kernel_postgresql.config.literals import Substrates
8788
from single_kernel_postgresql.utils.postgresql import (
8889
ACCESS_GROUP_IDENTITY,
8990
ACCESS_GROUPS,
@@ -446,17 +447,24 @@ def is_unit_stopped(self) -> bool:
446447
"""Returns whether the unit is stopped."""
447448
return "stopped" in self.unit_peer_data
448449

450+
@cached_property
451+
def _container(self) -> Container:
452+
"""Returns the postgresql container."""
453+
return self.unit.get_container("postgresql")
454+
449455
@property
450456
def postgresql(self) -> PostgreSQL:
451457
"""Returns an instance of the object used to interact with the database."""
452458
return PostgreSQL(
459+
substrate=Substrates.K8S,
453460
primary_host=self.primary_endpoint,
454461
current_host=self.endpoint,
455462
user=USER,
456463
# TODO switching to the new lib will expect None
457464
password=self.get_secret(APP_SCOPE, f"{USER}-password"), # type: ignore
458465
database=DATABASE_DEFAULT_NAME,
459466
system_users=SYSTEM_USERS,
467+
container=self._container,
460468
)
461469

462470
@property
@@ -617,8 +625,7 @@ def _on_peer_relation_changed(self, event: HookEvent) -> None: # noqa: C901
617625

618626
# Update the list of the cluster members in the replicas to make them know each other.
619627
# Update the cluster members in this unit (updating patroni configuration).
620-
container = self.unit.get_container("postgresql")
621-
if not container.can_connect():
628+
if not self._container.can_connect():
622629
logger.debug(
623630
"Early exit on_peer_relation_changed: Waiting for container to become available"
624631
)
@@ -635,11 +642,11 @@ def _on_peer_relation_changed(self, event: HookEvent) -> None: # noqa: C901
635642
logger.debug("on_peer_relation_changed early exit: Unit in blocked status")
636643
return
637644

638-
services = container.pebble.get_services(names=[self.postgresql_service])
645+
services = self._container.pebble.get_services(names=[self.postgresql_service])
639646
if (
640647
(self.is_cluster_restoring_backup or self.is_cluster_restoring_to_time)
641648
and len(services) > 0
642-
and not self._was_restore_successful(container, services[0])
649+
and not self._was_restore_successful(self._container, services[0])
643650
):
644651
logger.debug("on_peer_relation_changed early exit: Backup restore check failed")
645652
return
@@ -1586,11 +1593,10 @@ def _check_pgdata_storage_size(self) -> None:
15861593

15871594
def _on_update_status(self, _) -> None:
15881595
"""Update the unit status message."""
1589-
container = self.unit.get_container("postgresql")
1590-
if not self._on_update_status_early_exit_checks(container):
1596+
if not self._on_update_status_early_exit_checks(self._container):
15911597
return
15921598

1593-
services = container.pebble.get_services(names=[self.postgresql_service])
1599+
services = self._container.pebble.get_services(names=[self.postgresql_service])
15941600
if len(services) == 0:
15951601
# Service has not been added nor started yet, so don't try to check Patroni API.
15961602
logger.debug("on_update_status early exit: Service has not been added nor started yet")
@@ -1606,7 +1612,7 @@ def _on_update_status(self, _) -> None:
16061612
f"{self.postgresql_service} pebble service inactive, restarting service"
16071613
)
16081614
try:
1609-
container.restart(self.postgresql_service)
1615+
self._container.restart(self.postgresql_service)
16101616
except ChangeError:
16111617
logger.exception("Failed to restart patroni")
16121618
# If service doesn't recover fast, exit and wait for next hook run to re-check
@@ -1616,7 +1622,7 @@ def _on_update_status(self, _) -> None:
16161622

16171623
if (
16181624
self.is_cluster_restoring_backup or self.is_cluster_restoring_to_time
1619-
) and not self._was_restore_successful(container, services[0]):
1625+
) and not self._was_restore_successful(self._container, services[0]):
16201626
return
16211627

16221628
if self._handle_processes_failures():
@@ -1704,14 +1710,12 @@ def _handle_processes_failures(self) -> bool:
17041710
Returns:
17051711
a bool indicating whether the charm performed any action.
17061712
"""
1707-
container = self.unit.get_container("postgresql")
1708-
17091713
# Restart the Patroni process if it was killed (in that case, the PostgreSQL
17101714
# process is still running). This is needed until
17111715
# https://github.com/canonical/pebble/issues/149 is resolved.
17121716
if not self._patroni.member_started and self._patroni.is_database_running:
17131717
try:
1714-
container.restart(self.postgresql_service)
1718+
self._container.restart(self.postgresql_service)
17151719
logger.info("restarted Patroni because it was not running")
17161720
except ChangeError:
17171721
logger.error("failed to restart Patroni after checking that it was not running")
@@ -1994,41 +1998,41 @@ def _push_file_to_workload(self, container: Container, file_path: str, file_data
19941998

19951999
def push_tls_files_to_workload(self) -> bool:
19962000
"""Uploads TLS files to the workload container."""
1997-
container = self.unit.get_container("postgresql")
1998-
19992001
key, ca, cert = self.tls.get_tls_files()
20002002

20012003
if key is not None:
2002-
self._push_file_to_workload(container, f"{self._storage_path}/{TLS_KEY_FILE}", key)
2004+
self._push_file_to_workload(
2005+
self._container, f"{self._storage_path}/{TLS_KEY_FILE}", key
2006+
)
20032007
if ca is not None:
2004-
self._push_file_to_workload(container, f"{self._storage_path}/{TLS_CA_FILE}", ca)
2005-
self._push_file_to_workload(container, f"{self._certs_path}/ca.crt", ca)
2006-
container.exec(["update-ca-certificates"]).wait()
2008+
self._push_file_to_workload(self._container, f"{self._storage_path}/{TLS_CA_FILE}", ca)
2009+
self._push_file_to_workload(self._container, f"{self._certs_path}/ca.crt", ca)
2010+
self._container.exec(["update-ca-certificates"]).wait()
20072011
if cert is not None:
2008-
self._push_file_to_workload(container, f"{self._storage_path}/{TLS_CERT_FILE}", cert)
2012+
self._push_file_to_workload(
2013+
self._container, f"{self._storage_path}/{TLS_CERT_FILE}", cert
2014+
)
20092015

20102016
return self.update_config()
20112017

20122018
def push_ca_file_into_workload(self, secret_name: str) -> bool:
20132019
"""Uploads CA certificate into the workload container."""
2014-
container = self.unit.get_container("postgresql")
20152020
certificates = self.get_secret(UNIT_SCOPE, secret_name)
20162021

20172022
if certificates is not None:
20182023
self._push_file_to_workload(
2019-
container=container,
2024+
container=self._container,
20202025
file_path=f"{self._certs_path}/{secret_name}.crt",
20212026
file_data=certificates,
20222027
)
2023-
container.exec(["update-ca-certificates"]).wait()
2028+
self._container.exec(["update-ca-certificates"]).wait()
20242029

20252030
return self.update_config()
20262031

20272032
def clean_ca_file_from_workload(self, secret_name: str) -> bool:
20282033
"""Cleans up CA certificate from the workload container."""
2029-
container = self.unit.get_container("postgresql")
2030-
container.remove_path(f"{self._certs_path}/{secret_name}.crt")
2031-
container.exec(["update-ca-certificates"]).wait()
2034+
self._container.remove_path(f"{self._certs_path}/{secret_name}.crt")
2035+
self._container.exec(["update-ca-certificates"]).wait()
20322036

20332037
return self.update_config()
20342038

@@ -2064,57 +2068,54 @@ def _restart(self, event: RunWithLock) -> None:
20642068

20652069
def _restart_metrics_service(self) -> None:
20662070
"""Restart the monitoring service if the password was rotated."""
2067-
container = self.unit.get_container("postgresql")
2068-
current_layer = container.get_plan()
2071+
current_layer = self._container.get_plan()
20692072

20702073
metrics_service = current_layer.services[self.metrics_service]
20712074
data_source_name = metrics_service.environment.get("DATA_SOURCE_NAME", "")
20722075

20732076
if metrics_service and not data_source_name.startswith(
20742077
f"user={MONITORING_USER} password={self.get_secret('app', MONITORING_PASSWORD_KEY)} "
20752078
):
2076-
container.add_layer(
2079+
self._container.add_layer(
20772080
self.metrics_service,
20782081
Layer({"services": {self.metrics_service: self._generate_metrics_service()}}),
20792082
combine=True,
20802083
)
2081-
container.restart(self.metrics_service)
2084+
self._container.restart(self.metrics_service)
20822085

20832086
def _restart_ldap_sync_service(self) -> None:
20842087
"""Restart the LDAP sync service in case any configuration changed."""
20852088
if not self._patroni.member_started:
20862089
logger.debug("Restart LDAP sync early exit: Patroni has not started yet")
20872090
return
20882091

2089-
container = self.unit.get_container("postgresql")
2090-
sync_service = container.pebble.get_services(names=[self.ldap_sync_service])
2092+
sync_service = self._container.pebble.get_services(names=[self.ldap_sync_service])
20912093

20922094
if not self.is_primary and sync_service[0].is_running():
20932095
logger.debug("Stopping LDAP sync service. It must only run in the primary")
2094-
container.stop(self.ldap_sync_service)
2096+
self._container.stop(self.ldap_sync_service)
20952097

20962098
if self.is_primary and not self.is_ldap_enabled:
20972099
logger.debug("Stopping LDAP sync service")
2098-
container.stop(self.ldap_sync_service)
2100+
self._container.stop(self.ldap_sync_service)
20992101
return
21002102

21012103
if self.is_primary and self.is_ldap_enabled:
2102-
container.add_layer(
2104+
self._container.add_layer(
21032105
self.ldap_sync_service,
21042106
Layer({"services": {self.ldap_sync_service: self._generate_ldap_service()}}),
21052107
combine=True,
21062108
)
21072109
logger.debug("Starting LDAP sync service")
2108-
container.restart(self.ldap_sync_service)
2110+
self._container.restart(self.ldap_sync_service)
21092111

21102112
@property
21112113
def _is_workload_running(self) -> bool:
21122114
"""Returns whether the workload is running (in an active state)."""
2113-
container = self.unit.get_container("postgresql")
2114-
if not container.can_connect():
2115+
if not self._container.can_connect():
21152116
return False
21162117

2117-
services = container.pebble.get_services(names=[self.postgresql_service])
2118+
services = self._container.pebble.get_services(names=[self.postgresql_service])
21182119
if len(services) == 0:
21192120
return False
21202121

@@ -2252,8 +2253,7 @@ def _validate_config_options(self) -> None:
22522253
"storage_default_table_access_method config option has an invalid value"
22532254
)
22542255

2255-
container = self.unit.get_container("postgresql")
2256-
output, _ = container.exec(["locale", "-a"]).wait_output()
2256+
output, _ = self._container.exec(["locale", "-a"]).wait_output()
22572257
locales = list(output.splitlines())
22582258
for parameter in ["response_lc_monetary", "response_lc_numeric", "response_lc_time"]:
22592259
value = self.model.config.get(parameter)
@@ -2298,25 +2298,23 @@ def _handle_postgresql_restart_need(self):
22982298

22992299
def _update_pebble_layers(self, replan: bool = True) -> None:
23002300
"""Update the pebble layers to keep the health check URL up-to-date."""
2301-
container = self.unit.get_container("postgresql")
2302-
23032301
# Get the current layer.
2304-
current_layer = container.get_plan()
2302+
current_layer = self._container.get_plan()
23052303

23062304
# Create a new config layer.
23072305
new_layer = self._postgresql_layer()
23082306

23092307
# Check if there are any changes to layer services.
23102308
if current_layer.services != new_layer.services:
23112309
# Changes were made, add the new layer.
2312-
container.add_layer(self.postgresql_service, new_layer, combine=True)
2310+
self._container.add_layer(self.postgresql_service, new_layer, combine=True)
23132311
logging.info("Added updated layer 'postgresql' to Pebble plan")
23142312
if replan:
2315-
container.replan()
2313+
self._container.replan()
23162314
logging.info("Restarted postgresql service")
23172315
if current_layer.checks != new_layer.checks:
23182316
# Changes were made, add the new layer.
2319-
container.add_layer(self.postgresql_service, new_layer, combine=True)
2317+
self._container.add_layer(self.postgresql_service, new_layer, combine=True)
23202318
logging.info("Updated health checks")
23212319

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

src/relations/postgresql_provider.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
ACCESS_GROUP_RELATION,
1919
ACCESS_GROUPS,
2020
INVALID_DATABASE_NAME_BLOCKING_MESSAGE,
21+
INVALID_DATABASE_NAMES,
2122
INVALID_EXTRA_USER_ROLE_BLOCKING_MESSAGE,
2223
PostgreSQLCreateDatabaseError,
2324
PostgreSQLCreateUserError,
@@ -429,7 +430,7 @@ def check_for_invalid_database_name(self, relation_id: int) -> bool:
429430
for data in relation.data.values():
430431
database = data.get("database")
431432
if database is not None and (
432-
len(database) > 49 or database in ["postgres", "template0", "template1"]
433+
len(database) > 49 or database in INVALID_DATABASE_NAMES
433434
):
434435
return True
435436
return False

tests/integration/test_invalid_database.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ def test_database(juju: jubilant.Juju, predefined_roles) -> None:
7272
"""Check that an invalid database name makes the database charm block."""
7373
del predefined_roles[""]
7474
invalid_database_names = [
75+
"databases",
7576
"postgres",
7677
"template0",
7778
"template1",

0 commit comments

Comments
 (0)