Skip to content

Commit 0cf8b71

Browse files
Address PR feedback
1 parent 2e276ba commit 0cf8b71

File tree

10 files changed

+67
-81
lines changed

10 files changed

+67
-81
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.

pyproject.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ authors = []
1010

1111
[tool.poetry.dependencies]
1212
python = "^3.8.1" # ^3.8.1 required by flake8
13-
ops = "^2.8.0"
13+
ops = "^2.12.0"
1414
tenacity = "^8.2.3"
1515
poetry-core = "^1.7.0"
1616
jinja2 = "^3.1.2"
1717
requests = "^2.31.0"
1818

1919
[tool.poetry.group.charm-libs.dependencies]
2020
# data_platform_libs/v0/data_interfaces.py
21-
ops = ">=2.8.0"
21+
ops = ">=2.12.0"
2222
# tls_certificates_interface/v1/tls_certificates.py
2323
cryptography = ">=42.0.5"
2424
jsonschema = "*"
@@ -52,7 +52,7 @@ pytest = "^7.4.0"
5252
pytest-xdist = "^3.3.1"
5353
pytest-cov = "^4.1.0"
5454
ops-scenario = "^5.4.1"
55-
ops = ">=2.8.0"
55+
ops = ">=2.12.0"
5656
pytest-mock = "^3.11.1"
5757

