Skip to content

Commit

Permalink
Bootstrap new units as soon as possible instead of doing so in update…
Browse files Browse the repository at this point in the history
…-status (#25)

* Minor improvements + add comments documenting methods

* Fix failing unit tests

* Fix lint warning

* Address PR feedback
  • Loading branch information
shayancanonical authored Feb 7, 2023
1 parent 1aea2de commit f2b502c
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 14 deletions.
35 changes: 25 additions & 10 deletions src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,12 @@ class MySQLRouterOperatorCharm(CharmBase):
def __init__(self, *args):
super().__init__(*args)

self.framework.observe(self.on.install, self._on_install)
self.framework.observe(self.on.leader_elected, self._on_leader_elected)
self.framework.observe(
self.on.mysql_router_pebble_ready, self._on_mysql_router_pebble_ready
)
self.framework.observe(self.on.update_status, self._on_update_status)
self.framework.observe(self.on[PEER].relation_changed, self._on_peer_relation_changed)

self.database_provides = DatabaseProvidesRelation(self)
self.database_requires = DatabaseRequiresRelation(self)
Expand Down Expand Up @@ -90,6 +91,11 @@ def read_only_endpoint(self):
# =======================

def _create_service(self, name: str, port: int) -> None:
"""Create a k8s service that is tied to pod-0.
The k8s service is tied to pod-0 so that the service is auto cleaned by
k8s when the last pod is scaled down.
"""
client = Client()
pod0 = client.get(
res=Pod,
Expand Down Expand Up @@ -184,9 +190,11 @@ def _bootstrap_mysqlrouter(self) -> bool:
return False

requires_data = json.loads(self.app_peer_data[MYSQL_ROUTER_REQUIRES_DATA])

endpoint_host, endpoint_port = requires_data["endpoints"].split(",")[0].split(":")
pebble_layer = self._mysql_router_layer(
requires_data["endpoints"].split(",")[0].split(":")[0],
"3306",
endpoint_host,
endpoint_port,
requires_data["username"],
self._get_secret("app", "database-password"),
)
Expand All @@ -202,10 +210,6 @@ def _bootstrap_mysqlrouter(self) -> bool:

self.unit_peer_data[UNIT_BOOTSTRAPPED] = "true"

# Triggers a peer_relation_changed event in the DatabaseProvidesRelation
num_units_bootstrapped = int(self.app_peer_data.get(NUM_UNITS_BOOTSTRAPPED, "0"))
self.app_peer_data[NUM_UNITS_BOOTSTRAPPED] = str(num_units_bootstrapped + 1)

return True

return False
Expand All @@ -214,6 +218,10 @@ def _bootstrap_mysqlrouter(self) -> bool:
# Handlers
# =======================

def _on_install(self, _) -> None:
"""Handle the install event."""
self.unit.status = WaitingStatus()

def _on_leader_elected(self, _) -> None:
"""Handle the leader elected event.
Expand All @@ -228,15 +236,14 @@ def _on_leader_elected(self, _) -> None:
logger.exception("Failed to create k8s service", exc_info=e)
self.unit.status = BlockedStatus("Failed to create k8s service")
return
self.unit.status = WaitingStatus()

def _on_mysql_router_pebble_ready(self, _) -> None:
"""Handle the mysql-router pebble ready event."""
if self._bootstrap_mysqlrouter():
self.unit.status = ActiveStatus()

def _on_update_status(self, _) -> None:
"""Handle the update status event.
def _on_peer_relation_changed(self, _) -> None:
"""Handle the peer relation changed event.
Bootstraps mysqlrouter if the relations exist, but pebble_ready event
fired before the requires relation was formed.
Expand All @@ -248,6 +255,14 @@ def _on_update_status(self, _) -> None:
):
self.unit.status = ActiveStatus()

if self.unit.is_leader():
num_units_bootstrapped = sum(
1
for unit in self._peers.units.union({self.unit})
if self._peers.data[unit].get(UNIT_BOOTSTRAPPED)
)
self.app_peer_data[NUM_UNITS_BOOTSTRAPPED] = str(num_units_bootstrapped)


if __name__ == "__main__":
main(MySQLRouterOperatorCharm)
1 change: 1 addition & 0 deletions src/relations/database_provides.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ def _on_database_requested(self, event: DatabaseRequestedEvent) -> None:
if not self.charm.unit.is_leader():
return

# Store data in databag to trigger DatabaseRequires initialization in database_requires.py
self.charm.app_peer_data[MYSQL_ROUTER_PROVIDES_DATA] = json.dumps(
{"database": event.database, "extra_user_roles": event.extra_user_roles}
)
Expand Down
5 changes: 5 additions & 0 deletions src/relations/database_requires.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,11 @@ def _on_database_created(self, event: DatabaseCreatedEvent) -> None:
self.charm.app_peer_data[MYSQL_DATABASE_CREATED] = "true"

def _on_endpoints_changed(self, event: DatabaseEndpointsChangedEvent) -> None:
"""Handle the endpoints changed event.
Update the endpoint in the MYSQL_ROUTER_REQUIRES_DATA so that future
bootstrapping units will not fail.
"""
if not self.charm.unit.is_leader():
return

Expand Down
8 changes: 4 additions & 4 deletions tests/unit/test_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from unittest.mock import MagicMock, patch

import lightkube
from ops.model import BlockedStatus, WaitingStatus
from ops.model import BlockedStatus, MaintenanceStatus
from ops.testing import Harness

from charm import MySQLRouterOperatorCharm
Expand All @@ -28,7 +28,7 @@ def test_on_peer_relation_created(self, _lightkube_client):

self.assertEqual(_lightkube_client.return_value.apply.call_count, 2)

self.assertTrue(isinstance(self.harness.model.unit.status, WaitingStatus))
self.assertTrue(isinstance(self.harness.model.unit.status, MaintenanceStatus))

@patch("charm.Client", return_value=MagicMock())
def test_on_peer_relation_created_delete_exception(self, _lightkube_client):
Expand All @@ -50,7 +50,7 @@ def test_on_peer_relation_created_delete_nothing(self, _lightkube_client):

self.charm.on.leader_elected.emit()

self.assertTrue(isinstance(self.harness.model.unit.status, WaitingStatus))
self.assertTrue(isinstance(self.harness.model.unit.status, MaintenanceStatus))

@patch("charm.Client", return_value=MagicMock())
def test_on_leader_elected_create_exception(self, _lightkube_client):
Expand All @@ -72,4 +72,4 @@ def test_on_leader_elected_create_existing_service(self, _lightkube_client):

self.charm.on.leader_elected.emit()

self.assertTrue(isinstance(self.harness.model.unit.status, WaitingStatus))
self.assertTrue(isinstance(self.harness.model.unit.status, MaintenanceStatus))

0 comments on commit f2b502c

Please sign in to comment.