Skip to content

Commit

Permalink
[DPE-2992] - Provide config db to mongos (#311)
Browse files Browse the repository at this point in the history
## Issue
Related mongos charm cannot start without configdb string

## Solution
Provide configdb string to mongos charm

## Testing
```
juju deploy application
juju deploy mongos
juju deploy ./*charm --config role="config-server" config-server-one 
juju deploy ./*charm --config role="shard" shard-one 

# relate
juju integrate mongos application
juju integrate config-server-one:config-server shard-one:sharding
juju integrate mongos:cluster config-server-one:cluster

# relate shards
juju ssh application/0
sudo snap logs charmed-mongodb.mongos
charmed-mongodb.mongosh mongodb://localhost:27018
```

## Future PRs
1. implement this on the mongos charm side
2. basic int test
  • Loading branch information
MiaAltieri authored Dec 1, 2023
1 parent 0794ee0 commit 6faf6d8
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 23 deletions.
88 changes: 71 additions & 17 deletions lib/charms/mongodb/v0/config_server_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,23 @@
This class handles the sharing of secrets between sharded components, adding shards, and removing
shards.
"""
import json
import logging

from charms.mongodb.v1.helpers import add_args_to_env, get_mongos_args
from charms.mongodb.v1.mongos import MongosConnection
from ops.charm import CharmBase, EventBase
from ops.framework import Object
from ops.model import WaitingStatus
from ops.model import ActiveStatus, MaintenanceStatus, WaitingStatus

from config import Config

logger = logging.getLogger(__name__)
KEYFILE_KEY = "key-file"
KEY_FILE = "keyFile"
HOSTS_KEY = "host"
CONFIG_SERVER_URI_KEY = "config-server-uri"
CONFIG_SERVER_DB_KEY = "config-server-db"
MONGOS_SOCKET_URI_FMT = "%2Fvar%2Fsnap%2Fcharmed-mongodb%2Fcommon%2Fvar%2Fmongodb-27018.sock"

# The unique Charmhub library identifier, never change it
LIBID = "58ad1ccca4974932ba22b97781b9b2a0"
Expand All @@ -28,7 +32,7 @@

# 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


class ClusterProvider(Object):
Expand All @@ -47,6 +51,7 @@ def __init__(
)

# TODO Future PRs handle scale down
# TODO Future PRs handle changing of units/passwords to be propagated to mongos

def pass_hook_checks(self, event: EventBase) -> bool:
"""Runs the pre-hooks checks for ClusterProvider, returns True if all pass."""
Expand All @@ -66,20 +71,22 @@ def pass_hook_checks(self, event: EventBase) -> bool:

return True

def _on_relation_joined(self, event):
def _on_relation_joined(self, event) -> None:
"""Handles providing mongos with KeyFile and hosts."""
if not self.pass_hook_checks(event):
logger.info("Skipping relation joined event: hook checks did not pass")
return

# TODO Future PR, provide URI
config_server_db = self.generate_config_server_db()

# TODO Future PR, use secrets
self._update_relation_data(
event.relation.id,
{
KEYFILE_KEY: self.charm.get_secret(
Config.Relations.APP_SCOPE, Config.Secrets.SECRET_KEYFILE_NAME
),
CONFIG_SERVER_DB_KEY: config_server_db,
},
)

Expand All @@ -99,6 +106,16 @@ def _update_relation_data(self, relation_id: int, data: dict) -> None:
if relation:
relation.data[self.charm.model.app].update(data)

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:
hosts.append(f"{host}:{Config.MONGODB_PORT}")

hosts = ",".join(hosts)
return f"{replica_set_name}/{hosts}"


class ClusterRequirer(Object):
"""Manage relations between the config server and mongos router on the mongos side."""
Expand All @@ -116,35 +133,72 @@ def __init__(
)
# TODO Future PRs handle scale down

def _on_relation_changed(self, event):
def _on_relation_changed(self, event) -> None:
"""Starts/restarts monogs with config server information."""
relation_data = event.relation.data[event.app]
if not relation_data.get(KEYFILE_KEY):
if not relation_data.get(KEYFILE_KEY) or not relation_data.get(CONFIG_SERVER_DB_KEY):
event.defer()
self.charm.unit.status = WaitingStatus("Waiting for secrets from config-server")
return

self.update_keyfile(key_file_contents=relation_data.get(KEYFILE_KEY))
updated_keyfile = self.update_keyfile(key_file_contents=relation_data.get(KEYFILE_KEY))
updated_config = self.update_config_server_db(
config_server_db=relation_data.get(CONFIG_SERVER_DB_KEY)
)

# avoid restarting mongos when possible
if not updated_keyfile and not updated_config and self.is_mongos_running():
return

# mongos is not available until it is using new secrets
logger.info("Restarting mongos with new secrets")
self.charm.unit.status = MaintenanceStatus("starting mongos")
self.charm.restart_mongos_service()

# restart on high loaded databases can be very slow (e.g. up to 10-20 minutes).
if not self.is_mongos_running():
logger.info("mongos has not started, deferring")
self.charm.unit.status = WaitingStatus("Waiting for mongos to start")
event.defer()
return

# TODO: Follow up PR. Start mongos with the config-server URI
# TODO: Follow up PR. Add a user for mongos once it has been started
self.charm.unit.status = ActiveStatus()

def is_mongos_running(self) -> bool:
"""Returns true if mongos service is running."""
with MongosConnection(None, f"mongodb://{MONGOS_SOCKET_URI_FMT}") as mongo:
return mongo.is_ready

def update_config_server_db(self, config_server_db) -> bool:
"""Updates config server str when necessary."""
if self.charm.config_server_db == config_server_db:
return False

mongos_config = self.charm.mongos_config
mongos_start_args = get_mongos_args(
mongos_config, snap_install=True, config_server_db=config_server_db
)
add_args_to_env("MONGOS_ARGS", mongos_start_args)
self.charm.unit_peer_data["config_server_db"] = json.dumps(config_server_db)
return True

def update_keyfile(self, key_file_contents: str) -> None:
"""Updates keyfile on all units."""
def update_keyfile(self, key_file_contents: str) -> bool:
"""Updates keyfile when necessary."""
# keyfile is set by leader in application data, application data does not necessarily
# match what is on the machine.
current_key_file = self.charm.get_keyfile_contents()
if not key_file_contents or key_file_contents == current_key_file:
return
return False

# put keyfile on the machine with appropriate permissions
self.charm.push_file_to_unit(
parent_dir=Config.MONGOD_CONF_DIR, file_name=KEY_FILE, file_contents=key_file_contents
)

if not self.charm.unit.is_leader():
return
if self.charm.unit.is_leader():
self.charm.set_secret(
Config.Relations.APP_SCOPE, Config.Secrets.SECRET_KEYFILE_NAME, key_file_contents
)

self.charm.set_secret(
Config.Relations.APP_SCOPE, Config.Secrets.SECRET_KEYFILE_NAME, key_file_contents
)
return True
19 changes: 13 additions & 6 deletions lib/charms/mongodb/v1/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

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

# path to store mongodb ketFile
KEY_FILE = "keyFile"
Expand Down Expand Up @@ -81,24 +81,31 @@ def get_create_user_cmd(config: MongoDBConfiguration, mongo_path=MONGO_SHELL) ->


def get_mongos_args(
config: MongoDBConfiguration,
config,
snap_install: bool = False,
config_server_db: str = None,
) -> str:
"""Returns the arguments used for starting mongos on a config-server side application.
Returns:
A string representing the arguments to be passed to mongos.
"""
# suborinate charm which provides its own config_server_db, should only use unix domain socket
binding_ips = (
f"--bind_ip {MONGODB_COMMON_DIR}/var/mongodb-27018.sock"
if config_server_db
else "--bind_ip_all"
)

# mongos running on the config server communicates through localhost
# use constant for port
config_server_uri = f"{config.replset}/localhost:27017"
config_server_db = config_server_db or f"{config.replset}/localhost:{Config.MONGODB_PORT}"

full_conf_dir = f"{MONGODB_SNAP_DATA_DIR}{CONF_DIR}" if snap_install else CONF_DIR
cmd = [
# mongos on config server side should run on 0.0.0.0 so it can be accessed by other units
# in the sharded cluster
"--bind_ip_all",
f"--configdb {config_server_uri}",
binding_ips,
f"--configdb {config_server_db}",
# config server is already using 27017
f"--port {Config.MONGOS_PORT}",
f"--keyFile={full_conf_dir}/{KEY_FILE}",
Expand Down

0 comments on commit 6faf6d8

Please sign in to comment.