Skip to content

Commit 56c21ba

Browse files
Sync with vm
1 parent 99c2acc commit 56c21ba

File tree

6 files changed

+137
-31
lines changed

6 files changed

+137
-31
lines changed

poetry.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/abstract_charm.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ class MySQLRouterCharm(ops.CharmBase, abc.ABC):
6161
_READ_ONLY_X_PORT = 6449
6262

6363
refresh: charm_refresh.Common
64+
# Whether `reconcile` method is allowed to run
65+
# `False` if `charm_refresh.UnitTearingDown` or `charm_refresh.PeerRelationNotReady` raised
66+
# Most of the charm code should not run if either of those exceptions is raised
67+
# However, some charm libs (i.e. data-platform-libs) will break if they do not receive every
68+
# event they expect (e.g. relation-created)
69+
_reconcile_allowed: bool
6470

6571
def __init__(self, *args) -> None:
6672
super().__init__(*args)
@@ -173,9 +179,14 @@ def _cos_exporter_config(self, event) -> typing.Optional[relations.cos.ExporterC
173179
if cos_relation_exists:
174180
return self._cos_relation.exporter_user_config
175181

176-
def get_workload(self, *, event):
177-
"""MySQL Router workload"""
178-
if self.refresh.workload_allowed_to_start and (
182+
def get_workload(self, *, event, refresh: charm_refresh.Common = None):
183+
"""MySQL Router workload
184+
185+
Pass `refresh` if `self.refresh` is not set
186+
"""
187+
if refresh is None:
188+
refresh = self.refresh
189+
if refresh.workload_allowed_to_start and (
179190
connection_info := self._database_requires.get_connection_info(event=event)
180191
):
181192
return self._running_workload_type(
@@ -280,18 +291,29 @@ def _update_endpoints(self) -> None:
280291

281292
def reconcile(self, event=None) -> None: # noqa: C901
282293
"""Handle most events."""
294+
if not self._reconcile_allowed:
295+
logger.debug("Reconcile not allowed")
296+
return
283297
workload_ = self.get_workload(event=event)
284298
logger.debug(
285299
"State of reconcile "
286300
f"{self._unit_lifecycle.authorized_leader=}, "
287301
f"{isinstance(workload_, workload.RunningWorkload)=}, "
288302
f"{workload_.container_ready=}, "
289-
f"{self.refresh.workload_allowed_to_start= }, "
303+
f"{self.refresh.workload_allowed_to_start=}, "
290304
f"{self._database_requires.is_relation_breaking(event)=}, "
291305
f"{self._database_requires.does_relation_exist()=}, "
292306
f"{self.refresh.in_progress=}, "
293307
f"{self._cos_relation.is_relation_breaking(event)=}"
294308
)
309+
if isinstance(self.refresh, charm_refresh.Machines):
310+
workload_.install(
311+
unit=self.unit,
312+
model_uuid=self.model.uuid,
313+
snap_revision=self.refresh.pinned_snap_revision,
314+
refresh=self.refresh,
315+
)
316+
self.unit.set_workload_version(workload_.version)
295317

296318
# only in machine charms
297319
if self._ha_cluster:

src/charm.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,6 @@ def __init__(self, *args) -> None:
9090
self._peer_data = relations.secrets.RelationSecrets(self, self._PEER_RELATION_NAME)
9191

9292
self.framework.observe(self.on.install, self._on_install)
93-
self.framework.observe(
94-
self.on[rock.CONTAINER_NAME].pebble_ready, self._on_workload_container_pebble_ready
95-
)
9693
try:
9794
self.refresh = charm_refresh.Kubernetes(
9895
KubernetesRouterRefresh(
@@ -105,14 +102,16 @@ def __init__(self, *args) -> None:
105102
# MySQL server charm will clean up users & router metadata when the MySQL Router app or
106103
# unit(s) tear down
107104
self.unit.status = ops.MaintenanceStatus("Tearing down")
108-
exit()
105+
self._reconcile_allowed = False
109106
except charm_refresh.KubernetesJujuAppNotTrusted:
110-
exit()
107+
self._reconcile_allowed = False
111108
except charm_refresh.PeerRelationNotReady:
112109
self.unit.status = ops.MaintenanceStatus("Waiting for peer relation")
113110
if self.unit.is_leader():
114111
self.app.status = ops.MaintenanceStatus("Waiting for peer relation")
115-
exit()
112+
self._reconcile_allowed = False
113+
else:
114+
self._reconcile_allowed = True
116115

117116
@property
118117
def _subordinate_relation_endpoint_names(self) -> typing.Optional[typing.Iterable[str]]:
@@ -435,9 +434,6 @@ def _on_install(self, _) -> None:
435434
for port in (self._READ_WRITE_PORT, self._READ_ONLY_PORT, 6448, 6449):
436435
self.unit.open_port("tcp", port)
437436

438-
def _on_workload_container_pebble_ready(self, _) -> None:
439-
self.unit.set_workload_version(self.get_workload(event=None).version)
440-
441437

442438
if __name__ == "__main__":
443439
ops.main.main(KubernetesRouterCharm)

src/container.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import subprocess
99
import typing
1010

11+
import charm_refresh
1112
import ops
1213

1314
if typing.TYPE_CHECKING:
@@ -61,6 +62,13 @@ def __init__(
6162
super().__init__(returncode=returncode, cmd=cmd, output=output, stderr=stderr)
6263

6364

65+
class RefreshFailed(Exception):
66+
"""Snap failed to refresh. Previous snap revision is still installed
67+
68+
Only applies to machine charm
69+
"""
70+
71+
6472
class Container(abc.ABC):
6573
"""Workload container (snap or rock)"""
6674

@@ -163,11 +171,33 @@ def update_mysql_router_exporter_service(
163171
"`key`, `certificate` and `certificate_authority` required when tls=True"
164172
)
165173

174+
@staticmethod
175+
@abc.abstractmethod
176+
def install(
177+
*, unit: ops.Unit, model_uuid: str, snap_revision: str, refresh: charm_refresh.Machines
178+
) -> None:
179+
"""Ensure snap is installed by this charm
180+
181+
Only applies to machine charm
182+
183+
If snap is not installed, install it
184+
If snap is installed, check that it was installed by this charm & raise an exception otherwise
185+
186+
Automatically retries if snap installation fails
187+
"""
188+
189+
@staticmethod
166190
@abc.abstractmethod
167-
def upgrade(self, unit: ops.Unit) -> None:
168-
"""Upgrade container version
191+
def refresh(
192+
*, unit: ops.Unit, model_uuid: str, snap_revision: str, refresh: charm_refresh.Machines
193+
) -> None:
194+
"""Refresh snap
169195
170196
Only applies to machine charm
197+
198+
If snap refresh fails and previous revision is still installed, raises `RefreshFailed`
199+
200+
Does not automatically retry if snap installation fails
171201
"""
172202

173203
@abc.abstractmethod

src/rock.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,12 @@ def update_mysql_router_exporter_service(
196196
else:
197197
self._container.stop(self._EXPORTER_SERVICE_NAME)
198198

199-
def upgrade(self, unit: ops.Unit) -> None:
199+
@staticmethod
200+
def install(*_, **__) -> None:
201+
raise Exception("Not supported on Kubernetes")
202+
203+
@staticmethod
204+
def refresh(*_, **__) -> None:
200205
raise Exception("Not supported on Kubernetes")
201206

202207
def update_logrotate_executor_service(self, *, enabled: bool) -> None:

src/workload.py

Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import string
1212
import typing
1313

14+
import charm_refresh
1415
import ops
1516
import requests
1617
import tenacity
@@ -76,16 +77,45 @@ def version(self) -> str:
7677
return component
7778
return ""
7879

79-
def upgrade(
80-
self, *, event, unit: ops.Unit, tls: bool, exporter_config: "relations.cos.ExporterConfig"
80+
def install(
81+
self,
82+
*,
83+
unit: ops.Unit,
84+
model_uuid: str,
85+
snap_revision: str,
86+
refresh: charm_refresh.Machines,
87+
) -> None:
88+
"""Ensure snap is installed by this charm
89+
90+
Only applies to machine charm
91+
92+
If snap is not installed, install it
93+
If snap is installed, check that it was installed by this charm & raise an exception otherwise
94+
95+
Automatically retries if snap installation fails
96+
"""
97+
self._container.install(
98+
unit=unit, model_uuid=model_uuid, snap_revision=snap_revision, refresh=refresh
99+
)
100+
101+
def refresh(
102+
self,
103+
*,
104+
event,
105+
unit: ops.Unit,
106+
model_uuid: str,
107+
snap_revision: str,
108+
refresh: charm_refresh.Machines,
109+
tls: bool,
110+
exporter_config: "relations.cos.ExporterConfig",
81111
) -> None:
82-
"""Upgrade MySQL Router.
112+
"""Refresh MySQL Router
83113
84114
Only applies to machine charm
85115
"""
86-
logger.debug("Upgrading MySQL Router")
87-
self._container.upgrade(unit=unit)
88-
logger.debug("Upgraded MySQL Router")
116+
self._container.refresh(
117+
unit=unit, model_uuid=model_uuid, snap_revision=snap_revision, refresh=refresh
118+
)
89119

90120
@property
91121
def _tls_config_file_data(self) -> str:
@@ -420,8 +450,16 @@ def status(self) -> typing.Optional[ops.StatusBase]:
420450
"Router was manually removed from MySQL ClusterSet. Remove & re-deploy unit"
421451
)
422452

423-
def upgrade(
424-
self, *, event, unit: ops.Unit, tls: bool, exporter_config: "relations.cos.ExporterConfig"
453+
def refresh(
454+
self,
455+
*,
456+
event,
457+
unit: ops.Unit,
458+
model_uuid: str,
459+
snap_revision: str,
460+
refresh: charm_refresh.Machines,
461+
tls: bool,
462+
exporter_config: "relations.cos.ExporterConfig",
425463
) -> None:
426464
enabled = self._container.mysql_router_service_enabled
427465
exporter_enabled = self._container.mysql_router_exporter_service_enabled
@@ -430,12 +468,27 @@ def upgrade(
430468
if enabled:
431469
logger.debug("Disabling MySQL Router service before upgrade")
432470
self._disable_router()
433-
super().upgrade(event=event, unit=unit, tls=tls, exporter_config=exporter_config)
434-
if enabled:
435-
logger.debug("Re-enabling MySQL Router service after upgrade")
436-
self._enable_router(event=event, tls=tls, unit_name=unit.name)
437-
if exporter_enabled:
438-
self._enable_exporter(tls=tls, exporter_config=exporter_config)
471+
try:
472+
super().refresh(
473+
event=event,
474+
unit=unit,
475+
model_uuid=model_uuid,
476+
snap_revision=snap_revision,
477+
refresh=refresh,
478+
tls=tls,
479+
exporter_config=exporter_config,
480+
)
481+
except container.RefreshFailed:
482+
message = "Re-enabling MySQL Router service after failed snap refresh"
483+
raise
484+
else:
485+
message = "Re-enabling MySQL Router service after refresh"
486+
finally:
487+
if enabled:
488+
logger.debug(message)
489+
self._enable_router(event=event, tls=tls, unit_name=unit.name)
490+
if exporter_enabled:
491+
self._enable_exporter(tls=tls, exporter_config=exporter_config)
439492

440493
def _wait_until_http_server_authenticates(self) -> None:
441494
"""Wait until active connection with router HTTP server using monitoring credentials."""

0 commit comments

Comments
 (0)