postgresql-charms-single-kernel 16.3.10__tar.gz → 16.3.12__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/PKG-INFO +1 -1
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/pyproject.toml +4 -4
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/pyproject.toml.orig +4 -4
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/charms/abstract_charm.py +80 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/charms/k8s_charm.py +28 -1
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/charms/vm_charm.py +7 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/config/literals.py +10 -2
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/core/peer_relation.py +35 -31
- postgresql_charms_single_kernel-16.3.12/single_kernel_postgresql/events/async_replication.py +397 -0
- postgresql_charms_single_kernel-16.3.12/single_kernel_postgresql/events/watcher.py +524 -0
- postgresql_charms_single_kernel-16.3.12/single_kernel_postgresql/managers/async_replication.py +967 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/managers/k8s.py +23 -1
- postgresql_charms_single_kernel-16.3.12/single_kernel_postgresql/scripts/authorisation_rules_observer.py +145 -0
- postgresql_charms_single_kernel-16.3.12/single_kernel_postgresql/scripts/cluster_topology_observer.py +177 -0
- postgresql_charms_single_kernel-16.3.12/single_kernel_postgresql/scripts/raft_observer.py +85 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/workload/base.py +15 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/workload/k8s.py +52 -1
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/workload/paths/base.py +12 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/workload/paths/k8s.py +12 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/workload/paths/vm.py +11 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/workload/vm.py +82 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/LICENSE +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/README.md +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/__init__.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/charmcraft.yaml +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/charms/__init__.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/compat/__init__.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/compat/postgresql.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/compat/pyproject.toml +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/config/__init__.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/config/enums.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/config/exceptions.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/config/locales.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/config/pyproject.toml +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/config/statuses.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/core/__init__.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/core/config.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/core/relation_state.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/core/s3.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/core/state.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/events/__init__.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/events/backup.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/events/database.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/events/ldap.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/events/postgresql.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/events/tls.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/events/tls_transfer.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/lib/charms/data_platform_libs/v0/data_interfaces.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/lib/charms/data_platform_libs/v0/s3.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/lib/charms/data_platform_libs/v1/data_models.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/lib/charms/glauth_k8s/v0/ldap.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/managers/__init__.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/managers/backup.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/managers/base.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/managers/cluster.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/managers/config.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/managers/database.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/managers/patroni.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/managers/restore.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/managers/tls.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/scripts/rotate_logs.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/templates/__init__.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/templates/k8s/pgbackrest.conf.j2 +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/templates/k8s/pgbackrest.logrotate.j2 +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/templates/patroni.yml.j2 +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/templates/vm/pgbackrest.conf.j2 +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/templates/vm/pgbackrest.logrotate.j2 +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/utils/__init__.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/utils/arch.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/utils/backup.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/utils/filesystem.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/utils/postgresql.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/utils/s3.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/utils/secret.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/utils/status.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/workload/__init__.py +0 -0
- {postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/single_kernel_postgresql/workload/paths/__init__.py +0 -0
{postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: postgresql-charms-single-kernel
|
|
3
|
-
Version: 16.3.
|
|
3
|
+
Version: 16.3.12
|
|
4
4
|
Summary: Shared and reusable code for PostgreSQL-related charms
|
|
5
5
|
Author: Canonical Data Platform
|
|
6
6
|
Author-email: Canonical Data Platform <data-platform@lists.launchpad.net>
|
{postgresql_charms_single_kernel-16.3.10 → postgresql_charms_single_kernel-16.3.12}/pyproject.toml
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "postgresql-charms-single-kernel"
|
|
3
3
|
description = "Shared and reusable code for PostgreSQL-related charms"
|
|
4
|
-
version = "16.3.
|
|
4
|
+
version = "16.3.12"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
classifiers = [
|
|
7
7
|
"Development Status :: 3 - Alpha",
|
|
@@ -157,12 +157,12 @@ exclude = [
|
|
|
157
157
|
]
|
|
158
158
|
|
|
159
159
|
[dependency-groups]
|
|
160
|
-
format = ["ruff==0.16.
|
|
160
|
+
format = ["ruff==0.16.10"]
|
|
161
161
|
lint = [
|
|
162
162
|
"codespell==2.4.3",
|
|
163
|
-
"ty==0.0.
|
|
163
|
+
"ty==0.0.84",
|
|
164
164
|
]
|
|
165
165
|
unit = [
|
|
166
|
-
"coverage[toml]==7.16.
|
|
166
|
+
"coverage[toml]==7.16.2",
|
|
167
167
|
"pytest==9.1.1",
|
|
168
168
|
]
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[project]
|
|
5
5
|
name = "postgresql-charms-single-kernel"
|
|
6
6
|
description = "Shared and reusable code for PostgreSQL-related charms"
|
|
7
|
-
version = "16.3.
|
|
7
|
+
version = "16.3.12"
|
|
8
8
|
readme = "README.md"
|
|
9
9
|
license = {file = "LICENSE"}
|
|
10
10
|
authors = [
|
|
@@ -63,14 +63,14 @@ module-root = ""
|
|
|
63
63
|
|
|
64
64
|
[dependency-groups]
|
|
65
65
|
format = [
|
|
66
|
-
"ruff==0.16.
|
|
66
|
+
"ruff==0.16.10"
|
|
67
67
|
]
|
|
68
68
|
lint = [
|
|
69
69
|
"codespell==2.4.3",
|
|
70
|
-
"ty==0.0.
|
|
70
|
+
"ty==0.0.84"
|
|
71
71
|
]
|
|
72
72
|
unit = [
|
|
73
|
-
"coverage[toml]==7.16.
|
|
73
|
+
"coverage[toml]==7.16.2",
|
|
74
74
|
"pytest==9.1.1"
|
|
75
75
|
]
|
|
76
76
|
|
|
@@ -3,20 +3,26 @@
|
|
|
3
3
|
"""Skeleton for the abstract charm."""
|
|
4
4
|
|
|
5
5
|
from abc import ABC, abstractmethod
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
6
7
|
|
|
7
8
|
from data_platform_helpers.advanced_statuses import StatusHandler
|
|
8
9
|
from ops import StatusBase
|
|
9
10
|
from ops.charm import CharmBase
|
|
10
11
|
|
|
11
12
|
from single_kernel_postgresql.core.state import CharmState
|
|
13
|
+
from single_kernel_postgresql.events.async_replication import PostgreSQLAsyncReplication
|
|
12
14
|
from single_kernel_postgresql.events.database import DatabaseEventsHandler
|
|
13
15
|
from single_kernel_postgresql.events.ldap import LDAP
|
|
14
16
|
from single_kernel_postgresql.events.postgresql import PostgreSQLEventsHandler
|
|
15
17
|
from single_kernel_postgresql.events.tls import TLS
|
|
18
|
+
from single_kernel_postgresql.events.watcher import WatcherEventsHandler
|
|
16
19
|
from single_kernel_postgresql.lib.charms.data_platform_libs.v0.data_interfaces import (
|
|
17
20
|
DatabaseProvides,
|
|
18
21
|
)
|
|
19
22
|
from single_kernel_postgresql.lib.charms.data_platform_libs.v0.s3 import S3Requirer
|
|
23
|
+
from single_kernel_postgresql.managers.async_replication import (
|
|
24
|
+
AsyncReplicationManager,
|
|
25
|
+
)
|
|
20
26
|
from single_kernel_postgresql.managers.cluster import ClusterManager
|
|
21
27
|
from single_kernel_postgresql.managers.config import ConfigManager
|
|
22
28
|
from single_kernel_postgresql.managers.database import DatabaseManager
|
|
@@ -28,6 +34,9 @@ from ..config.enums import Substrates
|
|
|
28
34
|
from ..config.literals import DATABASE, S3_RELATION_NAME
|
|
29
35
|
from ..utils.postgresql import PostgreSQL
|
|
30
36
|
|
|
37
|
+
if TYPE_CHECKING:
|
|
38
|
+
from single_kernel_postgresql.managers.k8s import K8sManager
|
|
39
|
+
|
|
31
40
|
|
|
32
41
|
class AbstractPostgreSQLCharm(CharmBase, ABC):
|
|
33
42
|
"""An abstract PostgreSQL charm."""
|
|
@@ -60,6 +69,38 @@ class AbstractPostgreSQLCharm(CharmBase, ABC):
|
|
|
60
69
|
self.patroni_manager = PatroniManager(state=self.state, workload=self.workload)
|
|
61
70
|
self.cluster_manager = ClusterManager(state=self.state, workload=self.workload)
|
|
62
71
|
|
|
72
|
+
# Substrate-only K8s API seam: the K8s charm builds it by overriding
|
|
73
|
+
# build_k8s_manager(); VM charms need no K8s API access. Built here so the
|
|
74
|
+
# async-replication subsystem (below) can consume it.
|
|
75
|
+
k8s_manager = self.build_k8s_manager()
|
|
76
|
+
|
|
77
|
+
# Async-replication subsystem: the manager owns the data plane and the
|
|
78
|
+
# promotion/standby flows; the handler owns the observers and the public facade.
|
|
79
|
+
watcher = self._async_watcher()
|
|
80
|
+
self.async_replication_manager = AsyncReplicationManager(
|
|
81
|
+
state=self.state,
|
|
82
|
+
workload=self.workload,
|
|
83
|
+
patroni_manager=self.patroni_manager,
|
|
84
|
+
update_config=self.update_config,
|
|
85
|
+
set_unit_status=self.set_unit_status,
|
|
86
|
+
set_primary_status_message=self.set_primary_status_message,
|
|
87
|
+
set_app_status=self._recompute_async_app_status,
|
|
88
|
+
create_pgdata=self.create_pgdata,
|
|
89
|
+
fix_leader_annotation=self.fix_leader_annotation,
|
|
90
|
+
re_emit_relation_changed=self._re_emit_async_relation_changed,
|
|
91
|
+
k8s_manager=k8s_manager,
|
|
92
|
+
watcher=watcher,
|
|
93
|
+
)
|
|
94
|
+
self.async_replication = PostgreSQLAsyncReplication(
|
|
95
|
+
self,
|
|
96
|
+
self.state,
|
|
97
|
+
self.async_replication_manager,
|
|
98
|
+
self.patroni_manager,
|
|
99
|
+
self.workload,
|
|
100
|
+
k8s_manager=k8s_manager,
|
|
101
|
+
watcher=watcher,
|
|
102
|
+
)
|
|
103
|
+
|
|
63
104
|
# Client-relation subsystem: the charm is the composition root, as with the
|
|
64
105
|
# other managers; the handler owns only the observers and guard/defer decisions.
|
|
65
106
|
self.database_manager = DatabaseManager(
|
|
@@ -94,6 +135,7 @@ class AbstractPostgreSQLCharm(CharmBase, ABC):
|
|
|
94
135
|
self.config_manager,
|
|
95
136
|
self.patroni_manager,
|
|
96
137
|
)
|
|
138
|
+
self.watcher_handler = WatcherEventsHandler(self, self.workload, self.state)
|
|
97
139
|
|
|
98
140
|
# Status Handler
|
|
99
141
|
self.status_handler = StatusHandler(
|
|
@@ -146,6 +188,16 @@ class AbstractPostgreSQLCharm(CharmBase, ABC):
|
|
|
146
188
|
"""Restart the monitoring and LDAP-sync sidecar services."""
|
|
147
189
|
pass
|
|
148
190
|
|
|
191
|
+
@abstractmethod
|
|
192
|
+
def set_app_status(self, status: StatusBase) -> None:
|
|
193
|
+
"""Set the application status through the charm's own status gates."""
|
|
194
|
+
pass
|
|
195
|
+
|
|
196
|
+
@abstractmethod
|
|
197
|
+
def set_primary_status_message(self) -> None:
|
|
198
|
+
"""Recompute the unit's primary/standby status message."""
|
|
199
|
+
pass
|
|
200
|
+
|
|
149
201
|
@abstractmethod
|
|
150
202
|
def set_unit_status(self, status: StatusBase) -> None:
|
|
151
203
|
"""Set the unit status without overriding a higher-priority refresh status."""
|
|
@@ -156,6 +208,34 @@ class AbstractPostgreSQLCharm(CharmBase, ABC):
|
|
|
156
208
|
"""Re-render the Patroni configuration and apply it."""
|
|
157
209
|
pass
|
|
158
210
|
|
|
211
|
+
# Async-replication wiring helpers. The manager is constructed before the handler,
|
|
212
|
+
# so the framework-facing plumbing is re-exposed through these late-binding bridges;
|
|
213
|
+
# create_pgdata/fix_leader_annotation are K8s-only and default to no-ops the K8s
|
|
214
|
+
# charm overrides.
|
|
215
|
+
def _recompute_async_app_status(self) -> None:
|
|
216
|
+
"""Recompute the async-replication app status through the handler."""
|
|
217
|
+
self.async_replication.set_app_status()
|
|
218
|
+
|
|
219
|
+
def _re_emit_async_relation_changed(self) -> None:
|
|
220
|
+
"""Re-emitting the async relation-changed event goes through the handler."""
|
|
221
|
+
self.async_replication._re_emit_async_relation_changed_event()
|
|
222
|
+
|
|
223
|
+
def _async_watcher(self) -> "WatcherEventsHandler | None":
|
|
224
|
+
"""Overridable hook supplying the VM watcher bridge (K8s has none)."""
|
|
225
|
+
return None
|
|
226
|
+
|
|
227
|
+
def build_k8s_manager(self) -> "K8sManager | None":
|
|
228
|
+
"""Overridable hook supplying the K8s API seam (K8s charm only)."""
|
|
229
|
+
return None
|
|
230
|
+
|
|
231
|
+
def create_pgdata(self) -> None:
|
|
232
|
+
"""Create the PostgreSQL data directories (K8s only; overridden there)."""
|
|
233
|
+
return None
|
|
234
|
+
|
|
235
|
+
def fix_leader_annotation(self) -> bool:
|
|
236
|
+
"""Fix the leader annotation (K8s only; overridden there)."""
|
|
237
|
+
return False
|
|
238
|
+
|
|
159
239
|
@property
|
|
160
240
|
@abstractmethod
|
|
161
241
|
def primary_endpoint(self) -> str | None:
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
"""PostgreSQL Kubernetes Charm."""
|
|
6
6
|
|
|
7
7
|
import logging
|
|
8
|
+
from typing import cast
|
|
8
9
|
|
|
9
10
|
from ops import StatusBase
|
|
10
11
|
|
|
@@ -21,13 +22,24 @@ logger = logging.getLogger(__name__)
|
|
|
21
22
|
class PostgreSQLK8sCharm(AbstractPostgreSQLCharm):
|
|
22
23
|
"""PostgreSQL K8s Charm."""
|
|
23
24
|
|
|
25
|
+
# Built by build_k8s_manager() while the abstract __init__ wires the
|
|
26
|
+
# async-replication subsystem; never None on the K8s substrate.
|
|
27
|
+
k8s_manager: K8sManager
|
|
28
|
+
|
|
24
29
|
def __init__(self, *args):
|
|
25
30
|
"""Initialize the PostgreSQL Kubernetes Charm."""
|
|
26
31
|
super().__init__(*args)
|
|
27
32
|
assert isinstance(self.workload, K8sWorkload), ( # noqa: S101
|
|
28
33
|
"Workload must be an instance of K8sWorkload"
|
|
29
34
|
)
|
|
30
|
-
|
|
35
|
+
|
|
36
|
+
def build_k8s_manager(self) -> K8sManager:
|
|
37
|
+
"""Build the K8s API seam the async-replication subsystem consumes."""
|
|
38
|
+
self.k8s_manager = K8sManager(
|
|
39
|
+
self.state,
|
|
40
|
+
cast("K8sWorkload", self.workload),
|
|
41
|
+
)
|
|
42
|
+
return self.k8s_manager
|
|
31
43
|
|
|
32
44
|
@property
|
|
33
45
|
def postgresql(self) -> PostgreSQL:
|
|
@@ -88,6 +100,21 @@ class PostgreSQLK8sCharm(AbstractPostgreSQLCharm):
|
|
|
88
100
|
"""Re-render the Patroni configuration and apply it."""
|
|
89
101
|
return self.config_manager.update_config(self.postgresql)
|
|
90
102
|
|
|
103
|
+
def set_app_status(self, status: StatusBase) -> None:
|
|
104
|
+
"""Set the application status; the production charm gates this on its own state."""
|
|
105
|
+
self.app.status = status
|
|
106
|
+
|
|
107
|
+
def set_primary_status_message(self) -> None:
|
|
108
|
+
"""Recompute the unit's primary/standby status message."""
|
|
109
|
+
|
|
110
|
+
def fix_leader_annotation(self) -> bool:
|
|
111
|
+
"""Fix the leader annotation; the production charm owns the real implementation."""
|
|
112
|
+
return False
|
|
113
|
+
|
|
114
|
+
def create_pgdata(self) -> None:
|
|
115
|
+
"""Create the PostgreSQL data directories (ported from the K8s charm)."""
|
|
116
|
+
self.workload.init_storage()
|
|
117
|
+
|
|
91
118
|
@property
|
|
92
119
|
def primary_endpoint(self) -> str | None:
|
|
93
120
|
"""Address of the cluster primary's Service."""
|
|
@@ -79,6 +79,13 @@ class PostgreSQLVMCharm(AbstractPostgreSQLCharm):
|
|
|
79
79
|
"""Re-render the Patroni configuration and apply it."""
|
|
80
80
|
return self.config_manager.update_config(self.postgresql)
|
|
81
81
|
|
|
82
|
+
def set_app_status(self, status: StatusBase) -> None:
|
|
83
|
+
"""Set the application status; the production charm gates this on its own state."""
|
|
84
|
+
self.app.status = status
|
|
85
|
+
|
|
86
|
+
def set_primary_status_message(self) -> None:
|
|
87
|
+
"""Recompute the unit's primary/standby status message."""
|
|
88
|
+
|
|
82
89
|
@property
|
|
83
90
|
def primary_endpoint(self) -> str | None:
|
|
84
91
|
"""Address of the cluster primary, or None when there is not one."""
|
|
@@ -42,6 +42,8 @@ VM_PGBACKREST_LOGS_PATH = "var/log/pgbackrest"
|
|
|
42
42
|
|
|
43
43
|
## K8s Paths
|
|
44
44
|
K8S_DATA_PATH = "var/lib/pg/data"
|
|
45
|
+
K8S_ARCHIVE_PATH = "var/lib/pg/archive"
|
|
46
|
+
K8S_TEMP_STORAGE_PATH = "var/lib/pg/temp"
|
|
45
47
|
K8S_PGBACKREST_LOGS_PATH = "16/main/pgbackrest_logs"
|
|
46
48
|
|
|
47
49
|
## Shared Paths
|
|
@@ -134,6 +136,10 @@ ALL_CLIENT_RELATIONS = [DATABASE]
|
|
|
134
136
|
REPLICATION_CONSUMER_RELATION = "replication"
|
|
135
137
|
REPLICATION_OFFER_RELATION = "replication-offer"
|
|
136
138
|
|
|
139
|
+
# Async replication peer-data key holding the id of the labelless shared cluster-credentials
|
|
140
|
+
# secret the owner persists: referenced by id everywhere, never by label (DPE-10203).
|
|
141
|
+
ASYNC_SHARED_SECRET_ID_KEY = "async-replication-secret-id" # noqa: S105 — a databag key name, not a credential
|
|
142
|
+
|
|
137
143
|
# TLS files
|
|
138
144
|
TLS_KEY_FILE = "key.pem"
|
|
139
145
|
TLS_CA_FILE = "ca.pem"
|
|
@@ -192,8 +198,6 @@ VM_ROTATE_LOGS_LOG_FILE = "/var/log/rotate_logs.log"
|
|
|
192
198
|
|
|
193
199
|
## K8s restore storage paths (must match the metadata.yaml storage locations)
|
|
194
200
|
K8S_LOGS_STORAGE_PATH = "var/lib/pg/logs"
|
|
195
|
-
K8S_ARCHIVE_PATH = "var/lib/pg/archive"
|
|
196
|
-
K8S_TEMP_STORAGE_PATH = "var/lib/pg/temp"
|
|
197
201
|
K8S_WAL_DIR = "pg_wal"
|
|
198
202
|
K8S_TEMP_TABLESPACE_DIR = "pgsql_tmp"
|
|
199
203
|
K8S_PG_LOGS_PATH = "16/main/pg_logs"
|
|
@@ -207,3 +211,7 @@ K8S_PGBACKREST_LOGS_SYMLINK_PATH = "var/log/pgbackrest"
|
|
|
207
211
|
VM_PATRONICTL_EXECUTABLE = "charmed-postgresql.patronictl"
|
|
208
212
|
PATRONICTL_REMOVE_CONFIRMATION = "Yes I am aware"
|
|
209
213
|
RESTORE_REPEAT_CAUSE = "restore-backup"
|
|
214
|
+
|
|
215
|
+
# Watcher relation
|
|
216
|
+
RAFT_PORT = 2222
|
|
217
|
+
RAFT_PARTNER_PREFIX = "partner_node_status_server_"
|
|
@@ -9,7 +9,7 @@ import json
|
|
|
9
9
|
from collections.abc import MutableMapping
|
|
10
10
|
from functools import cached_property
|
|
11
11
|
|
|
12
|
-
from ops import ActiveStatus, Application, BlockedStatus, Relation, Unit
|
|
12
|
+
from ops import ActiveStatus, Application, BlockedStatus, ModelError, Relation, Unit
|
|
13
13
|
|
|
14
14
|
from single_kernel_postgresql.config.enums import Substrates
|
|
15
15
|
from single_kernel_postgresql.config.literals import (
|
|
@@ -27,22 +27,19 @@ from single_kernel_postgresql.lib.charms.data_platform_libs.v0.data_interfaces i
|
|
|
27
27
|
)
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
class
|
|
31
|
-
"""
|
|
30
|
+
class PeerRelationState(RelationState):
|
|
31
|
+
"""Common state for the unit and application sides of the peer relation."""
|
|
32
32
|
|
|
33
|
-
data_interface:
|
|
34
|
-
unit: Unit
|
|
33
|
+
data_interface: DataPeerData
|
|
35
34
|
|
|
36
35
|
def __init__(
|
|
37
36
|
self,
|
|
38
37
|
relation: Relation | None,
|
|
39
|
-
data_interface:
|
|
40
|
-
component: Unit,
|
|
38
|
+
data_interface: DataPeerData,
|
|
39
|
+
component: Unit | Application,
|
|
41
40
|
):
|
|
42
|
-
"""Initialize the PostgreSQLPeer object."""
|
|
43
41
|
super().__init__(relation, data_interface, component)
|
|
44
42
|
self.data_interface = data_interface
|
|
45
|
-
self.unit = component
|
|
46
43
|
|
|
47
44
|
def get_secret(self, key: str) -> str | None:
|
|
48
45
|
"""Get the secret value for 'key' from the peer relation data."""
|
|
@@ -62,6 +59,23 @@ class PostgreSQLPeer(RelationState):
|
|
|
62
59
|
return
|
|
63
60
|
self.data_interface.delete_relation_data(self.relation.id, [key])
|
|
64
61
|
|
|
62
|
+
|
|
63
|
+
class PostgreSQLPeer(PeerRelationState):
|
|
64
|
+
"""State/Relation data collection for a PostgreSQL unit."""
|
|
65
|
+
|
|
66
|
+
data_interface: DataPeerUnitData
|
|
67
|
+
unit: Unit
|
|
68
|
+
|
|
69
|
+
def __init__(
|
|
70
|
+
self,
|
|
71
|
+
relation: Relation | None,
|
|
72
|
+
data_interface: DataPeerUnitData,
|
|
73
|
+
component: Unit,
|
|
74
|
+
):
|
|
75
|
+
"""Initialize the PostgreSQLPeer object."""
|
|
76
|
+
super().__init__(relation, data_interface, component)
|
|
77
|
+
self.unit = component
|
|
78
|
+
|
|
65
79
|
@property
|
|
66
80
|
def is_app_leader(self) -> bool:
|
|
67
81
|
"""Check if the current unit is the leader of the application."""
|
|
@@ -384,7 +398,7 @@ class PostgreSQLPeer(RelationState):
|
|
|
384
398
|
return peer_addrs
|
|
385
399
|
|
|
386
400
|
|
|
387
|
-
class PostgreSQLApplication(
|
|
401
|
+
class PostgreSQLApplication(PeerRelationState):
|
|
388
402
|
"""An PostgreSQL Application is the peer application state.
|
|
389
403
|
|
|
390
404
|
This class defines state/relation data for a single PostgreSQL application.
|
|
@@ -403,7 +417,6 @@ class PostgreSQLApplication(RelationState):
|
|
|
403
417
|
"""Initialize the PostgreSQLApplication object."""
|
|
404
418
|
super().__init__(relation, data_interface, component)
|
|
405
419
|
self.app = component
|
|
406
|
-
self.data_interface = data_interface
|
|
407
420
|
self.substrate = substrate
|
|
408
421
|
|
|
409
422
|
@property
|
|
@@ -498,8 +511,17 @@ class PostgreSQLApplication(RelationState):
|
|
|
498
511
|
|
|
499
512
|
@cached_property
|
|
500
513
|
def planned_units(self) -> int:
|
|
501
|
-
"""Get the number of planned units for the application.
|
|
502
|
-
|
|
514
|
+
"""Get the number of planned units for the application.
|
|
515
|
+
|
|
516
|
+
ops implements ``Application.planned_units()`` via ``goal-state``, which fails
|
|
517
|
+
("saas application ... not found") while a cross-model SAAS force-removed during a
|
|
518
|
+
dead-DC teardown still lingers in goal-state. Fall back to the count of currently
|
|
519
|
+
known units so the hook reconciles instead of crashing every caller (DPE-10203).
|
|
520
|
+
"""
|
|
521
|
+
try:
|
|
522
|
+
return self.app.planned_units()
|
|
523
|
+
except ModelError:
|
|
524
|
+
return len(self.relation.units) + 1 if self.relation else 1
|
|
503
525
|
|
|
504
526
|
@property
|
|
505
527
|
def members_ips(self) -> set[str]:
|
|
@@ -674,24 +696,6 @@ class PostgreSQLApplication(RelationState):
|
|
|
674
696
|
return
|
|
675
697
|
self.relation.data[self.app]["restore-to-time"] = value
|
|
676
698
|
|
|
677
|
-
def get_secret(self, key: str) -> str | None:
|
|
678
|
-
"""Get the secret value for 'key' from the peer relation data."""
|
|
679
|
-
if not self.relation:
|
|
680
|
-
return None
|
|
681
|
-
return self.data_interface.get_secret(self.relation.id, key)
|
|
682
|
-
|
|
683
|
-
def set_secret(self, key: str, value: str) -> None:
|
|
684
|
-
"""Set the secret value for 'key' in the peer relation data."""
|
|
685
|
-
if not self.relation:
|
|
686
|
-
return
|
|
687
|
-
self.data_interface.set_secret(self.relation.id, key, value)
|
|
688
|
-
|
|
689
|
-
def remove_secret(self, key: str) -> None:
|
|
690
|
-
"""Remove the secret value for 'key' from the peer relation data."""
|
|
691
|
-
if not self.relation:
|
|
692
|
-
return
|
|
693
|
-
self.data_interface.delete_relation_data(self.relation.id, [key])
|
|
694
|
-
|
|
695
699
|
@cached_property
|
|
696
700
|
def data(self) -> MutableMapping[str, str]:
|
|
697
701
|
"""Escape hatch method to access the peer data directly."""
|