postgresql-charms-single-kernel 16.3.9__tar.gz → 16.3.10__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.9 → postgresql_charms_single_kernel-16.3.10}/PKG-INFO +1 -1
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/pyproject.toml +1 -1
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/pyproject.toml.orig +1 -1
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/config/literals.py +19 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/core/peer_relation.py +36 -2
- postgresql_charms_single_kernel-16.3.10/single_kernel_postgresql/events/backup.py +172 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/events/postgresql.py +4 -6
- postgresql_charms_single_kernel-16.3.10/single_kernel_postgresql/managers/backup.py +1241 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/managers/database.py +4 -4
- postgresql_charms_single_kernel-16.3.10/single_kernel_postgresql/managers/k8s.py +123 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/managers/patroni.py +3 -1
- postgresql_charms_single_kernel-16.3.10/single_kernel_postgresql/managers/restore.py +685 -0
- postgresql_charms_single_kernel-16.3.10/single_kernel_postgresql/scripts/rotate_logs.py +21 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/utils/backup.py +13 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/utils/s3.py +20 -8
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/workload/base.py +100 -2
- postgresql_charms_single_kernel-16.3.10/single_kernel_postgresql/workload/k8s.py +671 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/workload/vm.py +101 -1
- postgresql_charms_single_kernel-16.3.9/single_kernel_postgresql/managers/backup.py +0 -489
- postgresql_charms_single_kernel-16.3.9/single_kernel_postgresql/managers/k8s.py +0 -244
- postgresql_charms_single_kernel-16.3.9/single_kernel_postgresql/workload/k8s.py +0 -299
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/LICENSE +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/README.md +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/__init__.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/charmcraft.yaml +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/charms/__init__.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/charms/abstract_charm.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/charms/k8s_charm.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/charms/vm_charm.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/compat/__init__.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/compat/postgresql.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/compat/pyproject.toml +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/config/__init__.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/config/enums.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/config/exceptions.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/config/locales.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/config/pyproject.toml +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/config/statuses.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/core/__init__.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/core/config.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/core/relation_state.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/core/s3.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/core/state.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/events/__init__.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/events/database.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/events/ldap.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/events/tls.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/events/tls_transfer.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/lib/charms/data_platform_libs/v0/data_interfaces.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/lib/charms/data_platform_libs/v0/s3.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/lib/charms/data_platform_libs/v1/data_models.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/lib/charms/glauth_k8s/v0/ldap.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/managers/__init__.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/managers/base.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/managers/cluster.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/managers/config.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/managers/tls.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/templates/__init__.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/templates/k8s/pgbackrest.conf.j2 +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/templates/k8s/pgbackrest.logrotate.j2 +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/templates/patroni.yml.j2 +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/templates/vm/pgbackrest.conf.j2 +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/templates/vm/pgbackrest.logrotate.j2 +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/utils/__init__.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/utils/arch.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/utils/filesystem.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/utils/postgresql.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/utils/secret.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/utils/status.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/workload/__init__.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/workload/paths/__init__.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/workload/paths/base.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/workload/paths/k8s.py +0 -0
- {postgresql_charms_single_kernel-16.3.9 → postgresql_charms_single_kernel-16.3.10}/single_kernel_postgresql/workload/paths/vm.py +0 -0
|
@@ -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.10
|
|
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>
|
|
@@ -188,3 +188,22 @@ RAFT_PARTNER_PREFIX = "partner_node_status_server_"
|
|
|
188
188
|
VM_PATRONI_SERVICE_NAME = f"snap.{POSTGRESQL_SNAP_NAME}.patroni.service"
|
|
189
189
|
VM_PATRONI_SERVICE_DEFAULT_PATH = f"/etc/systemd/system/{VM_PATRONI_SERVICE_NAME}"
|
|
190
190
|
VM_PGBACKREST_SERVICE_NAME = "pgbackrest-service"
|
|
191
|
+
VM_ROTATE_LOGS_LOG_FILE = "/var/log/rotate_logs.log"
|
|
192
|
+
|
|
193
|
+
## K8s restore storage paths (must match the metadata.yaml storage locations)
|
|
194
|
+
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
|
+
K8S_WAL_DIR = "pg_wal"
|
|
198
|
+
K8S_TEMP_TABLESPACE_DIR = "pgsql_tmp"
|
|
199
|
+
K8S_PG_LOGS_PATH = "16/main/pg_logs"
|
|
200
|
+
K8S_PATRONI_LOGS_PATH = "16/main/patroni_logs"
|
|
201
|
+
K8S_DEBIAN_DATA_SYMLINK = "var/lib/postgresql/16"
|
|
202
|
+
K8S_POSTGRESQL_LOGS_SYMLINK_PATH = "var/log/postgresql"
|
|
203
|
+
K8S_PATRONI_LOGS_SYMLINK_PATH = "var/log/patroni"
|
|
204
|
+
K8S_PGBACKREST_LOGS_SYMLINK_PATH = "var/log/pgbackrest"
|
|
205
|
+
|
|
206
|
+
## Restore
|
|
207
|
+
VM_PATRONICTL_EXECUTABLE = "charmed-postgresql.patronictl"
|
|
208
|
+
PATRONICTL_REMOVE_CONFIRMATION = "Yes I am aware"
|
|
209
|
+
RESTORE_REPEAT_CAUSE = "restore-backup"
|
|
@@ -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 Application, BlockedStatus, Relation, Unit
|
|
12
|
+
from ops import ActiveStatus, Application, BlockedStatus, Relation, Unit
|
|
13
13
|
|
|
14
14
|
from single_kernel_postgresql.config.enums import Substrates
|
|
15
15
|
from single_kernel_postgresql.config.literals import (
|
|
@@ -68,7 +68,27 @@ class PostgreSQLPeer(RelationState):
|
|
|
68
68
|
return self.unit.is_leader()
|
|
69
69
|
|
|
70
70
|
@property
|
|
71
|
-
def
|
|
71
|
+
def is_active_status(self) -> bool:
|
|
72
|
+
"""Returns whether the unit is in an active state."""
|
|
73
|
+
return isinstance(self.unit.status, ActiveStatus)
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def rotate_logs_pid(self) -> int | None:
|
|
77
|
+
"""Get the rotate-logs process PID from the unit peer relation data."""
|
|
78
|
+
if not self.relation:
|
|
79
|
+
return None
|
|
80
|
+
stored = self.relation.data[self.unit].get("rotate-logs-pid")
|
|
81
|
+
return int(stored) if stored else None
|
|
82
|
+
|
|
83
|
+
@rotate_logs_pid.setter
|
|
84
|
+
def rotate_logs_pid(self, value: int | None) -> None:
|
|
85
|
+
"""Set or clear the rotate-logs process PID in the unit peer relation data."""
|
|
86
|
+
if not self.relation:
|
|
87
|
+
return
|
|
88
|
+
self.relation.data[self.unit]["rotate-logs-pid"] = str(value) if value else ""
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def is_blocked_status(self) -> bool:
|
|
72
92
|
"""Returns whether the unit is in a blocked state."""
|
|
73
93
|
return isinstance(self.unit.status, BlockedStatus)
|
|
74
94
|
|
|
@@ -251,6 +271,20 @@ class PostgreSQLPeer(RelationState):
|
|
|
251
271
|
return
|
|
252
272
|
self.relation.data[self.unit]["stanza"] = value
|
|
253
273
|
|
|
274
|
+
@property
|
|
275
|
+
def s3_initialization_start(self) -> str | None:
|
|
276
|
+
"""Get the s3-initialization-start timestamp from the unit peer relation data."""
|
|
277
|
+
if not self.relation:
|
|
278
|
+
return None
|
|
279
|
+
return self.relation.data[self.unit].get("s3-initialization-start")
|
|
280
|
+
|
|
281
|
+
@s3_initialization_start.setter
|
|
282
|
+
def s3_initialization_start(self, value: str) -> None:
|
|
283
|
+
"""Set the s3-initialization-start timestamp in the unit peer relation data."""
|
|
284
|
+
if not self.relation:
|
|
285
|
+
return
|
|
286
|
+
self.relation.data[self.unit]["s3-initialization-start"] = value
|
|
287
|
+
|
|
254
288
|
@property
|
|
255
289
|
def s3_initialization_done(self) -> str | None:
|
|
256
290
|
"""Get the s3-initialization-done marker from the unit peer relation data."""
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Copyright 2026 Canonical Ltd.
|
|
3
|
+
# See LICENSE file for licensing details.
|
|
4
|
+
|
|
5
|
+
"""Handler for backup-related charm events.
|
|
6
|
+
|
|
7
|
+
The charm is the composition root: the handler receives the managers and owns
|
|
8
|
+
only the observers and the mapping of manager results/exceptions onto action
|
|
9
|
+
results, unit statuses and defers. No business logic lives here.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
from typing import TYPE_CHECKING
|
|
14
|
+
|
|
15
|
+
from ops import MaintenanceStatus, Object
|
|
16
|
+
from ops.charm import ActionEvent
|
|
17
|
+
from ops.pebble import ExecError
|
|
18
|
+
from tenacity import RetryError
|
|
19
|
+
|
|
20
|
+
from single_kernel_postgresql.config.exceptions import ListBackupsError
|
|
21
|
+
from single_kernel_postgresql.managers.backup import BackupManager
|
|
22
|
+
from single_kernel_postgresql.utils.backup import STANDBY_CLUSTER_LIST_BACKUPS_ERROR_MESSAGE
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from single_kernel_postgresql.charms.abstract_charm import AbstractPostgreSQLCharm
|
|
26
|
+
from single_kernel_postgresql.core.state import CharmState
|
|
27
|
+
from single_kernel_postgresql.managers.restore import RestoreManager
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class BackupEventsHandler(Object):
|
|
33
|
+
"""Class implementing backup-related charm events handling."""
|
|
34
|
+
|
|
35
|
+
backup_manager: BackupManager
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
charm: "AbstractPostgreSQLCharm",
|
|
40
|
+
state: "CharmState",
|
|
41
|
+
backup_manager: BackupManager,
|
|
42
|
+
restore_manager: "RestoreManager",
|
|
43
|
+
) -> None:
|
|
44
|
+
super().__init__(charm, key="backup_events")
|
|
45
|
+
self.charm = charm
|
|
46
|
+
self.state = state
|
|
47
|
+
self.backup_manager = backup_manager
|
|
48
|
+
self.restore_manager = restore_manager
|
|
49
|
+
|
|
50
|
+
# s3 relation handles the config options for s3 backups
|
|
51
|
+
self.framework.observe(
|
|
52
|
+
self.state.s3_requirer.on.credentials_changed, self._on_s3_credential_changed
|
|
53
|
+
)
|
|
54
|
+
# When the leader unit is being removed, s3_client.on.credentials_gone is performed
|
|
55
|
+
# on it (and only on it). After a new leader is elected, the S3 connection must be
|
|
56
|
+
# reinitialized.
|
|
57
|
+
self.framework.observe(self.charm.on.leader_elected, self._on_s3_credential_changed)
|
|
58
|
+
self.framework.observe(
|
|
59
|
+
self.state.s3_requirer.on.credentials_gone, self._on_s3_credential_gone
|
|
60
|
+
)
|
|
61
|
+
self.framework.observe(self.charm.on.create_backup_action, self._on_create_backup_action)
|
|
62
|
+
self.framework.observe(self.charm.on.list_backups_action, self._on_list_backups_action)
|
|
63
|
+
self.framework.observe(self.charm.on.restore_action, self._on_restore_action)
|
|
64
|
+
|
|
65
|
+
def _on_s3_credential_changed(self, event) -> None:
|
|
66
|
+
"""Call the stanza initialization when the credentials or the connection info change."""
|
|
67
|
+
proceed, defer = self.backup_manager.credential_changed_checks()
|
|
68
|
+
if not proceed:
|
|
69
|
+
# The charms defer on every branch that deferred there: credentials
|
|
70
|
+
# arriving too early, mid-PITR, mid-restore, or on a unit that
|
|
71
|
+
# cannot initialise yet must be retried later, not dropped.
|
|
72
|
+
if defer:
|
|
73
|
+
event.defer()
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
# Start the pgBackRest service for the check_stanza to be successful. It's
|
|
77
|
+
# required to run on all the units if the tls is enabled.
|
|
78
|
+
try:
|
|
79
|
+
self.backup_manager.start_stop_pgbackrest_service()
|
|
80
|
+
except RetryError:
|
|
81
|
+
logger.debug("Cannot initialise service yet.")
|
|
82
|
+
event.defer()
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
self.backup_manager.reset_s3_initialization_markers()
|
|
86
|
+
|
|
87
|
+
if self.backup_manager.is_primary and self.state.peer.s3_initialization_done is None:
|
|
88
|
+
self.backup_manager.initialise_s3_repository()
|
|
89
|
+
|
|
90
|
+
def _on_s3_credential_gone(self, _) -> None:
|
|
91
|
+
"""Clear the stanza and the S3 initialization markers when credentials are gone."""
|
|
92
|
+
self.backup_manager.clear_s3_state()
|
|
93
|
+
|
|
94
|
+
def _on_create_backup_action(self, event: ActionEvent) -> None:
|
|
95
|
+
"""Request that pgBackRest creates a backup."""
|
|
96
|
+
backup_type = event.params.get("type", "full")
|
|
97
|
+
# The manager owns the creating-backup status bracket: the charms set
|
|
98
|
+
# Maintenance only after validation and the metadata upload, and reset
|
|
99
|
+
# Active only once the run started — never on a gate failure.
|
|
100
|
+
ok, message = self.backup_manager.create_backup(backup_type)
|
|
101
|
+
if not ok:
|
|
102
|
+
logger.error(f"Backup failed: {message}")
|
|
103
|
+
event.fail(message)
|
|
104
|
+
else:
|
|
105
|
+
event.set_results({"backup-status": "backup created"})
|
|
106
|
+
|
|
107
|
+
def _on_list_backups_action(self, event: ActionEvent) -> None:
|
|
108
|
+
"""List the previously created backups."""
|
|
109
|
+
if self.backup_manager.is_standby_cluster:
|
|
110
|
+
logger.warning(STANDBY_CLUSTER_LIST_BACKUPS_ERROR_MESSAGE)
|
|
111
|
+
event.fail(STANDBY_CLUSTER_LIST_BACKUPS_ERROR_MESSAGE)
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
are_backup_settings_ok, validation_message = self.backup_manager.are_backup_settings_ok()
|
|
115
|
+
if not are_backup_settings_ok:
|
|
116
|
+
logger.warning(validation_message)
|
|
117
|
+
event.fail(validation_message)
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
formatted_list = self.backup_manager.generate_backup_list_output()
|
|
122
|
+
event.set_results({"backups": formatted_list})
|
|
123
|
+
except (ListBackupsError, ExecError) as e:
|
|
124
|
+
# The K8s charm catches ExecError here (pgbackrest failures raise
|
|
125
|
+
# instead of returning a code); the message matches both charms.
|
|
126
|
+
logger.exception(e)
|
|
127
|
+
event.fail(f"Failed to list PostgreSQL backups with error: {e!s}")
|
|
128
|
+
|
|
129
|
+
def _on_restore_action(self, event: ActionEvent) -> None:
|
|
130
|
+
"""Request that pgBackRest restores a backup."""
|
|
131
|
+
backup_id = event.params.get("backup-id")
|
|
132
|
+
restore_to_time = event.params.get("restore-to-time")
|
|
133
|
+
logger.info(
|
|
134
|
+
f"A restore with backup-id {backup_id}"
|
|
135
|
+
f"{f' to time point {restore_to_time}' if restore_to_time else ''}"
|
|
136
|
+
f" has been requested on the unit"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
can_restore, error_message = self.restore_manager.pre_restore_checks(
|
|
140
|
+
backup_id, restore_to_time
|
|
141
|
+
)
|
|
142
|
+
if not can_restore:
|
|
143
|
+
event.fail(error_message)
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
# Validate the provided backup id and restore to time.
|
|
147
|
+
logger.info("Validating provided backup-id and restore-to-time")
|
|
148
|
+
try:
|
|
149
|
+
restore_stanza_timeline, is_backup_id_real, error_message = (
|
|
150
|
+
self.restore_manager.resolve_restore_target(backup_id, restore_to_time)
|
|
151
|
+
)
|
|
152
|
+
except ListBackupsError as e:
|
|
153
|
+
logger.exception(e)
|
|
154
|
+
error_message = "Failed to retrieve backups list"
|
|
155
|
+
logger.error(f"Restore failed: {error_message}")
|
|
156
|
+
event.fail(error_message)
|
|
157
|
+
return
|
|
158
|
+
if restore_stanza_timeline is None:
|
|
159
|
+
logger.error(f"Restore failed: {error_message}")
|
|
160
|
+
event.fail(error_message)
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
self.charm.set_unit_status(MaintenanceStatus("restoring backup"))
|
|
164
|
+
ok, message = self.restore_manager.restore(
|
|
165
|
+
restore_stanza_timeline, backup_id, restore_to_time, is_backup_id_real
|
|
166
|
+
)
|
|
167
|
+
if not ok:
|
|
168
|
+
logger.error(f"Restore failed: {message}")
|
|
169
|
+
event.fail(message)
|
|
170
|
+
return
|
|
171
|
+
|
|
172
|
+
event.set_results({"restore-status": "restore started"})
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import logging
|
|
8
8
|
from datetime import datetime
|
|
9
|
-
from typing import TYPE_CHECKING
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
10
|
|
|
11
11
|
from ops import (
|
|
12
12
|
InstallEvent,
|
|
@@ -30,12 +30,11 @@ from single_kernel_postgresql.managers.cluster import ClusterManager
|
|
|
30
30
|
from single_kernel_postgresql.managers.config import ConfigManager
|
|
31
31
|
from single_kernel_postgresql.managers.patroni import PatroniManager
|
|
32
32
|
from single_kernel_postgresql.managers.tls import TLSManager
|
|
33
|
-
from single_kernel_postgresql.workload.base import BaseWorkload
|
|
33
|
+
from single_kernel_postgresql.workload.base import BaseWorkload, PebbleLayerSpec
|
|
34
34
|
from single_kernel_postgresql.workload.vm import VMWorkload
|
|
35
35
|
|
|
36
36
|
if TYPE_CHECKING:
|
|
37
37
|
from single_kernel_postgresql.charms.abstract_charm import AbstractPostgreSQLCharm
|
|
38
|
-
from single_kernel_postgresql.charms.k8s_charm import PostgreSQLK8sCharm
|
|
39
38
|
|
|
40
39
|
logger = logging.getLogger(__name__)
|
|
41
40
|
|
|
@@ -124,7 +123,6 @@ class PostgreSQLEventsHandler(Object):
|
|
|
124
123
|
|
|
125
124
|
def _on_postgresql_pebble_ready(self, event: WorkloadEvent) -> None:
|
|
126
125
|
"""Event handler for PostgreSQL container on PebbleReadyEvent."""
|
|
127
|
-
charm = cast("PostgreSQLK8sCharm", self.charm)
|
|
128
126
|
# TODO: Safeguard against refresh
|
|
129
127
|
if self.state.endpoint in self.state.endpoints:
|
|
130
128
|
# TODO: Fix pod by adding services
|
|
@@ -152,7 +150,7 @@ class PostgreSQLEventsHandler(Object):
|
|
|
152
150
|
self.tls_manager.configure_internal_peer_cert()
|
|
153
151
|
|
|
154
152
|
# Start the database service
|
|
155
|
-
|
|
153
|
+
self.workload.update_pebble_layers(PebbleLayerSpec.from_state(self.state))
|
|
156
154
|
|
|
157
155
|
# Assert the member is up and running before marking it as initialised.
|
|
158
156
|
if not self.patroni_manager.member_started:
|
|
@@ -192,7 +190,7 @@ class PostgreSQLEventsHandler(Object):
|
|
|
192
190
|
|
|
193
191
|
# Doesn't try to bootstrap the cluster if it's in a blocked state
|
|
194
192
|
# caused, for example, because a failed installation of packages.
|
|
195
|
-
if self.state.peer.
|
|
193
|
+
if self.state.peer.is_blocked_status:
|
|
196
194
|
logger.debug("Early exit on_start: Unit blocked")
|
|
197
195
|
return False
|
|
198
196
|
|