Skip to content

Commit

Permalink
[DPE-5090] - Update mongodb provider to support K8s router (#462)
Browse files Browse the repository at this point in the history
## Issue
mongos k8s router cannot yet make use of the mongodb_provider lib due to
the lib not be flexible to support mongos

## Solution
Update the lib so that it can support mongos

---------

Co-authored-by: Neha Oudin <17551419+Gu1nness@users.noreply.github.com>
Co-authored-by: Mehdi Bendriss <bendrissmehdi@gmail.com>
  • Loading branch information
3 people committed Aug 23, 2024
1 parent 94f4376 commit 8cad660
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 58 deletions.
4 changes: 3 additions & 1 deletion lib/charms/mongodb/v0/config_server_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@

# Increment this PATCH version before using `charmcraft publish-lib` or reset
# to 0 if you are raising the major API version
LIBPATCH = 9
LIBPATCH = 10


class ClusterProvider(Object):
Expand Down Expand Up @@ -328,6 +328,8 @@ def _on_relation_broken(self, event: RelationBrokenEvent) -> None:
# K8s charm have a 1:Many client scheme and share connection info in a different manner.
if self.substrate == Config.Substrate.VM:
self.charm.remove_connection_info()
else:
self.db_initialised = False

# BEGIN: helper functions
def pass_hook_checks(self, event):
Expand Down
101 changes: 55 additions & 46 deletions lib/charms/mongodb/v1/mongodb_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
from typing import List, Optional, Set

from charms.data_platform_libs.v0.data_interfaces import DatabaseProvides
from charms.mongodb.v0.mongo import MongoConfiguration, MongoConnection
from charms.mongodb.v1.helpers import generate_password
from charms.mongodb.v1.mongodb import MongoConfiguration, MongoDBConnection
from ops.charm import CharmBase, EventBase, RelationBrokenEvent, RelationChangedEvent
from ops.framework import Object
from ops.model import Relation
Expand All @@ -31,17 +31,14 @@

# Increment this PATCH version before using `charmcraft publish-lib` or reset
# to 0 if you are raising the major API version
LIBPATCH = 10
LIBPATCH = 11

logger = logging.getLogger(__name__)
REL_NAME = "database"

MONGOS_RELATIONS = "cluster"
MONGOS_CLIENT_RELATIONS = "mongos_proxy"

# We expect the MongoDB container to use the default ports
MONGODB_PORT = 27017
MONGODB_VERSION = "5.0"
PEER = "database-peers"

Diff = namedtuple("Diff", "added changed deleted")
Diff.__doc__ = """
Expand All @@ -62,24 +59,19 @@ def __init__(self, charm: CharmBase, substrate="k8s", relation_name: str = "data
substrate: host type, either "k8s" or "vm"
relation_name: the name of the relation
"""
self.relation_name = relation_name
self.substrate = substrate
self.charm = charm

super().__init__(charm, self.relation_name)
super().__init__(charm, relation_name)
self.framework.observe(
charm.on[self.relation_name].relation_departed,
charm.on[relation_name].relation_departed,
self.charm.check_relation_broken_or_scale_down,
)
self.framework.observe(
charm.on[self.relation_name].relation_broken, self._on_relation_event
)
self.framework.observe(
charm.on[self.relation_name].relation_changed, self._on_relation_event
)
self.framework.observe(charm.on[relation_name].relation_broken, self._on_relation_event)
self.framework.observe(charm.on[relation_name].relation_changed, self._on_relation_event)

# Charm events defined in the database provides charm library.
self.database_provides = DatabaseProvides(self.charm, relation_name=self.relation_name)
self.database_provides = DatabaseProvides(self.charm, relation_name=relation_name)
self.framework.observe(
self.database_provides.on.database_requested, self._on_relation_event
)
Expand All @@ -91,7 +83,9 @@ def pass_hook_checks(self, event: EventBase) -> bool:
if not self.charm.db_initialised:
return False

if not self.charm.is_relation_feasible(self.relation_name):
if not self.charm.is_role(Config.Role.MONGOS) and not self.charm.is_relation_feasible(
self.get_relation_name()
):
logger.info("Skipping code for relations.")
return False

Expand Down Expand Up @@ -164,12 +158,18 @@ def oversee_users(self, departed_relation_id: Optional[int], event):
relation is still on the list of all relations. Therefore, for proper
work of the function, we need to exclude departed relation from the list.
"""
with MongoDBConnection(self.charm.mongodb_config) as mongo:
with MongoConnection(self.charm.mongo_config) as mongo:
database_users = mongo.get_users()
relation_users = self._get_users_from_relations(departed_relation_id)

for username in database_users - relation_users:
logger.info("Remove relation user: %s", username)
if (
self.charm.is_role(Config.Role.MONGOS)
and username == self.charm.mongo_config.username
):
continue

mongo.drop_user(username)

for username in relation_users - database_users:
Expand Down Expand Up @@ -245,10 +245,10 @@ def update_app_relation_data(self) -> None:

database_users = set()

with MongoDBConnection(self.charm.mongodb_config) as mongo:
with MongoConnection(self.charm.mongo_config) as mongo:
database_users = mongo.get_users()

for relation in self._get_relations(rel=REL_NAME):
for relation in self._get_relations():
username = self._get_username_from_relation_id(relation.id)
password = self._get_or_set_password(relation)
config = self._get_config(username, password)
Expand Down Expand Up @@ -290,16 +290,21 @@ def _get_config(self, username: str, password: Optional[str]) -> MongoConfigurat

database_name = self._get_database_from_relation(relation)

return MongoConfiguration(
replset=self.charm.app.name,
database=database_name,
username=username,
password=password,
hosts=self.charm.mongodb_config.hosts,
roles=self._get_roles_from_relation(relation),
tls_external=False,
tls_internal=False,
)
mongo_args = {
"database": database_name,
"username": username,
"password": password,
"hosts": self.charm.mongo_config.hosts,
"roles": self._get_roles_from_relation(relation),
"tls_external": False,
"tls_internal": False,
}
if self.charm.is_role(Config.Role.MONGOS):
mongo_args["port"] = Config.MONGOS_PORT
else:
mongo_args["replset"] = self.charm.app.name

return MongoConfiguration(**mongo_args)

def _set_relation(self, config: MongoConfiguration):
"""Save all output fields into application relation."""
Expand All @@ -318,10 +323,11 @@ def _set_relation(self, config: MongoConfiguration):
relation.id,
",".join(config.hosts),
)
self.database_provides.set_replset(
relation.id,
config.replset,
)
if not self.charm.is_role(Config.Role.MONGOS):
self.database_provides.set_replset(
relation.id,
config.replset,
)
self.database_provides.set_uris(
relation.id,
config.uri,
Expand All @@ -332,9 +338,9 @@ def _get_username_from_relation_id(relation_id: int) -> str:
"""Construct username."""
return f"relation-{relation_id}"

def _get_users_from_relations(self, departed_relation_id: Optional[int], rel=REL_NAME):
def _get_users_from_relations(self, departed_relation_id: Optional[int]):
"""Return usernames for all relations except departed relation."""
relations = self._get_relations(rel)
relations = self._get_relations()
return set(
[
self._get_username_from_relation_id(relation.id)
Expand All @@ -351,7 +357,7 @@ def _get_databases_from_relations(self, departed_relation_id: Optional[int]) ->
except for those databases that belong to the departing
relation specified.
"""
relations = self._get_relations(rel=REL_NAME)
relations = self._get_relations()
databases = set()
for relation in relations:
if relation.id == departed_relation_id:
Expand All @@ -370,22 +376,25 @@ def _get_relation_from_username(self, username: str) -> Relation:
assert match is not None, "No relation match"
relation_id = int(match.group(1))
logger.debug("Relation ID: %s", relation_id)
relation_name = (
MONGOS_RELATIONS if self.charm.is_role(Config.Role.CONFIG_SERVER) else REL_NAME
)
relation_name = self.get_relation_name()
return self.model.get_relation(relation_name, relation_id)

def _get_relations(self, rel=REL_NAME) -> List[Relation]:
def _get_relations(self) -> List[Relation]:
"""Return the set of relations for users.
We create users for either direct relations to charm or for relations through the mongos
charm.
"""
return (
self.model.relations[MONGOS_RELATIONS]
if self.charm.is_role(Config.Role.CONFIG_SERVER)
else self.model.relations[rel]
)
return self.model.relations[self.get_relation_name()]

def get_relation_name(self):
"""Returns the name of the relation to use."""
if self.charm.is_role(Config.Role.CONFIG_SERVER):
return MONGOS_RELATIONS
elif self.charm.is_role(Config.Role.MONGOS):
return MONGOS_CLIENT_RELATIONS
else:
return REL_NAME

@staticmethod
def _get_database_from_relation(relation: Relation) -> Optional[str]:
Expand Down
5 changes: 5 additions & 0 deletions src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,11 @@ def remote_mongodb_config(self, hosts, replset=None, standalone=None) -> MongoCo
OperatorUser, hosts, replset=replset, standalone=standalone
)

