Skip to content

Commit ac17e62

Browse files
Merge branch '6/edge' into no-private-ops
2 parents fc62fc7 + b2aa4b2 commit ac17e62

16 files changed

+448
-132
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ share/
1717

1818
/requirements.txt
1919
/requirements-last-build.txt
20+
/charm_internal_version

charmcraft.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ parts:
2020
override-build: |
2121
rustup default stable
2222
craftctl default
23+
files:
24+
plugin: dump
25+
source: .
26+
prime:
27+
- charm_internal_version
2328
bases:
2429
- build-on:
2530
- name: "ubuntu"

lib/charms/mongodb/v0/set_status.py

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from typing import Optional, Tuple
88

99
from charms.mongodb.v1.mongodb import MongoConfiguration, MongoDBConnection
10+
from data_platform_helpers.version_check import NoVersionError, get_charm_revision
1011
from ops.charm import CharmBase
1112
from ops.framework import Object
1213
from ops.model import ActiveStatus, BlockedStatus, StatusBase, WaitingStatus
@@ -22,7 +23,7 @@
2223

2324
# Increment this PATCH version before using `charmcraft publish-lib` or reset
2425
# to 0 if you are raising the major API version
25-
LIBPATCH = 3
26+
LIBPATCH = 4
2627

2728
AUTH_FAILED_CODE = 18
2829
UNAUTHORISED_CODE = 13
@@ -88,7 +89,7 @@ def is_status_related_to_mismatched_revision(self, status_type: str) -> bool:
8889
"goal state" which processes data differently and the other via the ".status" property.
8990
Hence we have to be flexible to handle each.
9091
"""
91-
if not self.charm.get_cluster_mismatched_revision_status():
92+
if not self.get_cluster_mismatched_revision_status():
9293
return False
9394

9495
if "waiting" in status_type and self.charm.is_role(Config.Role.CONFIG_SERVER):
@@ -153,7 +154,7 @@ def is_unit_status_ready_for_upgrade(self) -> bool:
153154
if isinstance(current_status, ActiveStatus):
154155
return True
155156

156-
if not isinstance(current_status, WaitingStatus):
157+
if not isinstance(current_status, BlockedStatus):
157158
return False
158159

159160
if status_message and "is not up-to date with config-server" in status_message:
@@ -235,7 +236,49 @@ def get_invalid_integration_status(self) -> Optional[StatusBase]:
235236
"Relation to s3-integrator is not supported, config role must be config-server"
236237
)
237238

238-
return self.charm.get_cluster_mismatched_revision_status()
239+
return self.get_cluster_mismatched_revision_status()
240+
241+
def get_cluster_mismatched_revision_status(self) -> Optional[StatusBase]:
242+
"""Returns a Status if the cluster has mismatched revisions."""
243+
# check for invalid versions in sharding integrations, i.e. a shard running on
244+
# revision 88 and a config-server running on revision 110
245+
current_charms_version = get_charm_revision(
246+
self.charm.unit, local_version=self.charm.get_charm_internal_revision
247+
)
248+
local_identifier = (
249+
"-locally built"
250+
if self.charm.version_checker.is_local_charm(self.charm.app.name)
251+
else ""
252+
)
253+
try:
254+
if self.charm.version_checker.are_related_apps_valid():
255+
return
256+
except NoVersionError as e:
257+
# relations to shards/config-server are expected to provide a version number. If they
258+
# do not, it is because they are from an earlier charm revision, i.e. pre-revison X.
259+
logger.debug(e)
260+
if self.charm.is_role(Config.Role.SHARD):
261+
return BlockedStatus(
262+
f"Charm revision ({current_charms_version}{local_identifier}) is not up-to date with config-server."
263+
)
264+
265+
if self.charm.is_role(Config.Role.SHARD):
266+
config_server_revision = self.charm.version_checker.get_version_of_related_app(
267+
self.get_config_server_name()
268+
)
269+
remote_local_identifier = (
270+
"-locally built"
271+
if self.charm.version_checker.is_local_charm(self.get_config_server_name())
272+
else ""
273+
)
274+
return BlockedStatus(
275+
f"Charm revision ({current_charms_version}{local_identifier}) is not up-to date with config-server ({config_server_revision}{remote_local_identifier})."
276+
)
277+
278+
if self.charm.is_role(Config.Role.CONFIG_SERVER):
279+
return WaitingStatus(
280+
f"Waiting for shards to upgrade/downgrade to revision {current_charms_version}{local_identifier}."
281+
)
239282

240283

241284
def build_unit_status(mongodb_config: MongoConfiguration, unit_host: str) -> StatusBase:

poetry.lock

Lines changed: 96 additions & 95 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ tenacity = "^8.2.3"
2121
pyyaml = "^6.0.1"
2222
jinja2 = "^3.1.3"
2323
poetry-core = "^1.9.0"
24-
data-platform-helpers = "^0.1.2"
24+
data-platform-helpers = "^0.1.3"
2525
pyOpenSSL = "^24.2.1"
2626
setuptools = "^72.0.0"
2727

@@ -72,7 +72,7 @@ juju = "^3.5.0"
7272
pytest = "^8.1.1"
7373
pytest-asyncio = "^0.21.1"
7474
pytest-mock = "^3.14.0"
75-
pytest-operator = "^0.34.0"
75+
pytest-operator = "^0.36.0"
7676
pytest-operator-cache = {git = "https://github.com/canonical/data-platform-workflows", tag = "v21.0.0", subdirectory = "python/pytest_plugins/pytest_operator_cache"}
7777
pytest-operator-groups = {git = "https://github.com/canonical/data-platform-workflows", tag = "v21.0.0", subdirectory = "python/pytest_plugins/pytest_operator_groups"}
7878
pytest-github-secrets = {git = "https://github.com/canonical/data-platform-workflows", tag = "v21.0.0", subdirectory = "python/pytest_plugins/github_secrets"}

src/charm.py

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,16 @@
3838
OperatorUser,
3939
)
4040
from charms.prometheus_k8s.v0.prometheus_scrape import MetricsEndpointProvider
41+
from data_platform_helpers.version_check import (
42+
CrossAppVersionChecker,
43+
get_charm_revision,
44+
)
4145
from ops.charm import (
4246
ActionEvent,
4347
CharmBase,
4448
ConfigChangedEvent,
4549
RelationDepartedEvent,
50+
RelationEvent,
4651
StartEvent,
4752
UpdateStatusEvent,
4853
)
@@ -55,7 +60,6 @@
5560
ModelError,
5661
Relation,
5762
RelationDataContent,
58-
StatusBase,
5963
Unit,
6064
WaitingStatus,
6165
)
@@ -93,6 +97,7 @@ def __init__(self, *args):
9397
super().__init__(*args)
9498

9599
self.framework.observe(self.on.mongod_pebble_ready, self._on_mongod_pebble_ready)
100+
self.framework.observe(self.on.config_changed, self._on_config_changed)
96101
self.framework.observe(self.on.start, self._on_start)
97102
self.framework.observe(self.on.update_status, self._on_update_status)
98103
self.framework.observe(
@@ -138,6 +143,15 @@ def __init__(self, *args):
138143
self.config_server = ShardingProvider(self)
139144
self.cluster = ClusterProvider(self)
140145

146+
self.version_checker = CrossAppVersionChecker(
147+
self,
148+
version=get_charm_revision(self.unit, local_version=self.get_charm_internal_revision),
149+
relations_to_check=[
150+
Config.Relations.SHARDING_RELATIONS_NAME,
151+
Config.Relations.CONFIG_SERVER_RELATIONS_NAME,
152+
],
153+
)
154+
141155
# BEGIN: properties
142156

143157
@property
@@ -468,16 +482,15 @@ def primary(self) -> str | None:
468482

469483
return None
470484

485+
@property
486+
def get_charm_internal_revision(self) -> str:
487+
"""Returns the contents of the get_charm_internal_revision file."""
488+
with open(Config.CHARM_INTERNAL_VERSION_FILE, "r") as f:
489+
return f.read().strip()
490+
471491
# END: properties
472492

473493
# BEGIN: generic helper methods
474-
def get_cluster_mismatched_revision_status(self) -> Optional[StatusBase]:
475-
"""Returns a Status if the cluster has mismatched revisions.
476-
477-
TODO implement this method as a part of sharding upgrades.
478-
"""
479-
return None
480-
481494
def remote_mongos_config(self, hosts) -> MongoConfiguration:
482495
"""Generates a MongoConfiguration object for mongos in the deployment of MongoDB."""
483496
# mongos that are part of the cluster have the same username and password, but different
@@ -659,25 +672,40 @@ def _on_start(self, event) -> None:
659672
event.defer()
660673
return
661674

662-
def _relation_changes_handler(self, event) -> None:
675+
def _relation_changes_handler(self, event: RelationEvent) -> None:
663676
"""Handles different relation events and updates MongoDB replica set."""
664677
self._connect_mongodb_exporter()
665678
self._connect_pbm_agent()
666679

667-
if type(event) is RelationDepartedEvent:
680+
if isinstance(event, RelationDepartedEvent):
668681
if event.departing_unit.name == self.unit.name:
669682
self.unit_peer_data.setdefault("unit_departed", "True")
670683

671684
if not self.unit.is_leader():
672685
return
673686

687+
if self.upgrade_in_progress:
688+
logger.warning(
689+
"Adding replicas during an upgrade is not supported. The charm may be in a broken, unrecoverable state"
690+
)
691+
event.defer()
692+
return
693+
674694
# Admin password and keyFile should be created before running MongoDB.
675695
# This code runs on leader_elected event before mongod_pebble_ready
676696
self._generate_secrets()
677697

678698
if not self.db_initialised:
679699
return
680700

701+
self._reconcile_mongo_hosts_and_users(event)
702+
703+
def _reconcile_mongo_hosts_and_users(self, event: RelationEvent) -> None:
704+
"""Auxiliary function to reconcile mongo data for relation events.
705+
706+
Args:
707+
event: The relation event
708+
"""
681709
with MongoDBConnection(self.mongodb_config) as mongo:
682710
try:
683711
replset_members = mongo.get_replset_members()
@@ -1551,7 +1579,7 @@ def is_relation_feasible(self, rel_interface: str) -> bool:
15511579
)
15521580
return False
15531581

1554-
if revision_mismatch_status := self.get_cluster_mismatched_revision_status():
1582+
if revision_mismatch_status := self.status.get_cluster_mismatched_revision_status():
15551583
self.status.set_and_share_status(revision_mismatch_status)
15561584
return False
15571585

src/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class Config:
2222
MONGOD_CONF_DIR = "/etc/mongod"
2323
MONGODB_LOG_FILENAME = "mongodb.log"
2424

25+
CHARM_INTERNAL_VERSION_FILE = "charm_internal_version"
2526
LICENSE_PATH = "/licenses/LICENSE"
2627
CONTAINER_NAME = "mongod"
2728
SERVICE_NAME = "mongod"

src/exceptions.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,40 +7,26 @@
77
class MongoError(Exception):
88
"""Common parent for Mongo errors, allowing to catch them all at once."""
99

10-
pass
11-
1210

1311
class AdminUserCreationError(MongoError):
1412
"""Raised when a commands to create an admin user on MongoDB fail."""
1513

16-
pass
17-
1814

1915
class ApplicationHostNotFoundError(MongoError):
2016
"""Raised when a queried host is not in the application peers or the current host."""
2117

22-
pass
23-
2418

2519
class MongoSecretError(MongoError):
2620
"""Common parent for all Mongo Secret Exceptions."""
2721

28-
pass
29-
3022

3123
class SecretNotAddedError(MongoSecretError):
3224
"""Raised when a Juju 3 secret couldn't be set or re-set."""
3325

34-
pass
35-
3626

3727
class MissingSecretError(MongoSecretError):
3828
"""Could be raised when a Juju 3 mandatory secret couldn't be found."""
3929

40-
pass
41-
4230

4331
class SecretAlreadyExistsError(MongoSecretError):
4432
"""A secret that we want to create already exists."""
45-
46-
pass
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Copyright 2024 Canonical Ltd.
2+
# See LICENSE file for licensing details.

0 commit comments

Comments
 (0)