diff --git a/src/charm.py b/src/charm.py index e09c3d1c6..edb122914 100755 --- a/src/charm.py +++ b/src/charm.py @@ -84,11 +84,19 @@ update_mongod_service, ) from upgrades.mongodb_upgrade import MongoDBUpgrade +from version_check import CrossAppVersionChecker, NoVersionError, get_charm_revision AUTH_FAILED_CODE = 18 UNAUTHORISED_CODE = 13 TLS_CANNOT_FIND_PRIMARY = 133 +LOCALLY_BUIT_CHARM_WARNING = ( + "WARNING: deploying a local charm, cannot check revision across components." +) +INTEGRATED_TO_LOCALLY_BUIT_CHARM_WARNING = ( + "WARNING: integrated to a local charm, cannot check revision across components." +) + logger = logging.getLogger(__name__) APP_SCOPE = Config.Relations.APP_SCOPE @@ -136,6 +144,18 @@ def __init__(self, *args): self.legacy_client_relations = MongoDBLegacyProvider(self) self.tls = MongoDBTLS(self, Config.Relations.PEERS, substrate=Config.SUBSTRATE) self.backups = MongoDBBackups(self) + + # TODO future PR - support pinning mongos version + self.version_checker = CrossAppVersionChecker( + self, + # TODO future PR add ops model revision variable: + # https://github.com/canonical/operator/issues/1255 + version=get_charm_revision(self.unit), + relations_to_check=[ + Config.Relations.SHARDING_RELATIONS_NAME, + Config.Relations.CONFIG_SERVER_RELATIONS_NAME, + ], + ) self.upgrade = MongoDBUpgrade(self) self.config_server = ShardingProvider(self) self.cluster = ClusterProvider(self) @@ -1478,6 +1498,8 @@ def get_invalid_integration_status(self) -> Optional[StatusBase]: "Relation to s3-integrator is not supported, config role must be config-server" ) + return self.get_cluster_mismatched_revision_status() + def get_statuses(self) -> Tuple: """Retrieves statuses for the different processes running inside the unit.""" mongodb_status = build_unit_status(self.mongodb_config, self.unit_ip(self.unit)) @@ -1558,12 +1580,56 @@ def is_relation_feasible(self, rel_interface) -> bool: ) return False + if revision_mismatch_status := self.get_cluster_mismatched_revision_status(): + self.unit.status = revision_mismatch_status + return False + return True def is_sharding_component(self) -> bool: """Returns true if charm is running as a sharded component.""" return self.is_role(Config.Role.SHARD) or self.is_role(Config.Role.CONFIG_SERVER) + def get_cluster_mismatched_revision_status(self) -> Optional[StatusBase]: + """Returns a Status if the cluster has mismatched revisions.""" + if self.version_checker.is_local_charm(self.app.name): + logger.warning(LOCALLY_BUIT_CHARM_WARNING) + return + + if self.version_checker.is_integrated_to_locally_built_charm(): + logger.warning(INTEGRATED_TO_LOCALLY_BUIT_CHARM_WARNING) + return + + # check for invalid versions in sharding integrations, i.e. a shard running on + # revision 88 and a config-server running on revision 110 + current_charms_version = get_charm_revision(self.unit) + try: + if self.version_checker.are_related_apps_valid(): + return + except NoVersionError as e: + # relations to shards/config-server are expected to provide a version number. If they + # do not, it is because they are from an earlier charm revision, i.e. pre-revison X. + # TODO once charm is published, determine which revision X is. + logger.debug(e) + if self.is_role(Config.Role.SHARD): + return BlockedStatus( + f"Charm revision ({current_charms_version}) is not up-to date with config-server." + ) + + if self.is_role(Config.Role.SHARD): + config_server_revision = self.version_checker.get_version_of_related_app( + self.get_config_server_name() + ) + return BlockedStatus( + f"Charm revision ({current_charms_version}) is not up-to date with config-server ({config_server_revision})." + ) + + if self.is_role(Config.Role.CONFIG_SERVER): + # TODO Future PR add handling for integrated mongos charms + return WaitingStatus( + f"Waiting for shards to upgrade/downgrade to revision {current_charms_version}." + ) + def get_config_server_name(self) -> Optional[str]: """Returns the name of the Juju Application that the shard is using as a config server.""" if not self.is_role(Config.Role.SHARD): diff --git a/src/version_check.py b/src/version_check.py new file mode 100644 index 000000000..c5ed3c870 --- /dev/null +++ b/src/version_check.py @@ -0,0 +1,244 @@ +# Copyright 2024 Canonical Ltd. +# See LICENSE file for licensing details. + +"""Class used to determine if a relevant version attribute across related applications are valid. + +There are many potential applications for this. Here are a few examples: +1. in a sharded cluster where is is important that the shards and cluster manager have the same +components. +2. kafka connect and kafka broker apps working together and needing to have the same ubnderlying +version. + +How to use: + +1. in src/charm.py of requirer + provider + in constructor [REQUIRED]: + self.version_checker = self.CrossAppVersionChecker( + self, + version=x, # can be a revision of a charm, version of a snap, version of a workload, etc + relations_to_check=[x,y,z], + # only use if the version doesn't not need to exactly match our current version + version_validity_range={"x": "b"}) + + in update status hook [OPTIONAL]: + if not self.version_checker.are_related_apps_valid(): + logger.debug( + "Warning relational version check failed, these relations have mismatched versions", + "%s", + self.version_checker(self.version_checker.get_invalid_versions()) + ) + # can set status, instruct user to change + +2. other areas of the charm (i.e. joined events, action events, etc) [OPTIONAL]: + if not self.charm.version_checker.are_related_apps_valid(): + # do something - i.e. fail event or log message + +3. in upgrade handler of requirer + provider [REQUIRED]: + if [last unit to upgrade]: + self.charm.version.set_version_across_all_relations() + + +""" +import logging +from typing import Dict, List, Optional, Tuple + +from ops.charm import CharmBase +from ops.framework import Object +from ops.model import Unit + +logger = logging.getLogger(__name__) + +VERSION_CONST = "version" +DEPLOYMENT_TYPE = "deployment" +PREFIX_DIR = "/var/lib/juju/agents/" +LOCAL_BUILT_CHARM_PREFIX = "local" + + +def get_charm_revision(unit: Unit) -> int: + """Returns the charm revision. + + TODO: Keep this until ops framework supports: https://github.com/canonical/operator/issues/1255 + """ + file_path = f"{PREFIX_DIR}unit-{unit.name.replace('/','-')}/charm/.juju-charm" + with open(file_path) as f: + charm_path = f.read().rstrip() + + # revision of charm in a locally built chamr is unreliable: + # https://chat.canonical.com/canonical/pl/ro9935ayxbyyxn9hn6opy4f4xw + if charm_path.split(":")[0] == LOCAL_BUILT_CHARM_PREFIX: + logger.debug("Charm is locally built. Cannot determine revision number.") + return 0 + + # charm_path is of the format ch:amd64/jammy/- + revision = charm_path.split("-")[-1] + return int(revision) + + +class CrossAppVersionChecker(Object): + """Verifies versions across multiple integrated applications.""" + + def __init__( + self, + charm: CharmBase, + version: int, + relations_to_check: List[str], + version_validity_range: Optional[Dict] = None, + ) -> None: + """Constructor for CrossAppVersionChecker. + + Args: + charm: charm to inherit from + version: (int), the current version of the desired attribute of the charm + relations_to_check: (List), a list of relations who should have compatible versions + with the current charm + version_validity_range: (Optional Dict), a list of ranges for valid version ranges. + If not provided it is assumed that relations on the provided interface must have + the same version. + """ + super().__init__(charm, None) + self.charm = charm + # Future PR: upgrade this to a dictionary name versions + self.version = version + self.relations_to_check = relations_to_check + + for rel in relations_to_check: + self.framework.observe( + charm.on[rel].relation_created, + self.set_version_on_relation_created, + ) + + # this feature has yet to be implemented, MongoDB does not need it and it is unclear if + # this will be extended to other charms. If this code is extended to other charms and + # there is a valid usecase we will use the `version_validity_range` variable in the + # function `get_invalid_versions` + self.version_validity_range = version_validity_range + + def get_invalid_versions(self) -> List[Tuple[str, int]]: + """Returns a list of (app name, version number) pairs, if the version number mismatches. + + Mismatches are decided based on version_validity_range, if version_validity_range is not + provided, then the mismatch is expected to match this current app's version number. + + Raises: + NoVersionError. + """ + try: + invalid_relations = [] + for relation_name in self.relations_to_check: + for relation in self.charm.model.relations[relation_name]: + related_version = relation.data[relation.app][VERSION_CONST] + if int(related_version) != self.version: + invalid_relations.append((relation.app.name, related_version)) + except KeyError: + raise NoVersionError(f"Expected {relation.app.name} to have version info.") + + return invalid_relations + + def get_version_of_related_app(self, related_app_name: str) -> int: + """Returns a int for the version of the related app. + + Raises: + NoVersionError. + """ + try: + for relation_name in self.relations_to_check: + for rel in self.charm.model.relations[relation_name]: + if rel.app.name == related_app_name: + return int(rel.data[rel.app][VERSION_CONST]) + except KeyError: + pass + + raise NoVersionError(f"Expected {related_app_name} to have version info.") + + def get_deployment_prefix(self) -> str: + """Returns the deployment prefix, indicating if the charm is locally deployred or not. + + TODO: Keep this until ops framework supports: + https://github.com/canonical/operator/issues/1255 + """ + file_path = f"{PREFIX_DIR}/unit-{self.charm.unit.name.replace('/','-')}/charm/.juju-charm" + with open(file_path) as f: + charm_path = f.read().rstrip() + + return charm_path.split(":")[0] + + def are_related_apps_valid(self) -> bool: + """Returns True if a related app has a version that's incompatible with the current app. + + Raises: + NoVersionError. + """ + return self.get_invalid_versions() == [] + + def set_version_across_all_relations(self) -> None: + """Sets the version number across all related apps, prvided by relations_to_check.""" + if not self.charm.unit.is_leader(): + return + + for relation_name in self.relations_to_check: + for rel in self.charm.model.relations[relation_name]: + rel.data[self.charm.model.app][VERSION_CONST] = str(self.version) + rel.data[self.charm.model.app][DEPLOYMENT_TYPE] = str(self.get_deployment_prefix()) + + def set_version_on_related_app(self, relation_name: str, related_app_name: str) -> None: + """Sets the version number across for a specified relation on a specified app.""" + if not self.charm.unit.is_leader(): + return + + for rel in self.charm.model.relations[relation_name]: + if rel.app.name == related_app_name: + rel.data[self.charm.model.app][VERSION_CONST] = str(self.version) + rel.data[self.charm.model.app][DEPLOYMENT_TYPE] = str(self.get_deployment_prefix()) + + def set_version_on_relation_created(self, event) -> None: + """Shares the charm's revision to the newly integrated application. + + Raises: + RelationInvalidError + """ + if event.relation.name not in self.relations_to_check: + raise RelationInvalidError( + f"Provided relation: {event.relation.name} not in self.relations_to_check." + ) + + self.set_version_on_related_app(event.relation.name, event.app.name) + + def is_integrated_to_locally_built_charm(self) -> bool: + """Returns a boolean value indicating whether the charm is integrated is a local charm. + + NOTE: this function ONLY checks relations on the provided interfaces in + relations_to_check. + """ + for relation_name in self.relations_to_check: + for rel in self.charm.model.relations[relation_name]: + if rel.data[rel.app][DEPLOYMENT_TYPE] == LOCAL_BUILT_CHARM_PREFIX: + return True + + return False + + def is_local_charm(self, app_name: str) -> bool: + """Returns a boolean value indicating whether the provided app is a local charm.""" + if self.charm.app.name == app_name: + return self.get_deployment_prefix() == LOCAL_BUILT_CHARM_PREFIX + + try: + for relation_name in self.relations_to_check: + for rel in self.charm.model.relations[relation_name]: + if rel.app.name == app_name: + return rel.data[rel.app][DEPLOYMENT_TYPE] == LOCAL_BUILT_CHARM_PREFIX + except KeyError: + pass + + raise NoVersionError(f"Expected {app_name} to have version info.") + + +class CrossAppVersionCheckerError(Exception): + """Parent class for errors raised in CrossAppVersionChecker class.""" + + +class RelationInvalidError(CrossAppVersionCheckerError): + """Raised if a relation is not in the provided set of relations to check.""" + + +class NoVersionError(CrossAppVersionCheckerError): + """Raised if an application does not contain any version information.""" diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py index 5c2f3f6cf..af418628a 100644 --- a/tests/unit/test_charm.py +++ b/tests/unit/test_charm.py @@ -42,6 +42,7 @@ class TestCharm(unittest.TestCase): + @patch("charm.get_charm_revision") def setUp(self, *unused): self.harness = Harness(MongodbOperatorCharm) self.addCleanup(self.harness.cleanup) @@ -340,13 +341,24 @@ def test_initialise_replica_failure_leads_to_waiting_state( self.assertTrue(isinstance(self.harness.charm.unit.status, WaitingStatus)) @patch_network_get(private_address="1.1.1.1") + @patch("charm.CrossAppVersionChecker.is_local_charm") + @patch("charm.CrossAppVersionChecker.is_integrated_to_locally_built_charm") + @patch("charm.get_charm_revision") @patch("charms.mongodb.v1.helpers.MongoDBConnection") @patch("charm.MongoDBConnection") @patch("charm.MongoDBBackups.get_pbm_status") @patch("charm.build_unit_status") @patch("charm.MongodbOperatorCharm._connect_mongodb_exporter") def test_update_status_mongodb_error( - self, _, get_mongodb_status, get_pbm_status, connection, status_connection + self, + _, + get_mongodb_status, + get_pbm_status, + connection, + status_connection, + get_charm_revision, + is_local, + is_integrated_to_local, ): """Tests that when MongoDB is not active, that is reported instead of pbm.""" # assume leader has already initialised the replica set @@ -375,13 +387,24 @@ def test_update_status_mongodb_error( self.assertEqual(self.harness.charm.unit.status, mongodb_status) @patch_network_get(private_address="1.1.1.1") + @patch("charm.CrossAppVersionChecker.is_local_charm") + @patch("charm.CrossAppVersionChecker.is_integrated_to_locally_built_charm") + @patch("charm.get_charm_revision") @patch("charms.mongodb.v1.helpers.MongoDBConnection") @patch("charm.MongoDBConnection") @patch("charm.MongoDBBackups.get_pbm_status") @patch("charm.build_unit_status") @patch("charm.MongodbOperatorCharm._connect_mongodb_exporter") def test_update_status_pbm_error( - self, _, get_mongodb_status, get_pbm_status, connection, status_connection + self, + _, + get_mongodb_status, + get_pbm_status, + connection, + status_connection, + get_rev, + is_local, + is_integrated_to_local, ): """Tests when MongoDB is active and pbm is in the error state, pbm status is reported.""" # assume leader has already initialised the replica set @@ -405,13 +428,24 @@ def test_update_status_pbm_error( self.assertEqual(self.harness.charm.unit.status, pbm_status) @patch_network_get(private_address="1.1.1.1") + @patch("charm.CrossAppVersionChecker.is_local_charm") + @patch("charm.CrossAppVersionChecker.is_integrated_to_locally_built_charm") + @patch("charm.get_charm_revision") @patch("charms.mongodb.v1.helpers.MongoDBConnection") @patch("charm.MongoDBConnection") @patch("charm.MongoDBBackups.get_pbm_status") @patch("charm.build_unit_status") @patch("charm.MongodbOperatorCharm._connect_mongodb_exporter") def test_update_status_pbm_and_mongodb_ready( - self, _, get_mongodb_status, get_pbm_status, connection, status_connection + self, + _, + get_mongodb_status, + get_pbm_status, + connection, + status_connection, + get_rev, + is_local, + is_integrated_to_local, ): """Tests when both Mongodb and pbm are ready that MongoDB status is reported.""" # assume leader has already initialised the replica set @@ -427,13 +461,24 @@ def test_update_status_pbm_and_mongodb_ready( self.assertEqual(self.harness.charm.unit.status, ActiveStatus("mongodb")) @patch_network_get(private_address="1.1.1.1") + @patch("charm.CrossAppVersionChecker.is_local_charm") + @patch("charm.CrossAppVersionChecker.is_integrated_to_locally_built_charm") + @patch("charm.get_charm_revision") @patch("charms.mongodb.v1.helpers.MongoDBConnection") @patch("charm.MongoDBConnection") @patch("charm.build_unit_status") @patch("charm.MongodbOperatorCharm.has_backup_service") @patch("charm.MongodbOperatorCharm._connect_mongodb_exporter") def test_update_status_no_s3( - self, _, has_backup_service, get_mongodb_status, connection, status_connection + self, + _, + has_backup_service, + get_mongodb_status, + connection, + status_connection, + get_rev, + is_local, + is_integrated_to_local, ): """Tests when the s3 relation isn't present that the MongoDB status is reported.""" # assume leader has already initialised the replica set @@ -447,11 +492,23 @@ def test_update_status_no_s3( self.assertEqual(self.harness.charm.unit.status, ActiveStatus("mongodb")) @patch_network_get(private_address="1.1.1.1") + @patch("charm.CrossAppVersionChecker.is_local_charm") + @patch("charm.CrossAppVersionChecker.is_integrated_to_locally_built_charm") + @patch("charm.get_charm_revision") @patch("charms.mongodb.v1.helpers.MongoDBConnection") @patch("charm.MongoDBConnection") @patch("charm.MongoDBBackups.get_pbm_status") @patch("charm.MongodbOperatorCharm._connect_mongodb_exporter") - def test_update_status_primary(self, _, pbm_status, connection, status_connection): + def test_update_status_primary( + self, + _, + pbm_status, + connection, + status_connection, + get_rev, + is_local, + is_integrated_to_local, + ): """Tests that update status identifies the primary unit and updates status.""" # assume leader has already initialised the replica set self.harness.set_leader(True) @@ -467,11 +524,23 @@ def test_update_status_primary(self, _, pbm_status, connection, status_connectio self.assertEqual(self.harness.charm.unit.status, ActiveStatus("Primary")) @patch_network_get(private_address="1.1.1.1") + @patch("charm.CrossAppVersionChecker.is_local_charm") + @patch("charm.CrossAppVersionChecker.is_integrated_to_locally_built_charm") + @patch("charm.get_charm_revision") @patch("charms.mongodb.v1.helpers.MongoDBConnection") @patch("charm.MongoDBConnection") @patch("charm.MongoDBBackups.get_pbm_status") @patch("charm.MongodbOperatorCharm._connect_mongodb_exporter") - def test_update_status_secondary(self, _, pbm_status, connection, status_connection): + def test_update_status_secondary( + self, + _, + pbm_status, + connection, + status_connection, + get_rev, + is_local, + is_integrated_to_local, + ): """Tests that update status identifies secondary units and doesn't update status.""" # assume leader has already initialised the replica set self.harness.set_leader(True) @@ -487,11 +556,23 @@ def test_update_status_secondary(self, _, pbm_status, connection, status_connect self.assertEqual(self.harness.charm.unit.status, ActiveStatus("")) @patch_network_get(private_address="1.1.1.1") + @patch("charm.CrossAppVersionChecker.is_local_charm") + @patch("charm.CrossAppVersionChecker.is_integrated_to_locally_built_charm") + @patch("charm.get_charm_revision") @patch("charms.mongodb.v1.helpers.MongoDBConnection") @patch("charm.MongoDBConnection") @patch("charm.MongoDBBackups.get_pbm_status") @patch("charm.MongodbOperatorCharm._connect_mongodb_exporter") - def test_update_status_additional_messages(self, _, pbm_status, connection, status_connection): + def test_update_status_additional_messages( + self, + _, + pbm_status, + connection, + status_connection, + get_rev, + is_local, + is_integrated_to_local, + ): """Tests status updates are correct for non-primary and non-secondary cases.""" # assume leader has already initialised the replica set self.harness.set_leader(True) @@ -527,10 +608,15 @@ def test_update_status_additional_messages(self, _, pbm_status, connection, stat self.harness.charm.on.update_status.emit() self.assertEqual(self.harness.charm.unit.status, BlockedStatus("unknown")) + @patch("charm.CrossAppVersionChecker.is_local_charm") + @patch("charm.CrossAppVersionChecker.is_integrated_to_locally_built_charm") + @patch("charm.get_charm_revision") @patch("charm.MongodbOperatorCharm.get_secret") @patch_network_get(private_address="1.1.1.1") @patch("charm.MongoDBConnection") - def test_update_status_not_ready(self, connection, get_secret): + def test_update_status_not_ready( + self, connection, get_secret, get_rev, is_local, is_integrated_to_local + ): """Tests that if mongod is not running on this unit it restarts it.""" get_secret.return_value = "pass123" connection.return_value.__enter__.return_value.is_ready = False diff --git a/tests/unit/test_config_server_lib.py b/tests/unit/test_config_server_lib.py index 05e791e1e..fdd303fc0 100644 --- a/tests/unit/test_config_server_lib.py +++ b/tests/unit/test_config_server_lib.py @@ -13,8 +13,9 @@ class TestConfigServerInterface(unittest.TestCase): + @mock.patch("charm.get_charm_revision") @patch_network_get(private_address="1.1.1.1") - def setUp(self): + def setUp(self, *unused): self.harness = Harness(MongodbOperatorCharm) self.harness.begin() self.harness.add_relation("database-peers", "database-peers") @@ -86,7 +87,15 @@ def is_config_mock_call(*args): self.harness.charm.cluster.update_config_server_db(mock.Mock()) self.harness.charm.cluster.database_provides.update_relation_data.assert_not_called() - def test_pass_hooks_check_waits_for_start_config_server(self): + @mock.patch("version_check.CrossAppVersionChecker.is_local_charm") + @mock.patch("version_check.CrossAppVersionChecker.is_integrated_to_locally_built_charm") + @mock.patch("charm.get_charm_revision") + def test_pass_hooks_check_waits_for_start_config_server( + self, + get_rev, + is_local, + is_integrated_to_local, + ): """Ensure that pass_hooks defers until the database is initialized. Note: in some cases sharding related hooks execute before config and leader elected hooks, diff --git a/tests/unit/test_mongodb_backups.py b/tests/unit/test_mongodb_backups.py index 1da4d8fb7..45d147203 100644 --- a/tests/unit/test_mongodb_backups.py +++ b/tests/unit/test_mongodb_backups.py @@ -32,8 +32,9 @@ class TestMongoBackups(unittest.TestCase): + @patch("charm.get_charm_revision") @patch_network_get(private_address="1.1.1.1") - def setUp(self): + def setUp(self, *unused): self.harness = Harness(MongodbOperatorCharm) self.harness.begin() self.harness.add_relation("database-peers", "database-peers") diff --git a/tests/unit/test_mongodb_provider.py b/tests/unit/test_mongodb_provider.py index 6829a71f2..7635a8b48 100644 --- a/tests/unit/test_mongodb_provider.py +++ b/tests/unit/test_mongodb_provider.py @@ -24,8 +24,9 @@ class TestMongoProvider(unittest.TestCase): + @patch("charm.get_charm_revision") @patch_network_get(private_address="1.1.1.1") - def setUp(self): + def setUp(self, *unused): self.harness = Harness(MongodbOperatorCharm) self.harness.begin() self.harness.add_relation("database-peers", "mongodb-peers") @@ -57,10 +58,21 @@ def test_relation_event_db_not_initialised(self, oversee_users, defer): defer.assert_not_called() @patch_network_get(private_address="1.1.1.1") + @patch("charm.CrossAppVersionChecker.is_local_charm") + @patch("charm.CrossAppVersionChecker.is_integrated_to_locally_built_charm") + @patch("charm.get_charm_revision") @patch("ops.framework.EventBase.defer") @patch("charm.MongoDBProvider.oversee_users") @patch("charm.MongodbOperatorCharm.auth_enabled", return_value=True) - def test_relation_event_oversee_users_mongo_failure(self, auth_enabled, oversee_users, defer): + def test_relation_event_oversee_users_mongo_failure( + self, + auth_enabled, + oversee_users, + defer, + get_rev, + is_local, + is_integrated_to_local, + ): """Tests the errors related to pymongo when overseeing users result in a defer.""" # presets self.harness.set_leader(True) @@ -82,11 +94,20 @@ def test_relation_event_oversee_users_mongo_failure(self, auth_enabled, oversee_ # oversee_users raises AssertionError when unable to attain users from relation @patch_network_get(private_address="1.1.1.1") + @patch("version_check.CrossAppVersionChecker.is_local_charm") + @patch("version_check.CrossAppVersionChecker.is_integrated_to_locally_built_charm") + @patch("charm.get_charm_revision") @patch("ops.framework.EventBase.defer") @patch("charm.MongoDBProvider.oversee_users") @patch("charm.MongodbOperatorCharm.auth_enabled", return_value=True) def test_relation_event_oversee_users_fails_to_get_relation( - self, auth_enabled, oversee_users, defer + self, + auth_enabled, + oversee_users, + defer, + get_rev, + is_local, + is_integrated_to_local, ): """Verifies that when users are formatted incorrectly an assertion error is raised.""" # presets diff --git a/tests/unit/test_tls_lib.py b/tests/unit/test_tls_lib.py index 83abbcd2e..f97cd39ae 100644 --- a/tests/unit/test_tls_lib.py +++ b/tests/unit/test_tls_lib.py @@ -15,8 +15,9 @@ class TestMongoTLS(unittest.TestCase): + @patch("charm.get_charm_revision") @patch_network_get(private_address="1.1.1.1") - def setUp(self): + def setUp(self, *unused): self.harness = Harness(MongodbOperatorCharm) self.harness.begin() self.harness.add_relation("database-peers", "database-peers") diff --git a/tests/unit/test_upgrade.py b/tests/unit/test_upgrade.py index c2af96a13..20da7865d 100644 --- a/tests/unit/test_upgrade.py +++ b/tests/unit/test_upgrade.py @@ -13,6 +13,8 @@ class TestCharm(unittest.TestCase): + @patch("charm.get_charm_revision") + @patch_network_get(private_address="1.1.1.1") def setUp(self, *unused): self.harness = Harness(MongodbOperatorCharm) self.addCleanup(self.harness.cleanup)