5858
[tool.poetry.group.integration.dependencies]
@@ -64,7 +64,7 @@ pytest-github-secrets = {git = "https://github.com/canonical/data-platform-workf
6464
juju = "3.2.0.1"
6565
mysql-connector-python = "~8.0.33"
6666
tenacity = "^8.2.2"
67-
ops = ">=2.8.0"
67+
ops = ">=2.12.0"
6868
pytest-mock = "^3.11.1"
6969

7070

src/abstract_charm.py

Lines changed: 11 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,9 @@
55

66
import abc
77
import logging
8-
import socket
98
import typing
109

1110
import ops
12-
import tenacity
1311

1412
import container
1513
import lifecycle
@@ -31,6 +29,8 @@ class MySQLRouterCharm(ops.CharmBase, abc.ABC):
3129

3230
_READ_WRITE_PORT = 6446
3331
_READ_ONLY_PORT = 6447
32+
_READ_WRITE_X_PORT = 6448
33+
_READ_ONLY_X_PORT = 6449
3434

3535
def __init__(self, *args) -> None:
3636
super().__init__(*args)
@@ -111,32 +111,28 @@ def _exposed_read_only_endpoint(self) -> str:
111111

112112
@property
113113
@abc.abstractmethod
114+
def is_exposed(self) -> typing.Optional[bool]:
115+
"""Whether router is exposed externally"""
116+
117+
@property
114118
def _tls_certificate_saved(self) -> bool:
115119
"""Whether a TLS certificate is available to use"""
120+
return self.tls.certificate_saved
116121

117122
@property
118-
@abc.abstractmethod
119123
def _tls_key(self) -> typing.Optional[str]:
120124
"""Custom TLS key"""
125+
return self.tls.key
121126

122127
@property
123-
@abc.abstractmethod
124128
def _tls_certificate_authority(self) -> typing.Optional[str]:
125129
"""Custom TLS certificate authority"""
130+
return self.tls.certificate_authority
126131

127132
@property
128-
@abc.abstractmethod
129133
def _tls_certificate(self) -> typing.Optional[str]:
130134
"""Custom TLS certificate"""
131-
132-
@property
133-
@abc.abstractmethod
134-
def _substrate(self) -> str:
135-
"""Returns the substrate of the charm: vm or k8s"""
136-
137-
@abc.abstractmethod
138-
def is_exposed(self, relation=None) -> typing.Optional[bool]:
139-
"""Whether router is exposed externally"""
135+
return self.tls.certificate
140136

141137
def _cos_exporter_config(self, event) -> typing.Optional[relations.cos.ExporterConfig]:
142138
"""Returns the exporter config for MySQLRouter exporter if cos relation exists"""
@@ -212,37 +208,12 @@ def set_status(self, *, event, app=True, unit=True) -> None:
212208
self.unit.status = self._determine_unit_status(event=event)
213209
logger.debug(f"Set unit status to {self.unit.status}")
214210

211+
@abc.abstractmethod
215212
def wait_until_mysql_router_ready(self) -> None:
216213
"""Wait until a connection to MySQL Router is possible.
217214
218215
Retry every 5 seconds for up to 30 seconds.
219216
"""
220-
logger.debug("Waiting until MySQL Router is ready")
221-
self.unit.status = ops.MaintenanceStatus("MySQL Router starting")
222-
try:
223-
for attempt in tenacity.Retrying(
224-
reraise=True,
225-
stop=tenacity.stop_after_delay(30),
226-
wait=tenacity.wait_fixed(5),
227-
):
228-
with attempt:
229-
if self._substrate == "k8s" or self.is_exposed():
230-
for port in (6446, 6447):
231-
with socket.socket() as s:
232-
assert s.connect_ex(("localhost", port)) == 0
233-
else:
234-
for socket_file in (
235-
"/run/mysqlrouter/mysql.sock",
236-
"/run/mysqlrouter/mysqlro.sock",
237-
):
238-
assert self._container.path(socket_file).exists()
239-
with socket.socket(socket.AF_UNIX) as s:
240-
assert s.connect_ex(str(self._container.path(socket_file))) == 0
241-
except AssertionError:
242-
logger.exception("Unable to connect to MySQL Router")
243-
raise
244-
else:
245-
logger.debug("MySQL Router is ready")
246217

247218
@abc.abstractmethod
248219
def _reconcile_node_port(self, event) -> None:

src/machine_charm.py

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
"""MySQL Router machine charm"""
88

99
import logging
10+
import socket
1011
import typing
1112

1213
import ops
14+
import tenacity
1315

1416
import abstract_charm
1517
import machine_logrotate
@@ -82,41 +84,52 @@ def _exposed_read_only_endpoint(self) -> str:
8284
return f"{self.host_address}:{self._READ_ONLY_PORT}"
8385

8486
@property
85-
def _tls_certificate_saved(self) -> bool:
86-
"""Whether a TLS certificate is available to use"""
87-
return self.tls.certificate_saved
88-
89-
@property
90-
def _tls_key(self) -> typing.Optional[str]:
91-
"""Custom TLS key"""
92-
return self.tls.key
93-
94-
@property
95-
def _tls_certificate(self) -> typing.Optional[str]:
96-
"""Custom TLS certificate"""
97-
return self.tls.certificate
98-
99-
@property
100-
def _tls_certificate_authority(self) -> typing.Optional[str]:
101-
return self.tls.certificate_authority
102-
103-
def is_exposed(self, relation=None) -> typing.Optional[bool]:
104-
return self._database_provides.is_exposed
87+
def is_exposed(self) -> typing.Optional[bool]:
88+
return self._database_provides.external_connectivity
10589

10690
def _reconcile_node_port(self, event) -> None:
10791
"""Only applies to Kubernetes charm, so no-op."""
10892
pass
10993

11094
def _reconcile_ports(self) -> None:
111-
if self.is_exposed():
95+
if self.is_exposed:
11296
ports = [self._READ_WRITE_PORT, self._READ_ONLY_PORT]
11397
else:
11498
ports = []
11599
self.unit.set_ports(*ports)
116100

117-
@property
118-
def _substrate(self) -> str:
119-
return "vm"
101+
def wait_until_mysql_router_ready(self) -> None:
102+
logger.debug("Waiting until MySQL Router is ready")
103+
self.unit.status = ops.MaintenanceStatus("MySQL Router starting")
104+
try:
105+
for attempt in tenacity.Retrying(
106+
reraise=True,
107+
stop=tenacity.stop_after_delay(30),
108+
wait=tenacity.wait_fixed(5),
109+
):
110+
with attempt:
111+
if self.is_exposed:
112+
for port in (
113+
self._READ_WRITE_PORT,
114+
self._READ_ONLY_PORT,
115+
self._READ_WRITE_X_PORT,
116+
self._READ_ONLY_X_PORT,
117+
):
118+
with socket.socket() as s:
119+
assert s.connect_ex(("localhost", port)) == 0
120+
else:
121+
for socket_file in (
122+
"/run/mysqlrouter/mysql.sock",
123+
"/run/mysqlrouter/mysqlro.sock",
124+
):
125+
assert self._container.path(socket_file).exists()
126+
with socket.socket(socket.AF_UNIX) as s:
127+
assert s.connect_ex(str(self._container.path(socket_file))) == 0
128+
except AssertionError:
129+
logger.exception("Unable to connect to MySQL Router")
130+
raise
131+
else:
132+
logger.debug("MySQL Router is ready")
120133

121134
# =======================
122135
# Handlers

src/machine_workload.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class AuthenticatedMachineWorkload(workload.AuthenticatedWorkload):
2020
# TODO python3.10 min version: Use `list` instead of `typing.List`
2121
def _get_bootstrap_command(self, password: str) -> typing.List[str]:
2222
command = super()._get_bootstrap_command(password)
23-
if self._charm.is_exposed():
23+
if self._charm.is_exposed:
2424
command.extend(
2525
[
2626
"--conf-bind-address",
@@ -68,5 +68,5 @@ def _update_configured_socket_file_locations_and_bind_address(self) -> None:
6868

6969
def _bootstrap_router(self, *, tls: bool) -> None:
7070
super()._bootstrap_router(tls=tls)
71-
if not self._charm.is_exposed():
71+
if not self._charm.is_exposed:
7272
self._update_configured_socket_file_locations_and_bind_address()

src/relations/database_providers_wrapper.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ def __init__(
3939
)
4040

4141
@property
42-
def is_exposed(self) -> bool:
42+
def external_connectivity(self) -> bool:
4343
"""Whether the relation is exposed"""
44-
return self._database_provides.is_exposed
44+
return self._database_provides.external_connectivity
4545

4646
def reconcile_users(
4747
self,

src/relations/database_provides.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,6 @@ class RelationEndpoint:
181181

182182
def __init__(self, charm_: "abstract_charm.MySQLRouterCharm") -> None:
183183
self._interface = data_interfaces.DatabaseProvides(charm_, relation_name=self._NAME)
184-
self._charm = charm_
185184

186185
charm_.framework.observe(charm_.on[self._NAME].relation_created, charm_.reconcile)
187186
charm_.framework.observe(self._interface.on.database_requested, charm_.reconcile)
@@ -201,7 +200,7 @@ def _shared_users(self) -> typing.List[_RelationWithSharedUser]:
201200
return shared_users
202201

203202
@property
204-
def is_exposed(self) -> bool:
203+
def external_connectivity(self) -> bool:
205204
"""Whether the relation is exposed."""
206205
relation_data = self._interface.fetch_relation_data(fields=["external-node-connectivity"])
207206
return any(

src/relations/tls.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ def save_certificate(self, event: tls_certificates.CertificateAvailableEvent) ->
114114
def _generate_csr(self, key: bytes) -> bytes:
115115
"""Generate certificate signing request (CSR)."""
116116
sans_ip = ["127.0.0.1"] # needed for the HTTP server when related with COS
117-
if self._charm.is_exposed():
117+
if self._charm.is_exposed:
118118
sans_ip.append(self._charm.host_address)
119119

120120
return tls_certificates.generate_csr(

src/workload.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,8 @@ def _tls_config_file_data(self) -> str:
101101
@property
102102
def _custom_certificate(self) -> typing.Optional[str]:
103103
"""Whether custom TLS certs are enabled for MySQL Router"""
104-
if not self._tls_key_file.exists() or not self._tls_certificate_file.exists():
105-
return None
106-
return self._tls_certificate_file.read_text()
104+
if self._tls_key_file.exists() and self._tls_certificate_file.exists():
105+
return self._tls_certificate_file.read_text()
107106

108107
def cleanup_monitoring_user(self) -> None:
109108
"""Clean up router REST API user for mysqlrouter exporter."""
@@ -333,6 +332,7 @@ def reconcile(
333332
"`key`, `certificate`, and `certificate_authority` arguments required when tls=True"
334333
)
335334

335+
# `self._custom_certificate` will change after we enable/disable TLS
336336
custom_certificate = self._custom_certificate
337337
if tls:
338338
self._enable_tls(
@@ -348,7 +348,10 @@ def reconcile(
348348
# If the host or port changes, MySQL Router will receive topology change
349349
# notifications from MySQL.
350350
# Therefore, if the host or port changes, we do not need to restart MySQL Router.
351-
if not self._container.mysql_router_service_enabled:
351+
is_charm_exposed = self._charm.is_exposed
352+
socket_file_exists = self._container.path("/run/mysqlrouter/mysql.sock").exists()
353+
require_rebootstrap = is_charm_exposed == socket_file_exists
354+
if not self._container.mysql_router_service_enabled or require_rebootstrap:
352355
logger.debug("Enabling MySQL Router service")
353356
self._cleanup_after_upgrade_or_potential_container_restart()
354357
# create an empty credentials file, if the file does not exist

tests/unit/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def disable_tenacity_retry(monkeypatch):
3333
@pytest.fixture(autouse=True)
3434
def patch(monkeypatch):
3535
monkeypatch.setattr(
36-
"abstract_charm.MySQLRouterCharm.wait_until_mysql_router_ready",
36+
"machine_charm.MachineSubordinateRouterCharm.wait_until_mysql_router_ready",
3737
lambda *args, **kwargs: None,
3838
)
3939
monkeypatch.setattr("workload.AuthenticatedWorkload._router_username", "")

0 commit comments

Comments
 (0)