Skip to content

Commit

Permalink
Merge pull request #42 from canonical/update-libs
Browse files Browse the repository at this point in the history
[DPE-4757] Pre-cursor to supporting upgrades -  Update MongoDB libs
  • Loading branch information
MiaAltieri authored Jul 4, 2024
2 parents 0486be7 + 14408a6 commit 644eb39
Show file tree
Hide file tree
Showing 15 changed files with 1,604 additions and 865 deletions.
18 changes: 15 additions & 3 deletions 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 = 10
LIBPATCH = 12


class ClusterProvider(Object):
Expand Down Expand Up @@ -85,6 +85,13 @@ def pass_hook_checks(self, event: EventBase) -> bool:
if not self.charm.unit.is_leader():
return False

if self.charm.upgrade_in_progress:
logger.warning(
"Processing mongos applications is not supported during an upgrade. The charm may be in a broken, unrecoverable state."
)
event.defer()
return False

return True

def is_valid_mongos_integration(self) -> bool:
Expand Down Expand Up @@ -130,6 +137,11 @@ def _on_relation_changed(self, event) -> None:
self.database_provides.update_relation_data(event.relation.id, relation_data)

def _on_relation_broken(self, event) -> None:
if self.charm.upgrade_in_progress:
logger.warning(
"Removing integration to mongos is not supported during an upgrade. The charm may be in a broken, unrecoverable state."
)

# Only relation_deparated events can check if scaling down
departed_relation_id = event.relation.id
if not self.charm.has_departed_run(departed_relation_id):
Expand Down Expand Up @@ -172,7 +184,7 @@ def generate_config_server_db(self) -> str:
"""Generates the config server database for mongos to connect to."""
replica_set_name = self.charm.app.name
hosts = []
for host in self.charm._unit_ips:
for host in self.charm.unit_ips:
hosts.append(f"{host}:{Config.MONGODB_PORT}")

hosts = ",".join(hosts)
Expand Down Expand Up @@ -378,7 +390,7 @@ def get_tls_statuses(self) -> Optional[StatusBase]:
)
return BlockedStatus("mongos CA and Config-Server CA don't match.")

return
return None

def get_config_server_name(self) -> Optional[str]:
"""Returns the name of the Juju Application that mongos is using as a config server."""
Expand Down
80 changes: 77 additions & 3 deletions lib/charms/mongodb/v0/mongodb.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Code for interactions with MongoDB."""

# Copyright 2023 Canonical Ltd.
# See LICENSE file for licensing details.

Expand All @@ -21,6 +22,8 @@
wait_fixed,
)

from config import Config

# The unique Charmhub library identifier, never change it
LIBID = "49c69d9977574dd7942eb7b54f43355b"

Expand All @@ -29,12 +32,16 @@

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

# path to store mongodb ketFile
logger = logging.getLogger(__name__)


class FailedToMovePrimaryError(Exception):
"""Raised when attempt to move a primary fails."""


@dataclass
class MongoDBConfiguration:
"""Class for MongoDB configuration.
Expand All @@ -56,6 +63,7 @@ class MongoDBConfiguration:
roles: Set[str]
tls_external: bool
tls_internal: bool
standalone: bool = False

@property
def uri(self):
Expand All @@ -65,6 +73,14 @@ def uri(self):
auth_source = ""
if self.database != "admin":
auth_source = "&authSource=admin"

if self.standalone:
return (
f"mongodb://{quote_plus(self.username)}:"
f"{quote_plus(self.password)}@"
f"localhost:{Config.MONGODB_PORT}/?authSource=admin"
)

