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-3180] Share connection info with host application #13

Merged
merged 4 commits into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions lib/charms/mongodb/v0/config_server_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@

# Increment this PATCH version before using `charmcraft publish-lib` or reset
# to 0 if you are raising the major API version
LIBPATCH = 5
LIBPATCH = 2
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ported file over from mongodb charm



class ClusterProvider(Object):
Expand Down Expand Up @@ -86,7 +86,6 @@ def _on_relation_changed(self, event) -> None:
# create user and set secrets for mongos relation
self.charm.client_relations.oversee_users(None, None)

# TODO Future PR, use secrets
if self.charm.unit.is_leader():
self.database_provides.update_relation_data(
event.relation.id,
Expand All @@ -98,6 +97,25 @@ def _on_relation_changed(self, event) -> None:
},
)

def update_config_server_db(self, event):
"""Provides related mongos applications with new config server db."""
if not self.pass_hook_checks(event):
logger.info("Skipping update_config_server_db: hook checks did not pass")
return

config_server_db = self.generate_config_server_db()

if not self.charm.unit.is_leader():
return

for relation in self.charm.model.relations[self.relation_name]:
self.database_provides.update_relation_data(
relation.id,
{
CONFIG_SERVER_DB_KEY: config_server_db,
},
)

def generate_config_server_db(self) -> str:
"""Generates the config server database for mongos to connect to."""
replica_set_name = self.charm.app.name
Expand All @@ -121,6 +139,7 @@ def __init__(
self.database_requires = DatabaseRequires(
self.charm,
relation_name=self.relation_name,
relations_aliases=[self.relation_name],
database_name=self.charm.database,
extra_user_roles=self.charm.extra_user_roles,
additional_secret_fields=[KEYFILE_KEY],
Expand All @@ -131,11 +150,24 @@ def __init__(
charm.on[self.relation_name].relation_created,
self.database_requires._on_relation_created_event,
)

self.framework.observe(
self.database_requires.on.database_created, self._on_database_created
)
self.framework.observe(
charm.on[self.relation_name].relation_changed, self._on_relation_changed
)
# TODO Future PRs handle scale down

def _on_database_created(self, event) -> None:
if not self.charm.unit.is_leader():
return

logger.info("Database and user created for mongos application")
self.charm.set_secret(Config.Relations.APP_SCOPE, Config.Secrets.USERNAME, event.username)
self.charm.set_secret(Config.Relations.APP_SCOPE, Config.Secrets.PASSWORD, event.password)
self.charm.share_connection_info()

def _on_relation_changed(self, event) -> None:
"""Starts/restarts monogs with config server information."""
key_file_contents = self.database_requires.fetch_relation_field(
Expand Down Expand Up @@ -168,7 +200,6 @@ def _on_relation_changed(self, event) -> None:
event.defer()
return

self.charm.share_uri()
self.charm.unit.status = ActiveStatus()

# BEGIN: helper functions
Expand Down
7 changes: 5 additions & 2 deletions lib/charms/mongodb/v1/mongos.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,11 @@ class MongosConfiguration:
@property
def uri(self):
MiaAltieri marked this conversation as resolved.
Show resolved Hide resolved
"""Return URI concatenated from fields."""
hosts = [f"{host}:{self.port}" for host in self.hosts]
hosts = ",".join(hosts)
# mongos using Unix Domain Socket to communicate do not use port
if self.port:
self.hosts = [f"{host}:{self.port}" for host in self.hosts]

hosts = ",".join(self.hosts)
# Auth DB should be specified while user connects to application DB.
auth_source = ""
if self.database != "admin":
Expand Down
11 changes: 11 additions & 0 deletions lib/charms/mongos/v0/mongos_client_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,17 @@ def _on_relation_changed(self, event) -> None:

self.charm.set_user_role(new_extra_user_roles)

def update_connection_info(self, config) -> None:
MiaAltieri marked this conversation as resolved.
Show resolved Hide resolved
"""Sends the URI to the related parent application"""
logger.info("Sharing connection information to host application.")
for relation in self.model.relations[MONGOS_RELATION_NAME]:
self.database_provides.set_credentials(relation.id, config.username, config.password)
self.database_provides.set_database(relation.id, config.database)
self.database_provides.set_uris(
relation.id,
config.uri,
)


class MongosRequirer(Object):
"""Manage relations between the mongos router and the application on the application side."""
Expand Down
24 changes: 19 additions & 5 deletions src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
from charms.mongodb.v0.config_server_interface import ClusterRequirer
from charms.mongodb.v1.users import (
MongoDBUser,
OperatorUser,
)

from config import Config
Expand Down Expand Up @@ -234,10 +233,9 @@ def restart_mongos_service(self) -> None:
self.stop_mongos_service()
self.start_mongos_service()

def share_uri(self) -> None:
def share_connection_info(self) -> None:
"""Future PR - generate URI and give it to related app"""
# TODO future PR - generate different URI for data-integrator as that charm will not
# communicate to mongos via the Unix Domain Socket.
self.mongos_provider.update_connection_info(self.mongos_config)

def set_user_role(self, roles: List[str]) -> None:
"""Updates the roles for the mongos user."""
Expand Down Expand Up @@ -297,7 +295,23 @@ def extra_user_roles(self) -> Set[str]:
@property
def mongos_config(self) -> MongosConfiguration:
"""Generates a MongoDBConfiguration object for mongos in the deployment of MongoDB."""
return self._get_mongos_config_for_user(OperatorUser, set(Config.MONGOS_SOCKET))
# TODO future PR - use ip addresses for hosts for data-integrator as that charm will not
# communicate to mongos via the Unix Domain Socket.
hosts = [Config.MONGOS_SOCKET_URI_FMT]
# mongos using Unix Domain Socket to communicate do not use port, Future PR - use port
# when suborinate charm of data-integrator.
port = None

return MongosConfiguration(
database=self.database,
username=self.get_secret(APP_SCOPE, Config.Secrets.USERNAME),
password=self.get_secret(APP_SCOPE, Config.Secrets.PASSWORD),
hosts=hosts,
port=port,
roles=self.extra_user_roles,
tls_external=None, # Future PR will support TLS
tls_internal=None, # Future PR will support TLS
)

@property
def _peers(self) -> Optional[Relation]:
Expand Down
5 changes: 5 additions & 0 deletions src/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ class Config:

MONGOS_PORT = 27018
MONGOS_SOCKET = "/var/snap/charmed-mongodb/common/var/mongodb-27018.sock"
MONGOS_SOCKET_URI_FMT = (
"%2Fvar%2Fsnap%2Fcharmed-mongodb%2Fcommon%2Fvar%2Fmongodb-27018.sock"
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can simplify this with the following:

from urllib.parse import quote

MONGOS_SOCKET_URI_FMT = quote(MONGOS_SOCKET, safe='')

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL!

MONGODB_PORT = 27017
SUBSTRATE = "vm"
ENV_VAR_PATH = "/etc/environment"
Expand Down Expand Up @@ -51,5 +54,7 @@ class Secrets:
SECRET_CACHE_LABEL = "cache"
SECRET_KEYFILE_NAME = "keyfile"
SECRET_INTERNAL_LABEL = "internal-secret"
USERNAME = "username"
PASSWORD = "password"
SECRET_DELETED_LABEL = "None"
MAX_PASSWORD_LENGTH = 4096
7 changes: 2 additions & 5 deletions tests/integration/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,11 @@ async def generate_mongos_uri(ops_test: OpsTest, auth: bool) -> str:
return f"mongodb://{MONGOS_SOCKET}"

secret_uri = await get_application_relation_data(
ops_test, "mongos", "cluster", "secret-user"
ops_test, "application", "mongos_proxy", "secret-user"
)

secret_data = await get_secret_data(ops_test, secret_uri)
username = secret_data.get("username")
password = secret_data.get("password")

return f"mongodb://{username}:{password}@{MONGOS_SOCKET}"
return secret_data.get("uris")
MiaAltieri marked this conversation as resolved.
Show resolved Hide resolved


async def get_secret_data(ops_test, secret_uri) -> Dict:
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/test_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

TEST_USER_NAME = "TestUserName1"
TEST_USER_PWD = "Test123"
TEST_DB_NAME = "test"
TEST_DB_NAME = "my-test-db"


@pytest.mark.abort_on_fail
Expand Down
17 changes: 17 additions & 0 deletions tests/unit/test_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,26 @@

from .helpers import patch_network_get

from charms.data_platform_libs.v0.data_interfaces import DatabaseRequiresEvents

CLUSTER_ALIAS = "cluster"


class TestCharm(unittest.TestCase):
def setUp(self, *unused):
try:
# runs before each test to delete the custom events created for the aliases. This is
# needed because the events are created again in the next test, which causes an error
# related to duplicated events.
delattr(DatabaseRequiresEvents, f"{CLUSTER_ALIAS}_database_created")
delattr(DatabaseRequiresEvents, f"{CLUSTER_ALIAS}_endpoints_changed")
delattr(
DatabaseRequiresEvents, f"{CLUSTER_ALIAS}_read_only_endpoints_changed"
)
except AttributeError:
# Ignore the events not existing before the first test.
pass

self.harness = Harness(MongosOperatorCharm)
self.addCleanup(self.harness.cleanup)
self.harness.begin()
Expand Down
16 changes: 16 additions & 0 deletions tests/unit/test_config_server_lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from .helpers import patch_network_get

from charms.data_platform_libs.v0.data_interfaces import DatabaseRequiresEvents

PEER_ADDR = {"private-address": "127.4.5.6"}
REL_DATA = {
Expand All @@ -18,10 +19,25 @@
}
MONGOS_VAR = "MONGOS_ARGS=--configdb config-server-db/host:port"

CLUSTER_ALIAS = "cluster"


class TestConfigServerInterface(unittest.TestCase):
@patch_network_get(private_address="1.1.1.1")
def setUp(self):
try:
# runs before each test to delete the custom events created for the aliases. This is
# needed because the events are created again in the next test, which causes an error
# related to duplicated events.
delattr(DatabaseRequiresEvents, f"{CLUSTER_ALIAS}_database_created")
delattr(DatabaseRequiresEvents, f"{CLUSTER_ALIAS}_endpoints_changed")
delattr(
DatabaseRequiresEvents, f"{CLUSTER_ALIAS}_read_only_endpoints_changed"
)
except AttributeError:
# Ignore the events not existing before the first test.
pass

self.harness = Harness(MongosOperatorCharm)
self.harness.begin()
self.harness.add_relation("router-peers", "router-peers")
Expand Down