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

update libs #354

Merged
merged 7 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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 @@ -14,8 +14,15 @@
DatabaseRequestedEvent,
DatabaseRequires,
)
from charms.mongodb.v0.mongo import MongoConnection
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 All @@ -24,6 +31,7 @@
StatusBase,
WaitingStatus,
)
from pymongo.errors import PyMongoError

from config import Config

Expand All @@ -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
58 changes: 39 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 @@ -227,6 +210,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 +506,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
Loading