@property
def mongo_config(self) -> MongoConfiguration:
"""Returns a MongoConfiguration object for shared libs with agnostic mongo commands."""
return self.mongodb_config

@property
def mongodb_config(self) -> MongoConfiguration:
"""Generates a MongoConfiguration object for this deployment of MongoDB."""
Expand Down
22 changes: 11 additions & 11 deletions tests/unit/test_mongodb_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def test_relation_event_oversee_users_fails_to_get_relation(
self.harness.remove_relation_unit(relation_id, "consumer/0")

@patch_network_get(private_address="1.1.1.1")
@patch("charms.mongodb.v1.mongodb_provider.MongoDBConnection")
@patch("charms.mongodb.v1.mongodb_provider.MongoConnection")
def test_oversee_users_get_users_failure(self, connection):
"""Verifies that when unable to retrieve users from mongod an exception is raised."""
for dep_id in DEPARTED_IDS:
Expand All @@ -143,7 +143,7 @@ def test_oversee_users_get_users_failure(self, connection):

@patch_network_get(private_address="1.1.1.1")
@patch("charm.MongoDBProvider._get_users_from_relations")
@patch("charms.mongodb.v1.mongodb_provider.MongoDBConnection")
@patch("charms.mongodb.v1.mongodb_provider.MongoConnection")
def test_oversee_users_drop_user_failure(self, connection, relation_users):
"""Verifies that when unable to drop users from mongod an exception is raised."""
# presets, such that there is a need to drop users.
Expand All @@ -163,7 +163,7 @@ def test_oversee_users_drop_user_failure(self, connection, relation_users):

@patch_network_get(private_address="1.1.1.1")
@patch("charm.MongoDBProvider._get_users_from_relations")
@patch("charms.mongodb.v1.mongodb_provider.MongoDBConnection")
@patch("charms.mongodb.v1.mongodb_provider.MongoConnection")
def test_oversee_users_get_config_failure(self, connection, relation_users):
"""Verifies that when users do not match necessary schema an AssertionError is raised."""
# presets, such that the need to create user relations is triggered. Further presets
Expand All @@ -182,7 +182,7 @@ def test_oversee_users_get_config_failure(self, connection, relation_users):
@patch("charm.MongoDBProvider._set_relation")
@patch("charm.MongoDBProvider._get_config")
@patch("charm.MongoDBProvider._get_users_from_relations")
@patch("charms.mongodb.v1.mongodb_provider.MongoDBConnection")
@patch("charms.mongodb.v1.mongodb_provider.MongoConnection")
@patch("charm.MongoDBProvider._diff")
def test_oversee_users_no_config_database(
self, diff, connection, relation_users, get_config, set_relation
Expand All @@ -205,7 +205,7 @@ def test_oversee_users_no_config_database(
@patch("charm.MongoDBProvider._set_relation")
@patch("charm.MongoDBProvider._get_config")
@patch("charm.MongoDBProvider._get_users_from_relations")
@patch("charms.mongodb.v1.mongodb_provider.MongoDBConnection")
@patch("charms.mongodb.v1.mongodb_provider.MongoConnection")
def test_oversee_users_create_user_failure(
self, connection, relation_users, get_config, set_relation
):
Expand All @@ -226,7 +226,7 @@ def test_oversee_users_create_user_failure(
@patch_network_get(private_address="1.1.1.1")
@patch("charm.MongoDBProvider._get_config")
@patch("charm.MongoDBProvider._get_users_from_relations")
@patch("charms.mongodb.v1.mongodb_provider.MongoDBConnection")
@patch("charms.mongodb.v1.mongodb_provider.MongoConnection")
def test_oversee_users_set_relation_failure(self, connection, relation_users, get_config):
"""Verifies that when adding a user with an invalid name that an exception is raised."""
# presets, such that the need to create user relations is triggered and user naming such
Expand All @@ -245,7 +245,7 @@ def test_oversee_users_set_relation_failure(self, connection, relation_users, ge

@patch_network_get(private_address="1.1.1.1")
@patch("charm.MongoDBProvider._get_users_from_relations")
@patch("charms.mongodb.v1.mongodb_provider.MongoDBConnection")
@patch("charms.mongodb.v1.mongodb_provider.MongoConnection")
def test_oversee_users_update_get_config_failure(self, connection, relation_users):
"""Verifies that when updating a user with an invalid name that an exception is raised."""
# presets, such that the need to update user relations is triggered and user naming such
Expand All @@ -263,7 +263,7 @@ def test_oversee_users_update_get_config_failure(self, connection, relation_user
@patch_network_get(private_address="1.1.1.1")
@patch("charm.MongoDBProvider._get_config")
@patch("charm.MongoDBProvider._get_users_from_relations")
@patch("charms.mongodb.v1.mongodb_provider.MongoDBConnection")
@patch("charms.mongodb.v1.mongodb_provider.MongoConnection")
def test_oversee_users_update_user_failure(self, connection, relation_users, get_config):
"""Verifies that when updating users fails an exception is raised."""
# presets, such that the need to update user relations is triggered
Expand All @@ -282,7 +282,7 @@ def test_oversee_users_update_user_failure(self, connection, relation_users, get
@patch_network_get(private_address="1.1.1.1")
@patch("charm.MongoDBProvider._get_databases_from_relations")
@patch("charm.MongoDBProvider._get_users_from_relations")
@patch("charms.mongodb.v1.mongodb_provider.MongoDBConnection")
@patch("charms.mongodb.v1.mongodb_provider.MongoConnection")
def test_oversee_users_no_auto_delete(
self, connection, relation_users, databases_from_relations
):
Expand All @@ -299,7 +299,7 @@ def test_oversee_users_no_auto_delete(

@patch_network_get(private_address="1.1.1.1")
@patch("charm.MongoDBProvider._get_users_from_relations")
@patch("charms.mongodb.v1.mongodb_provider.MongoDBConnection")
@patch("charms.mongodb.v1.mongodb_provider.MongoConnection")
def test_oversee_users_mongo_databases_failure(self, connection, relation_users):
"""Verifies failures in checking for databases with mongod result in raised exceptions."""
self.harness.update_config({"auto-delete": True})
Expand All @@ -317,7 +317,7 @@ def test_oversee_users_mongo_databases_failure(self, connection, relation_users)
@patch_network_get(private_address="1.1.1.1")
@patch("charm.MongoDBProvider._get_databases_from_relations")
@patch("charm.MongoDBProvider._get_users_from_relations")
@patch("charms.mongodb.v1.mongodb_provider.MongoDBConnection")
@patch("charms.mongodb.v1.mongodb_provider.MongoConnection")
def test_oversee_users_drop_database_failure(
self, connection, relation_users, databases_from_relations
):
Expand Down

0 comments on commit 8cad660

Please sign in to comment.