Skip to content

Commit 274abc1

Browse files
[DPE-6964] Storage pools (#941)
* Separate storage pools Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Fix password rotation test Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Filter storages Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Partial fix for async replication Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Fix upgrade integration tests Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Add test to check new multiple storages Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Fix smoke test Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Fix temp storage path Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Add storages'descriptions Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Improve garbage storage check Signed-off-by: Marcelo Henrique Neppel <[email protected]> * Fix clean-data-dir.sh Signed-off-by: Marcelo Henrique Neppel <[email protected]> --------- Signed-off-by: Marcelo Henrique Neppel <[email protected]>
1 parent 1c90b97 commit 274abc1

File tree

16 files changed

+281
-137
lines changed

16 files changed

+281
-137
lines changed

lib/charms/postgresql_k8s/v0/postgresql.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535

3636
# Increment this PATCH version before using `charmcraft publish-lib` or reset
3737
# to 0 if you are raising the major API version
38-
LIBPATCH = 51
38+
LIBPATCH = 52
3939

4040
# Groups to distinguish HBA access
4141
ACCESS_GROUP_IDENTITY = "identity_access"
@@ -698,15 +698,22 @@ def list_valid_privileges_and_roles(self) -> Tuple[Set[str], Set[str]]:
698698
"superuser",
699699
}, {role[0] for role in cursor.fetchall() if role[0]}
700700

701-
def set_up_database(self) -> None:
701+
def set_up_database(self, temp_location: Optional[str] = None) -> None:
702702
"""Set up postgres database with the right permissions."""
703703
connection = None
704+
cursor = None
704705
try:
705-
with self._connect_to_database() as connection, connection.cursor() as cursor:
706-
cursor.execute("SELECT TRUE FROM pg_roles WHERE rolname='admin';")
707-
if cursor.fetchone() is not None:
708-
return
706+
connection = self._connect_to_database()
707+
cursor = connection.cursor()
709708

709+
if temp_location is not None:
710+
cursor.execute("SELECT TRUE FROM pg_tablespace WHERE spcname='temp';")
711+
if cursor.fetchone() is None:
712+
cursor.execute(f"CREATE TABLESPACE temp LOCATION '{temp_location}';")
713+
cursor.execute("GRANT CREATE ON TABLESPACE temp TO public;")
714+
715+
cursor.execute("SELECT TRUE FROM pg_roles WHERE rolname='admin';")
716+
if cursor.fetchone() is None:
710717
# Allow access to the postgres database only to the system users.
711718
cursor.execute("REVOKE ALL PRIVILEGES ON DATABASE postgres FROM PUBLIC;")
712719
cursor.execute("REVOKE CREATE ON SCHEMA public FROM PUBLIC;")
@@ -725,6 +732,8 @@ def set_up_database(self) -> None:
725732
logger.error(f"Failed to set up databases: {e}")
726733
raise PostgreSQLDatabasesSetupError() from e
727734
finally:
735+
if cursor is not None:
736+
cursor.close()
728737
if connection is not None:
729738
connection.close()
730739

metadata.yaml

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,14 @@ containers:
2121
postgresql:
2222
resource: postgresql-image
2323
mounts:
24-
- storage: pgdata
24+
- storage: archive
25+
location: /var/lib/postgresql/archive
26+
- storage: data
2527
location: /var/lib/postgresql/data
28+
- storage: logs
29+
location: /var/lib/postgresql/logs
30+
- storage: temp
31+
location: /var/lib/postgresql/temp
2632

2733
resources:
2834
postgresql-image:
@@ -80,9 +86,22 @@ requires:
8086
optional: true
8187

8288
storage:
83-
pgdata:
89+
archive:
8490
type: filesystem
91+
description: Storage mount used for holding local backups (before typically sending them to remote object storage) when relevant/needed.
92+
location: /var/lib/postgresql/archive
93+
data:
94+
type: filesystem
95+
description: Storage mount used for storing all tables, indexes, and so on (except those from temporary tablespaces).
8596
location: /var/lib/postgresql/data
97+
logs:
98+
type: filesystem
99+
description: Storage mount used for storing all the logs that are part of the transactional commit path (WAL files).
100+
location: /var/lib/postgresql/logs
101+
temp:
102+
type: filesystem
103+
description: Storage mount used for storing temporary tablespaces (where typically sort operations happen).
104+
location: /var/lib/postgresql/temp
86105

