Skip to content

Commit

Permalink
update libs
Browse files Browse the repository at this point in the history
  • Loading branch information
MiaAltieri committed Oct 18, 2024
1 parent f9224f3 commit 04ad288
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 25 deletions.
53 changes: 47 additions & 6 deletions lib/charms/mongodb/v0/config_server_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,22 @@
"""
import logging
from typing import Optional
from charms.mongodb.v0.mongo import MongoConnection

from pymongo.errors import PyMongoError
from charms.data_platform_libs.v0.data_interfaces import (
DatabaseProvides,
DatabaseRequestedEvent,
DatabaseRequires,
)
from charms.mongodb.v1.mongos import MongosConnection
from ops.charm import CharmBase, EventBase, RelationBrokenEvent, RelationChangedEvent
from ops.charm import (
CharmBase,
EventBase,
RelationBrokenEvent,
RelationChangedEvent,
RelationCreatedEvent,
)
from ops.framework import Object
from ops.model import (
ActiveStatus,
Expand Down Expand Up @@ -43,7 +51,7 @@

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


class ClusterProvider(Object):
Expand Down Expand Up @@ -179,8 +187,6 @@ def _on_relation_broken(self, event) -> None:
logger.info("Skipping relation broken event, broken event due to scale down")
return

self.charm.client_relations.oversee_users(departed_relation_id, event)

def update_config_server_db(self, event):
"""Provides related mongos applications with new config server db."""
if not self.pass_hook_checks(event):
Expand Down Expand Up @@ -244,7 +250,7 @@ def __init__(
super().__init__(charm, self.relation_name)
self.framework.observe(
charm.on[self.relation_name].relation_created,
self.database_requires._on_relation_created_event,
self._on_relation_created_handler,
)

self.framework.observe(
Expand All @@ -261,6 +267,11 @@ def __init__(
charm.on[self.relation_name].relation_broken, self._on_relation_broken
)

def _on_relation_created_handler(self, event: RelationCreatedEvent) -> None:
logger.info("Integrating to config-server")
self.charm.status.set_and_share_status(WaitingStatus("Connecting to config-server"))
self.database_requires._on_relation_created_event(event)

def _on_database_created(self, event) -> None:
if self.charm.upgrade_in_progress:
logger.warning(
Expand Down Expand Up @@ -303,6 +314,8 @@ def _on_relation_changed(self, event) -> None:

# avoid restarting mongos when possible
if not updated_keyfile and not updated_config and self.is_mongos_running():
# mongos-k8s router must update its users on start
self._update_k8s_users(event)
return

# mongos is not available until it is using new secrets
Expand All @@ -321,6 +334,18 @@ def _on_relation_changed(self, event) -> None:
if self.charm.unit.is_leader():
self.charm.mongos_initialised = True

# mongos-k8s router must update its users on start
self._update_k8s_users(event)

def _update_k8s_users(self, event) -> None:
# K8s can handle its 1:Many users after being initialized
try:
if self.substrate == Config.Substrate.K8S:
self.charm.client_relations.oversee_users(None, None)
except PyMongoError:
event.defer()
logger.debug("failed to add users on mongos-k8s router, will defer and try again.")

def _on_relation_broken(self, event: RelationBrokenEvent) -> None:
# Only relation_deparated events can check if scaling down
if not self.charm.has_departed_run(event.relation.id):
Expand All @@ -334,6 +359,22 @@ def _on_relation_broken(self, event: RelationBrokenEvent) -> None:
logger.info("Skipping relation broken event, broken event due to scale down")
return

# remove all client mongos-k8s users
if (
self.charm.unit.is_leader()
and self.charm.client_relations.remove_all_relational_users()
):
try:
self.charm.client_relations.remove_all_relational_users()

# now that the client mongos users have been removed we can remove ourself
with MongoConnection(self.charm.mongo_config) as mongo:
mongo.drop_user(self.charm.mongo_config.username)
except PyMongoError:
logger.debug("Trouble removing router users, will defer and try again")
event.defer()
return

self.charm.stop_mongos_service()
logger.info("Stopped mongos daemon")

Expand All @@ -348,7 +389,7 @@ def _on_relation_broken(self, event: RelationBrokenEvent) -> None:
if self.substrate == Config.Substrate.VM:
self.charm.remove_connection_info()
else:
self.db_initialised = False
self.charm.db_initialised = False

# BEGIN: helper functions
def pass_hook_checks(self, event):
Expand Down
59 changes: 40 additions & 19 deletions lib/charms/mongodb/v1/mongodb_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,7 @@
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 ops.charm import (
CharmBase,
EventBase,
RelationBrokenEvent,
RelationChangedEvent,
RelationEvent,
)
from ops.charm import CharmBase, EventBase, RelationBrokenEvent, RelationChangedEvent
from ops.framework import Object
from ops.model import Relation
from pymongo.errors import PyMongoError
Expand Down Expand Up @@ -94,11 +88,6 @@ def pass_sanity_hook_checks(self) -> bool:
if not self.charm.db_initialised:
return False

# Warning: the sanity_hook_checks can pass when the call is
# issued by a config-sever because the config-server is allowed to manage the users
# in MongoDB. This is not well named and does not protect integration of a config-server
# to a client application. The mongos charm however doesn't care
# because it supports only one integration that uses MongoDBProvider.
if not self.charm.is_role(Config.Role.MONGOS) and not self.charm.is_relation_feasible(
self.get_relation_name()
):
Expand All @@ -110,14 +99,8 @@ def pass_sanity_hook_checks(self) -> bool:

return True

def pass_hook_checks(self, event: RelationEvent) -> bool:
def pass_hook_checks(self, event: EventBase) -> bool:
"""Runs the pre-hooks checks for MongoDBProvider, returns True if all pass."""
# First, ensure that the relation is valid, useless to do anything else otherwise
if not self.charm.is_role(Config.Role.MONGOS) and not self.charm.is_relation_feasible(
event.relation.name
):
return False

if not self.pass_sanity_hook_checks():
return False

Expand Down Expand Up @@ -218,6 +201,7 @@ def remove_users(
Raises:
PyMongoError
"""