return (
f"mongodb://{quote_plus(self.username)}:"
f"{quote_plus(self.password)}@"
Expand Down Expand Up @@ -224,7 +240,7 @@ def add_replset_member(self, hostname: str) -> None:
# Such operation reduce performance of the cluster. To avoid huge performance
# degradation, before adding new members, it is needed to check that all other
# members finished init sync.
if self._is_any_sync(rs_status):
if self.is_any_sync(rs_status):
# it can take a while, we should defer
raise NotReadyError

Expand Down Expand Up @@ -274,6 +290,64 @@ def remove_replset_member(self, hostname: str) -> None:
logger.debug("rs_config: %r", dumps(rs_config["config"]))
self.client.admin.command("replSetReconfig", rs_config["config"])

def step_down_primary(self) -> None:
"""Steps down the current primary, forcing a re-election."""
self.client.admin.command("replSetStepDown", {"stepDownSecs": "60"})

def move_primary(self, new_primary_ip: str) -> None:
"""Forcibly moves the primary to the new primary provided.
Args:
new_primary_ip: ip address of the unit chosen to be the new primary.
"""
# Do not move a priary unless the cluster is in sync
rs_status = self.client.admin.command("replSetGetStatus")
if self.is_any_sync(rs_status):
# it can take a while, we should defer
raise NotReadyError

is_move_successful = True
self.set_replicaset_election_priority(priority=0.5, ignore_member=new_primary_ip)
try:
for attempt in Retrying(stop=stop_after_delay(180), wait=wait_fixed(3)):
with attempt:
self.step_down_primary()
if self.primary() != new_primary_ip:
raise FailedToMovePrimaryError
except RetryError:
# catch all possible exceptions when failing to step down primary. We do this in order
# to ensure that we reset the replica set election priority.
is_move_successful = False

# reset all replicas to the same priority
self.set_replicaset_election_priority(priority=1)

if not is_move_successful:
raise FailedToMovePrimaryError

def set_replicaset_election_priority(self, priority: int, ignore_member: str = None) -> None:
"""Set the election priority for the entire replica set."""
rs_config = self.client.admin.command("replSetGetConfig")
rs_config = rs_config["config"]
rs_config["version"] += 1

# keep track of the original configuration before setting the priority, reconfiguring the
# replica set can result in primary re-election, which would would like to avoid when
# possible.
original_rs_config = rs_config

for member in rs_config["members"]:
if member["host"] == ignore_member:
continue

member["priority"] = priority

if original_rs_config == rs_config:
return

logger.debug("rs_config: %r", rs_config)
self.client.admin.command("replSetReconfig", rs_config)

def create_user(self, config: MongoDBConfiguration):
"""Create user.
Expand Down Expand Up @@ -402,7 +476,7 @@ def primary(self) -> str:
return primary

@staticmethod
def _is_any_sync(rs_status: Dict) -> bool:
def is_any_sync(rs_status: Dict) -> bool:
"""Returns true if any replica set members are syncing data.
Checks if any members in replica set are syncing data. Note it is recommended to run only
Expand Down
1 change: 1 addition & 0 deletions lib/charms/mongodb/v0/mongodb_secrets.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Secrets related helper classes/functions."""

# Copyright 2023 Canonical Ltd.
# See LICENSE file for licensing details.

Expand Down
26 changes: 21 additions & 5 deletions lib/charms/mongodb/v0/mongodb_tls.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
import socket
from typing import List, Optional, Tuple

from charms.tls_certificates_interface.v1.tls_certificates import (
from charms.tls_certificates_interface.v3.tls_certificates import (
CertificateAvailableEvent,
CertificateExpiringEvent,
TLSCertificatesRequiresV1,
TLSCertificatesRequiresV3,
generate_csr,
generate_private_key,
)
Expand All @@ -38,7 +38,7 @@

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

logger = logging.getLogger(__name__)

Expand All @@ -52,7 +52,7 @@ def __init__(self, charm, peer_relation, substrate):
self.charm = charm
self.substrate = substrate
self.peer_relation = peer_relation
self.certs = TLSCertificatesRequiresV1(self.charm, Config.TLS.TLS_PEER_RELATION)
self.certs = TLSCertificatesRequiresV3(self.charm, Config.TLS.TLS_PEER_RELATION)
self.framework.observe(
self.charm.on.set_tls_private_key_action, self._on_set_tls_private_key
)
Expand Down Expand Up @@ -81,6 +81,11 @@ def _on_set_tls_private_key(self, event: ActionEvent) -> None:
event.fail("Mongos cannot set TLS keys until integrated to config-server.")
return

if self.charm.upgrade_in_progress:
logger.warning("Setting TLS key during an upgrade is not supported.")
event.fail("Setting TLS key during an upgrade is not supported.")
return

try:
self.request_certificate(event.params.get("external-key", None), internal=False)
self.request_certificate(event.params.get("internal-key", None), internal=True)
Expand Down Expand Up @@ -141,12 +146,23 @@ def _on_tls_relation_joined(self, event: RelationJoinedEvent) -> None:
event.defer()
return

if self.charm.upgrade_in_progress:
logger.warning(
"Enabling TLS is not supported during an upgrade. The charm may be in a broken, unrecoverable state."
)
event.defer()
return

self.request_certificate(None, internal=True)
self.request_certificate(None, internal=False)

def _on_tls_relation_broken(self, event: RelationBrokenEvent) -> None:
"""Disable TLS when TLS relation broken."""
logger.debug("Disabling external and internal TLS for unit: %s", self.charm.unit.name)
if self.charm.upgrade_in_progress:
logger.warning(
"Disabling TLS is not supported during an upgrade. The charm may be in a broken, unrecoverable state."
)

for internal in [True, False]:
self.set_tls_secret(internal, Config.TLS.SECRET_CA_LABEL, None)
Expand Down Expand Up @@ -320,7 +336,7 @@ def get_tls_files(self, internal: bool) -> Tuple[Optional[str], Optional[str]]:
def get_host(self, unit: Unit):
"""Retrieves the hostname of the unit based on the substrate."""
if self.substrate == "vm":
return self.charm._unit_ip(unit)
return self.charm.unit_ip(unit)
else:
return self.charm.get_hostname_for_unit(unit)

Expand Down
1 change: 1 addition & 0 deletions lib/charms/mongodb/v1/users.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Users configuration for MongoDB."""

# Copyright 2023 Canonical Ltd.
# See LICENSE file for licensing details.
from typing import Set
Expand Down
17 changes: 12 additions & 5 deletions lib/charms/mongos/v0/mongos_client_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from ops.charm import CharmBase
from charms.data_platform_libs.v0.data_interfaces import (
DatabaseProvides,
DatabaseRequestedEvent,
)

from charms.mongodb.v1.mongos import MongosConfiguration
Expand Down Expand Up @@ -75,11 +74,15 @@ def __init__(self, *args):
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:
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)
self.database_provides = DatabaseProvides(
self.charm, relation_name=self.relation_name
)

super().__init__(charm, self.relation_name)
self.framework.observe(
Expand All @@ -98,7 +101,9 @@ def _on_relation_changed(self, event) -> None:
or self.charm.database
)
new_extra_user_roles = (
self.database_provides.fetch_relation_field(event.relation.id, USER_ROLES_KEY)
self.database_provides.fetch_relation_field(
event.relation.id, USER_ROLES_KEY
)
or self.charm.extra_user_roles
)
external_connectivity = (
Expand Down Expand Up @@ -133,7 +138,9 @@ def update_connection_info(self, config: MongosConfiguration) -> None:
"""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_credentials(
relation.id, config.username, config.password
)
self.database_provides.set_database(relation.id, config.database)
self.database_provides.set_uris(
relation.id,
Expand Down
Loading

0 comments on commit 644eb39

Please sign in to comment.