87106
assumes:
88107
- k8s-api

poetry.lock

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

src/charm.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -217,14 +217,14 @@ def __init__(self, *args):
217217
self.framework.observe(self.on.secret_changed, self._on_secret_changed)
218218
self.framework.observe(self.on[PEER].relation_departed, self._on_peer_relation_departed)
219219
self.framework.observe(self.on.postgresql_pebble_ready, self._on_postgresql_pebble_ready)
220-
self.framework.observe(self.on.pgdata_storage_detaching, self._on_pgdata_storage_detaching)
220+
self.framework.observe(self.on.data_storage_detaching, self._on_pgdata_storage_detaching)
221221
self.framework.observe(self.on.stop, self._on_stop)
222222
self.framework.observe(self.on.promote_to_primary_action, self._on_promote_to_primary)
223223
self.framework.observe(self.on.get_primary_action, self._on_get_primary)
224224
self.framework.observe(self.on.update_status, self._on_update_status)
225225

226226
self._certs_path = "/usr/local/share/ca-certificates"
227-
self._storage_path = self.meta.storages["pgdata"].location
227+
self._storage_path = self.meta.storages["data"].location
228228
self.pgdata_path = f"{self._storage_path}/pgdata"
229229

230230
self.upgrade = PostgreSQLUpgrade(
@@ -980,11 +980,26 @@ def _create_pgdata(self, container: Container):
980980
self.pgdata_path, permissions=0o750, user=WORKLOAD_OS_USER, group=WORKLOAD_OS_GROUP
981981
)
982982
# Also, fix the permissions from the parent directory.
983+
container.exec([
984+
"chown",
985+
f"{WORKLOAD_OS_USER}:{WORKLOAD_OS_GROUP}",
986+
"/var/lib/postgresql/archive",
987+
]).wait()
983988
container.exec([
984989
"chown",
985990
f"{WORKLOAD_OS_USER}:{WORKLOAD_OS_GROUP}",
986991
self._storage_path,
987992
]).wait()
993+
container.exec([
994+
"chown",
995+
f"{WORKLOAD_OS_USER}:{WORKLOAD_OS_GROUP}",
996+
"/var/lib/postgresql/logs",
997+
]).wait()
998+
container.exec([
999+
"chown",
1000+
f"{WORKLOAD_OS_USER}:{WORKLOAD_OS_GROUP}",
1001+
"/var/lib/postgresql/temp",
1002+
]).wait()
9881003

9891004
def _on_postgresql_pebble_ready(self, event: WorkloadEvent) -> None:
9901005
"""Event handler for PostgreSQL container on PebbleReadyEvent."""
@@ -1129,7 +1144,7 @@ def _initialize_cluster(self, event: WorkloadEvent) -> bool:
11291144
extra_user_roles=["pg_monitor"],
11301145
)
11311146

1132-
self.postgresql.set_up_database()
1147+
self.postgresql.set_up_database(temp_location="/var/lib/postgresql/temp")
11331148

11341149
access_groups = self.postgresql.list_access_groups()
11351150
if access_groups != set(ACCESS_GROUPS):
@@ -1463,6 +1478,9 @@ def _on_update_status_early_exit_checks(self, container) -> bool:
14631478
self.enable_disable_extensions()
14641479
return True
14651480

1481+
logger.error("calling self.fix_leader_annotation()")
1482+
self.fix_leader_annotation()
1483+
14661484
logger.debug("on_update_status early exit: Unit is in Blocked/Waiting status")
14671485
return False
14681486
return True

src/relations/async_replication.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -704,8 +704,14 @@ def _stop_database(self, event: RelationChangedEvent) -> bool:
704704

705705
# Remove and recreate the pgdata folder to enable replication of the data from the
706706
# primary cluster.
707-
logger.info("Removing and recreating pgdata folder")
708-
self.container.exec(f"rm -r {POSTGRESQL_DATA_PATH}".split()).wait_output()
707+
for path in [
708+
"/var/lib/postgresql/archive",
709+
POSTGRESQL_DATA_PATH,
710+
"/var/lib/postgresql/logs",
711+
"/var/lib/postgresql/temp",
712+
]:
713+
logger.info(f"Removing contents from {path}")
714+
self.container.exec(f"find {path} -mindepth 1 -delete".split()).wait_output()
709715
self.charm._create_pgdata(self.container)
710716

