Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DPE-3179] Use specified DB + Application roles from host charm #12

Merged
merged 10 commits into from
Jan 5, 2024
133 changes: 133 additions & 0 deletions lib/charms/mongos/v0/mongos_client_interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Copyright 2023 Canonical Ltd.
# See LICENSE file for licensing details.

"""In this class, we manage relations between config-servers and shards.

This class handles the sharing of secrets between sharded components, adding shards, and removing
shards.
"""
import logging
from ops.framework import Object
from ops.charm import CharmBase
from charms.data_platform_libs.v0.data_interfaces import (
DatabaseProvides,
DatabaseRequires,
)


logger = logging.getLogger(__name__)
DATABASE_KEY = "database"
USER_ROLES_KEY = "extra-user-roles"
MONGOS_RELATION_NAME = "mongos_proxy"

# TODO - the below LIBID, LIBAPI, and LIBPATCH are not valid and were made manually. These will be
# created automatically once the charm has been published. The charm has not yet been published
# due to:
# https://discourse.charmhub.io/t/request-ownership-of-reserved-mongos-charm/12735

# The unique Charmhub library identifier, never change it
LIBID = "58ad1ccca4974932ba22b97781b9b2a0"

# Increment this major API version when introducing breaking changes
LIBAPI = 0

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

"""Library to manage the relation for the application between mongos and the deployed application.
In short, this relation ensure that:
1. mongos receives the specified database and users roles needed by the host application
2. the host application receives the generated username, password and uri for connecting to the
sharded cluster.

This library contains the Requires and Provides classes for handling the relation between an
application and mongos. The mongos application relies on the MongosProvider class and the deployed
application uses the MongoDBRequires class.

The following is an example of how to use the MongoDBRequires class to specify the roles and
database name:

```python
from charms.mongos.v0.mongos_client_interface import MongosRequirer


class ApplicationCharm(CharmBase):

def __init__(self, *args):
super().__init__(*args)

# relation events for mongos client
self._mongos_client = MongosRequirer(
self,
database_name="my-test-db",
extra_user_roles="admin",
)
```

To receive the username, password, and uri:
# TODO this is to be implemented in a future PR
"""


class MongosProvider(Object):
"""Manage relations between the mongos router and the application on the mongos side."""

def __init__(self, charm: CharmBase, relation_name: str = MONGOS_RELATION_NAME) -> None:
"""Constructor for MongosProvider object."""
self.relation_name = relation_name
self.charm = charm
self.database_provides = DatabaseProvides(self.charm, relation_name=self.relation_name)

super().__init__(charm, self.relation_name)
self.framework.observe(
charm.on[self.relation_name].relation_changed, self._on_relation_changed
)

# TODO Future PRs handle relation broken

def _on_relation_changed(self, event) -> None:
"""Handles updating the database and extra user roles."""
if not self.charm.unit.is_leader():
return

relation_data = event.relation.data[event.app]
new_database_name = relation_data.get(DATABASE_KEY, self.charm.database)
new_extra_user_roles = relation_data.get(USER_ROLES_KEY, self.charm.extra_user_roles)

if new_database_name != self.charm.database:
self.charm.set_database(new_database_name)

if new_extra_user_roles != self.charm.extra_user_roles:
if isinstance(new_extra_user_roles, str):
new_extra_user_roles = [new_extra_user_roles]

self.charm.set_user_roles(new_extra_user_roles)


class MongosRequirer(Object):
"""Manage relations between the mongos router and the application on the application side."""

def __init__(
self,
charm: CharmBase,
database_name: str,
extra_user_roles: str,
relation_name: str = MONGOS_RELATION_NAME,
) -> None:
"""Constructor for MongosRequirer object."""
self.relation_name = relation_name
self.charm = charm

if not database_name:
database_name = f"{self.charm.app}"

self.database_requires = DatabaseRequires(
self.charm,
relation_name=self.relation_name,
database_name=database_name,
extra_user_roles=extra_user_roles,
)

super().__init__(charm, self.relation_name)
# TODO Future PRs handle relation broken
9 changes: 5 additions & 4 deletions src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from pathlib import Path

from charms.mongodb.v0.mongodb_secrets import SecretCache
from charms.mongos.v0.mongos_client_interface import MongosProvider
from typing import Set, List, Optional, Dict
from charms.mongodb.v0.mongodb_secrets import generate_secret_label
from charms.mongodb.v1.mongos import MongosConfiguration
Expand Down Expand Up @@ -53,6 +54,7 @@

self.cluster = ClusterRequirer(self)
self.secrets = SecretCache(self)
self.mongos_provider = MongosProvider(self)
# 1. add users for related application (to be done on config-server charm side)
# 2. update status indicates missing relations

Expand Down Expand Up @@ -237,9 +239,9 @@
# TODO future PR - generate different URI for data-integrator as that charm will not
# communicate to mongos via the Unix Domain Socket.

def set_user_role(self, roles: List[str]):
def set_user_roles(self, roles: List[str]) -> None:
"""Updates the roles for the mongos user."""
roles_str = roles.join(",")
roles_str = ",".join(roles)

Check warning on line 244 in src/charm.py

View check run for this annotation

Codecov / codecov/patch

src/charm.py#L244

Added line #L244 was not covered by tests
self.app_peer_data[USER_ROLES_TAG] = roles_str

if len(self.model.relations[Config.Relations.CLUSTER_RELATIONS_NAME]) == 0:
Expand All @@ -253,7 +255,7 @@
config_server_rel.id, {USER_ROLES_TAG: roles_str}
)

def set_database(self, database: str):
def set_database(self, database: str) -> None:
"""Updates the database requested for the mongos user."""
self.app_peer_data[DATABASE_TAG] = database

Expand All @@ -268,7 +270,6 @@
config_server_rel.id, {DATABASE_TAG: database}
)

# TODO future PR add function to set database field
# END: helper functions

# BEGIN: properties
Expand Down
Loading