Skip to content

Commit

Permalink
feat(DPE-5371): Storage reuse on a different cluster
Browse files Browse the repository at this point in the history
Features:
 * Storage reuse on the same cluster (scale down, scale to zero)
 * Storage reuse on a different cluster with same app name
 * Storage reuse on a different cluster with different app name
  • Loading branch information
Gu1nness committed Sep 17, 2024
1 parent a5e52a9 commit a477ac5
Show file tree
Hide file tree
Showing 11 changed files with 763 additions and 156 deletions.
11 changes: 10 additions & 1 deletion lib/charms/mongodb/v0/mongo.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class NotReadyError(PyMongoError):

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

ADMIN_AUTH_SOURCE = "authSource=admin"
SYSTEM_DBS = ("admin", "local", "config")
Expand Down Expand Up @@ -270,6 +270,11 @@ def get_users(self) -> Set[str]:
]
)

def get_all_users(self) -> Set[str]:
"""Get all users, including the three charmed managed users."""
users_info = self.client.admin.command("usersInfo")
return {user_obj["user"] for user_obj in users_info["users"]}

def get_databases(self) -> Set[str]:
"""Return list of all non-default databases."""
databases = self.client.list_database_names()
Expand All @@ -280,3 +285,7 @@ def drop_database(self, database: str):
if database in SYSTEM_DBS:
return
self.client.drop_database(database)

def drop_local_database(self):
"""DANGEROUS: Drops the local database."""
self.client.drop_database("local")
31 changes: 30 additions & 1 deletion lib/charms/mongodb/v1/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,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 = 11

# path to store mongodb ketFile
KEY_FILE = "keyFile"
Expand Down Expand Up @@ -176,6 +176,8 @@ def get_mongod_args(
auth: bool = True,
snap_install: bool = False,
role: str = "replication",
machine_ip: str = "127.0.0.1",
degraded: bool = False,
) -> str:
"""Construct the MongoDB startup command line.
Expand All @@ -186,6 +188,24 @@ def get_mongod_args(
full_conf_dir = f"{MONGODB_SNAP_DATA_DIR}{CONF_DIR}" if snap_install else CONF_DIR
logging_options = _get_logging_options(snap_install)
audit_log_settings = _get_audit_log_settings(snap_install)
if degraded:
cmd = [
# Only bind to local IP
f"--bind_ip {machine_ip}",
# db must be located within the snap common directory since the
# snap is strictly confined
f"--dbpath={full_data_dir}",
# for simplicity we run the mongod daemon on shards, configsvrs,
# and replicas on the same port
f"--port={Config.MONGODB_PORT}",
"--setParameter processUmask=037", # required for log files perminission (g+r)
"--logRotate reopen",
"--logappend",
logging_options,
"--setParameter enableLocalhostAuthBypass=0",
"\n",
]
return " ".join(cmd)
cmd = [
# bind to localhost and external interfaces
"--bind_ip_all",
Expand Down Expand Up @@ -266,6 +286,15 @@ def generate_keyfile() -> str:
return "".join([secrets.choice(choices) for _ in range(1024)])


def generate_lock_hash() -> str:
"""Lock hash used to check if we are reusing storage in a different context or not.
Returns:
An 8 character random hexadecimal string.
"""
return secrets.token_hex(8)


def copy_licenses_to_unit():
"""Copies licenses packaged in the snap to the charm's licenses directory."""
os.makedirs("src/licenses", exist_ok=True)
Expand Down
44 changes: 42 additions & 2 deletions lib/charms/mongodb/v1/mongodb.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# See LICENSE file for licensing details.

import logging
from typing import Dict, Set
from typing import Dict, Set, Tuple

from bson.json_util import dumps
from charms.mongodb.v0.mongo import MongoConfiguration, MongoConnection, NotReadyError
Expand All @@ -27,7 +27,7 @@

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

# path to store mongodb ketFile
logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -95,6 +95,35 @@ def init_replset(self) -> None:
# finished.
logger.error("Cannot initialize replica set. error=%r", e)
raise e
else:
logger.error("Error in init %s", e)

@retry(
stop=stop_after_attempt(3),
wait=wait_fixed(5),
reraise=True,
before=before_log(logger, logging.DEBUG),
)
def reconfigure_replset(self, hosts: set[str], version: int, force: bool = False) -> None:
"""Create replica set config the first time.
Raises:
ConfigurationError, ConfigurationError, OperationFailure
"""
config = {
"_id": self.config.replset,
"members": [{"_id": i, "host": h} for i, h in enumerate(hosts)],
"version": version,
}
try:
self.client.admin.command("replSetReconfig", config, force=force)
except OperationFailure as e:
# Unauthorized error can be raised only if initial user were
# created the step after this.
# AlreadyInitialized error can be raised only if this step
# finished.
logger.error("Cannot reconfigure replica set. error=%r", e)
raise e

def get_replset_status(self) -> Dict:
"""Get a replica set status as a dict.
Expand Down Expand Up @@ -128,6 +157,17 @@ def get_replset_members(self) -> Set[str]:
]
return set(curr_members)

def get_replset_members_and_version(self) -> Tuple[Set[str], int]:
"""Get replica set members through config."""
rs_config = self.client.admin.command("replSetGetConfig")
curr_members = [
self._hostname_from_hostport(member["host"])
for member in rs_config["config"]["members"]
]
version = rs_config["config"]["version"]

return set(curr_members), version

def add_replset_member(self, hostname: str) -> None:
"""Add a new member to replica set config inside MongoDB.
Expand Down
Loading

0 comments on commit a477ac5

Please sign in to comment.