711717
self.charm._peers.data[self.charm.unit].update({"stopped": "True"})

templates/patroni.yml.j2

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ bootstrap:
7171
- auth-local: trust
7272
- encoding: UTF8
7373
- data-checksums
74+
- waldir: /var/lib/postgresql/logs
7475
{%- endif %}
7576
pg_hba:
7677
- {{ 'hostssl' if enable_tls else 'host' }} all all 0.0.0.0/0 md5
@@ -106,6 +107,8 @@ ctl:
106107
{%- endif %}
107108
pod_ip: '{{ endpoint }}'
108109
postgresql:
110+
basebackup:
111+
- waldir: /var/lib/postgresql/logs
109112
connect_address: '{{ endpoint }}:5432'
110113
data_dir: {{ storage_path }}/pgdata
111114
bin_dir: /usr/lib/postgresql/{{ version }}/bin
@@ -123,6 +126,7 @@ postgresql:
123126
ssl_cert_file: {{ storage_path }}/cert.pem
124127
ssl_key_file: {{ storage_path }}/key.pem
125128
{%- endif %}
129+
temp_tablespaces: temp
126130
{%- if pg_parameters %}
127131
{%- for key, value in pg_parameters.items() %}
128132
{{key}}: {{value}}

tests/helpers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@
77
import yaml
88

99
METADATA = yaml.safe_load(Path("./metadata.yaml").read_text())
10-
STORAGE_PATH = METADATA["storage"]["pgdata"]["location"]
10+
STORAGE_PATH = METADATA["storage"]["data"]["location"]
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
#!/usr/bin/env bash
22