with MongoConnection(self.charm.mongo_config) as mongo:
for username in users_being_managed - expected_current_users:
logger.info("Remove relation user: %s", username)
Expand All @@ -227,6 +211,15 @@ def remove_users(
):
continue

# for user removal of mongos-k8s router, we let the router remove itself
if (
self.charm.is_role(Config.Role.CONFIG_SERVER)
and self.substrate == Config.Substrate.K8S
):
logger.info("K8s routers will remove themselves.")
self._remove_from_relational_users_to_manage(username)
return

mongo.drop_user(username)
self._remove_from_relational_users_to_manage(username)

Expand Down Expand Up @@ -514,6 +507,34 @@ def _add_to_relational_users_to_manage(self, user_to_add: str) -> None:
current_users.add(user_to_add)
self._update_relational_users_to_manage(current_users)

def remove_all_relational_users(self):
"""Removes all users from DB.
Raises: PyMongoError.
"""
with MongoConnection(self.charm.mongo_config) as mongo:
database_users = mongo.get_users()

users_being_managed = database_users.intersection(self._get_relational_users_to_manage())
self.remove_users(users_being_managed, expected_current_users=set())

# now we must remove all of their connection info
for relation in self._get_relations():
fields = self.database_provides.fetch_my_relation_data([relation.id])[relation.id]
self.database_provides.delete_relation_data(relation.id, fields=list(fields))

# unforatunately the above doesn't work to remove secrets, so we forcibly remove the rest
# manually remove the secret before clearing the databag
for unit in relation.units:
secret_id = json.loads(relation.data[unit]["data"])["secret-user"]
# secret id is the same on all units for `secret-user`
break

user_secrets = self.charm.model.get_secret(id=secret_id)
user_secrets.remove_all_revisions()
user_secrets.get_content(refresh=True)
relation.data[self.charm.app].clear()

@staticmethod
def _get_database_from_relation(relation: Relation) -> Optional[str]:
"""Return database name from relation."""
Expand Down

0 comments on commit 04ad288

Please sign in to comment.