33
set -Eeuo pipefail
4+
rm -rf /var/lib/postgresql/archive/*
45
rm -rf /var/lib/postgresql/data/pgdata/*
6+
rm -rf /var/lib/postgresql/logs/*
7+
rm -rf /var/lib/postgresql/temp/*

tests/integration/ha_tests/helpers.py

Lines changed: 49 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from kubernetes.client.api import core_v1_api
2222
from kubernetes.stream import stream
2323
from lightkube.core.client import Client, GlobalResource
24+
from lightkube.core.exceptions import ApiError
2425
from lightkube.resources.core_v1 import (
2526
PersistentVolume,
2627
PersistentVolumeClaim,
@@ -954,14 +955,17 @@ async def clear_continuous_writes(ops_test: OpsTest) -> None:
954955
action = await action.wait()
955956

956957

957-
async def get_storage_id(ops_test: OpsTest, unit_name: str) -> str:
958-
"""Retrieves storage id associated with provided unit.
958+
async def get_storage_ids(ops_test: OpsTest, unit_name: str) -> list[str]:
959+
"""Retrieves storage ids associated with provided unit.
959960
960961
Note: this function exists as a temporary solution until this issue is ported to libjuju 2:
961962
https://github.com/juju/python-libjuju/issues/694
962963
"""
964+
storage_ids = []
963965
model_name = ops_test.model.info.name
964-
proc = subprocess.check_output(f"juju storage --model={model_name}".split())
966+
proc = subprocess.check_output(
967+
f"juju storage --model={ops_test.controller_name}:{model_name}".split()
968+
)
965969
proc = proc.decode("utf-8")
966970
for line in proc.splitlines():
967971
if "Storage" in line:
@@ -973,8 +977,9 @@ async def get_storage_id(ops_test: OpsTest, unit_name: str) -> str:
973977
if "detached" in line:
974978
continue
975979

976-
if line.split()[0] == unit_name:
977-
return line.split()[1]
980+
if line.split()[0] == unit_name and line.split()[1].startswith("data"):
981+
storage_ids.append(line.split()[1])
982+
return storage_ids
978983

979984

980985
def is_pods_exists(ops_test: OpsTest, unit_name: str) -> bool:
@@ -1047,18 +1052,26 @@ async def check_db(ops_test: OpsTest, app: str, db: str) -> bool:
10471052
return db in query
10481053

10491054

1050-
async def get_any_deatached_storage(ops_test: OpsTest) -> str:
1051-
"""Returns any of the current available deatached storage."""
1055+
async def get_detached_storages(ops_test: OpsTest) -> list[str]:
1056+
"""Returns the current available detached storages."""
10521057
return_code, storages_list, stderr = await ops_test.juju(
10531058
"storage", "-m", f"{ops_test.controller_name}:{ops_test.model.info.name}", "--format=json"
10541059
)
10551060
if return_code != 0:
10561061
raise Exception(f"failed to get storages info with error: {stderr}")
10571062

10581063
parsed_storages_list = json.loads(storages_list)
1064+
detached_storages = []
10591065
for storage_name, storage in parsed_storages_list["storage"].items():
1060-
if (str(storage["status"]["current"]) == "detached") and (str(storage["life"] == "alive")):
1061-
return storage_name
1066+
if (
1067+
(storage_name.startswith("data"))
1068+
and (str(storage["status"]["current"]) == "detached")
1069+
and (str(storage["life"] == "alive"))
1070+
):
1071+
detached_storages.append(storage_name)
1072+
1073+
if len(detached_storages) > 0:
1074+
return detached_storages
10621075

10631076
raise Exception("failed to get deatached storage")
10641077

@@ -1078,39 +1091,49 @@ async def check_system_id_mismatch(ops_test: OpsTest, unit_name: str) -> bool:
10781091
def delete_pvc(ops_test: OpsTest, pvc: GlobalResource):
10791092
"""Deletes PersistentVolumeClaim."""
10801093
client = Client(namespace=ops_test.model.name)
1081-
client.delete(PersistentVolumeClaim, namespace=ops_test.model.name, name=pvc.metadata.name)
1094+
try:
1095+
client.delete(PersistentVolumeClaim, namespace=ops_test.model.name, name=pvc.metadata.name)
1096+
except ApiError as e:
1097+
logger.warning(f"failed to delete pvc {pvc.metadata.name}: {e}")
1098+
pass
10821099

10831100

1084-
def get_pvc(ops_test: OpsTest, unit_name: str):
1085-
"""Get PersistentVolumeClaim for unit."""
1101+
def get_pvcs(ops_test: OpsTest, unit_name: str):
1102+
"""Get PersistentVolumeClaims for unit."""
1103+
pvcs = []
10861104
client = Client(namespace=ops_test.model.name)
10871105
pvc_list = client.list(PersistentVolumeClaim, namespace=ops_test.model.name)
10881106
for pvc in pvc_list:
10891107
if unit_name.replace("/", "-") in pvc.metadata.name:
1090-
return pvc
1091-
return None
1108+
pvcs.append(pvc)
1109+
return pvcs
10921110

10931111

1094-
def get_pv(ops_test: OpsTest, unit_name: str):
1095-
"""Get PersistentVolume for unit."""
1112+
def get_pvs(ops_test: OpsTest, unit_name: str):
1113+
"""Get PersistentVolumes for unit."""
1114+
pvs = []
10961115
client = Client(namespace=ops_test.model.name)
10971116
pv_list = client.list(PersistentVolume, namespace=ops_test.model.name)
10981117
for pv in pv_list:
10991118
if unit_name.replace("/", "-") in str(pv.spec.hostPath.path):
1100-
return pv
1101-
return None
1119+
pvs.append(pv)
1120+
return pvs
11021121

11031122

1104-
def change_pv_reclaim_policy(ops_test: OpsTest, pvc_config: PersistentVolumeClaim, policy: str):
1123+
def change_pvs_reclaim_policy(ops_test: OpsTest, pvs_configs: list[PersistentVolume], policy: str):
11051124
"""Change PersistentVolume reclaim policy config value."""
11061125
client = Client(namespace=ops_test.model.name)
1107-
res = client.patch(
1108-
PersistentVolume,
1109-
pvc_config.metadata.name,
1110-
{"spec": {"persistentVolumeReclaimPolicy": f"{policy}"}},
1111-
namespace=ops_test.model.name,
1112-
)
1113-
return res
1126+
results = []
1127+
for pv_config in pvs_configs:
1128+
results.append(
1129+
client.patch(
1130+
PersistentVolume,
1131+
pv_config.metadata.name,
1132+
{"spec": {"persistentVolumeReclaimPolicy": f"{policy}"}},
1133+
namespace=ops_test.model.name,
1134+
)
1135+
)
1136+
return results
11141137

11151138

11161139
def remove_pv_claimref(ops_test: OpsTest, pv_config: PersistentVolume):

0 commit comments

Comments
 (0)