Skip to content

Commit

Permalink
Merge branch '6/edge' into DPE-5089-use-node-port
Browse files Browse the repository at this point in the history
  • Loading branch information
MiaAltieri committed Aug 23, 2024
2 parents 30becd8 + 797118e commit 7c3bebb
Show file tree
Hide file tree
Showing 10 changed files with 222 additions and 193 deletions.
21 changes: 21 additions & 0 deletions .github/.jira_sync_config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Sync GitHub issues to Jira issues

# Configuration syntax:
# https://github.com/canonical/gh-jira-sync-bot/blob/main/README.md#client-side-configuration
settings:
# Repository specific settings
components: # Jira components that will be added to Jira issue
- mongodb-k8s

# Settings shared across Data Platform repositories
label_mapping:
# If the GitHub issue does not have a label in this mapping, the Jira issue will be created as a Bug
enhancement: Story
jira_project_key: DPE # https://warthogs.atlassian.net/browse/DPE
status_mapping:
opened: untriaged
closed: done # GitHub issue closed as completed
not_planned: rejected # GitHub issue closed as not planned
add_gh_comment: true
sync_description: false
sync_comments: false
2 changes: 1 addition & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
uses: canonical/data-platform-workflows/.github/workflows/[email protected]
with:
channel: 6/edge
artifact-name: ${{ needs.build.outputs.artifact-name }}
artifact-prefix: ${{ needs.build.outputs.artifact-prefix }}
secrets:
charmhub-token: ${{ secrets.CHARMHUB_TOKEN }}
permissions:
Expand Down
21 changes: 0 additions & 21 deletions .github/workflows/sync_issue_to_jira.yaml

This file was deleted.

256 changes: 135 additions & 121 deletions poetry.lock

Large diffs are not rendered by default.

56 changes: 32 additions & 24 deletions src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,6 @@

from typing import Set, Optional, Dict
from charms.mongodb.v0.config_server_interface import ClusterRequirer
from tenacity import (
Retrying,
retry,
stop_after_attempt,
wait_fixed,
)


from charms.mongos.v0.set_status import MongosStatusHandler
Expand Down Expand Up @@ -66,7 +60,9 @@ class MongosCharm(ops.CharmBase):

def __init__(self, *args):
super().__init__(*args)
self.framework.observe(self.on.mongos_pebble_ready, self._on_mongos_pebble_ready)
self.framework.observe(
self.on.mongos_pebble_ready, self._on_mongos_pebble_ready
)
self.framework.observe(self.on.start, self._on_start)
self.framework.observe(self.on.update_status, self._on_update_status)
self.tls = MongoDBTLS(self, Config.Relations.PEERS, substrate=Config.SUBSTRATE)
Expand Down Expand Up @@ -137,7 +133,9 @@ def _on_start(self, event: StartEvent) -> None:
# start hooks are fired before relation hooks and `mongos` requires a config-server in
# order to start. Wait to receive config-server info from the relation event before
# starting `mongos` daemon
self.status.set_and_share_status(BlockedStatus("Missing relation to config-server."))
self.status.set_and_share_status(
BlockedStatus("Missing relation to config-server.")
)

def _on_update_status(self, _):
"""Handle the update status event"""
Expand All @@ -156,7 +154,9 @@ def _on_update_status(self, _):
logger.info(
"Missing integration to config-server. mongos cannot run unless connected to config-server."
)
self.status.set_and_share_status(BlockedStatus("Missing relation to config-server."))
self.status.set_and_share_status(
BlockedStatus("Missing relation to config-server.")
)
return

self.status.set_and_share_status(ActiveStatus())
Expand All @@ -169,7 +169,9 @@ def update_external_services(self) -> None:
# every unit attempts to create a nodeport service
# if exists, will silently continue
self.node_port_manager.apply_service(
service=self.node_port_manager.build_node_port_services(port=Config.MONGOS_PORT)
service=self.node_port_manager.build_node_port_services(
port=Config.MONGOS_PORT
)
)

def get_keyfile_contents(self) -> str | None:
Expand All @@ -188,7 +190,9 @@ def get_keyfile_contents(self) -> str | None:

def is_integrated_to_config_server(self) -> bool:
"""Returns True if the mongos application is integrated to a config-server."""
return self.model.get_relation(Config.Relations.CLUSTER_RELATIONS_NAME) is not None
return (
self.model.get_relation(Config.Relations.CLUSTER_RELATIONS_NAME) is not None
)

def _get_mongos_config_for_user(
self, user: MongoDBUser, hosts: Set[str]
Expand Down Expand Up @@ -244,17 +248,14 @@ def remove_secret(self, scope, key) -> None:
content = secret.get_content()

if not content.get(key) or content[key] == Config.Secrets.SECRET_DELETED_LABEL:
logger.error(f"Non-existing secret {scope}:{key} was attempted to be removed.")
logger.error(
f"Non-existing secret {scope}:{key} was attempted to be removed."
)
return

content[key] = Config.Secrets.SECRET_DELETED_LABEL
secret.set_content(content)

@retry(
stop=stop_after_attempt(3),
wait=wait_fixed(2),
reraise=True,
)
def stop_mongos_service(self):
"""Stop mongos service."""
container = self.unit.get_container(Config.CONTAINER_NAME)
Expand All @@ -265,9 +266,8 @@ def restart_charm_services(self):
container = self.unit.get_container(Config.CONTAINER_NAME)
container.stop(Config.SERVICE_NAME)

for _ in Retrying(stop=stop_after_attempt(3), wait=wait_fixed(2), reraise=True):
container.add_layer(Config.CONTAINER_NAME, self._mongos_layer, combine=True)
container.replan()
container.add_layer(Config.CONTAINER_NAME, self._mongos_layer, combine=True)
container.replan()

def set_database(self, database: str) -> None:
"""Updates the database requested for the mongos user."""
Expand All @@ -277,7 +277,9 @@ def set_database(self, database: str) -> None:
return

# a mongos shard can only be related to one config server
config_server_rel = self.model.relations[Config.Relations.CLUSTER_RELATIONS_NAME][0]
config_server_rel = self.model.relations[
Config.Relations.CLUSTER_RELATIONS_NAME
][0]
self.cluster.database_requires.update_relation_data(
config_server_rel.id, {DATABASE_TAG: database}
)
Expand Down Expand Up @@ -394,7 +396,9 @@ def _pull_licenses(container: Container) -> None:

for license_name in licenses:
try:
license_file = container.pull(path=Config.get_license_path(license_name))
license_file = container.pull(
path=Config.get_license_path(license_name)
)
f = open(f"LICENSE_{license_name}", "x")
f.write(str(license_file.read()))
f.close()
Expand All @@ -411,10 +415,14 @@ def _set_data_dir_permissions(container: Container) -> None:
for path in [Config.DATA_DIR]:
paths = container.list_files(path, itself=True)
if not len(paths) == 1:
raise ExtraDataDirError("list_files doesn't return only the directory itself")
raise ExtraDataDirError(
"list_files doesn't return only the directory itself"
)
logger.debug(f"Data directory ownership: {paths[0].user}:{paths[0].group}")
if paths[0].user != Config.UNIX_USER or paths[0].group != Config.UNIX_GROUP:
container.exec(f"chown {Config.UNIX_USER}:{Config.UNIX_GROUP} -R {path}".split())
container.exec(
f"chown {Config.UNIX_USER}:{Config.UNIX_GROUP} -R {path}".split()
)

def push_file_to_unit(
self,
Expand Down
4 changes: 3 additions & 1 deletion src/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ class Status:

# TODO Future PR add more status messages here as constants
UNHEALTHY_UPGRADE = BlockedStatus("Unhealthy after upgrade.")
INVALID_EXTERNAL_CONFIG = BlockedStatus("Config option for expose-external not valid.")
INVALID_EXTERNAL_CONFIG = BlockedStatus(
"Config option for expose-external not valid."
)

class Substrate:
"""Substrate related constants."""
Expand Down
19 changes: 4 additions & 15 deletions tests/integration/client_relations/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,19 @@
# Copyright 2024 Canonical Ltd.
# See LICENSE file for licensing details.

import json
import subprocess
import logging

from typing import Any, Dict, List, Optional, Tuple

from pathlib import Path
import yaml
from pymongo import MongoClient

from dateutil.parser import parse
from pytest_operator.plugin import OpsTest
from tenacity import Retrying, stop_after_delay, wait_fixed
from tenacity import (
RetryError,
)

logger = logging.getLogger(__name__)

PORT_MAPPING_INDEX = 4


def get_port_from_node_port(ops_test: OpsTest, node_port_name: str) -> None:
node_port_cmd = (
f"kubectl get svc -n {ops_test.model.name} | grep NodePort | grep {node_port_name}"
)
node_port_cmd = f"kubectl get svc -n {ops_test.model.name} | grep NodePort | grep {node_port_name}"
result = subprocess.run(node_port_cmd, shell=True, capture_output=True, text=True)
if result.returncode:
logger.info("was not able to find nodeport")
Expand All @@ -51,7 +38,9 @@ def assert_node_port_available(ops_test: OpsTest, node_port_name: str) -> None:


def get_public_k8s_ip() -> str:
result = subprocess.run("kubectl get nodes", shell=True, capture_output=True, text=True)
result = subprocess.run(
"kubectl get nodes", shell=True, capture_output=True, text=True
)

if result.returncode:
logger.info("failed to retrieve public facing k8s IP")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

from ..helpers import (
MONGOS_APP_NAME,
MongoClient,
build_cluster,
deploy_cluster_components,
get_mongos_user_password,
Expand Down Expand Up @@ -41,13 +40,17 @@ async def test_mongos_external_connections(ops_test: OpsTest) -> None:
configuration_parameters = {"expose-external": "nodeport"}

# apply new configuration options
await ops_test.model.applications[MONGOS_APP_NAME].set_config(configuration_parameters)
await ops_test.model.applications[MONGOS_APP_NAME].set_config(
configuration_parameters
)
for unit_id in range(len(ops_test.model.applications[MONGOS_APP_NAME].units)):
assert_node_port_available(
ops_test, node_port_name=f"{MONGOS_APP_NAME}-{unit_id}-external"
)

exposed_node_port = get_port_from_node_port(ops_test, node_port_name="mongos-k8s-nodeport")
exposed_node_port = get_port_from_node_port(
ops_test, node_port_name="mongos-k8s-nodeport"
)
public_k8s_ip = get_public_k8s_ip()
username, password = await get_mongos_user_password(ops_test, MONGOS_APP_NAME)
external_mongos_client = MongoClient(
Expand Down
21 changes: 15 additions & 6 deletions tests/integration/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
# See LICENSE file for licensing details.

import json
import subprocess
import logging

from typing import Any, Dict, List, Optional, Tuple
Expand Down Expand Up @@ -157,7 +156,9 @@ async def wait_for_mongos_units_blocked(
try:
old_interval = (await ops_test.model.get_config())[hook_interval_key]
await ops_test.model.set_config({hook_interval_key: "1m"})
for attempt in Retrying(stop=stop_after_delay(timeout), wait=wait_fixed(1), reraise=True):
for attempt in Retrying(
stop=stop_after_delay(timeout), wait=wait_fixed(1), reraise=True
):
with attempt:
await check_all_units_blocked_with_status(ops_test, db_app_name, status)
finally:
Expand All @@ -167,7 +168,9 @@ async def wait_for_mongos_units_blocked(
async def deploy_cluster_components(ops_test: OpsTest) -> None:
"""Deploys all cluster components and waits for idle."""
mongos_charm = await ops_test.build_charm(".")
resources = {"mongodb-image": METADATA["resources"]["mongodb-image"]["upstream-source"]}
resources = {
"mongodb-image": METADATA["resources"]["mongodb-image"]["upstream-source"]
}
await ops_test.model.deploy(
mongos_charm,
resources=resources,
Expand Down Expand Up @@ -296,7 +299,9 @@ async def get_application_relation_data(
raise ValueError(f"no unit info could be grabbed for { unit.name}")
data = yaml.safe_load(raw_data)
# Filter the data based on the relation name.
relation_data = [v for v in data[unit.name]["relation-info"] if v["endpoint"] == relation_name]
relation_data = [
v for v in data[unit.name]["relation-info"] if v["endpoint"] == relation_name
]

if relation_id:
# Filter the data based on the relation id.
Expand All @@ -318,7 +323,9 @@ async def get_application_relation_data(
return relation_data[0]["application-data"].get(key)


async def get_mongos_user_password(ops_test: OpsTest, app_name=MONGOS_APP_NAME) -> Tuple[str, str]:
async def get_mongos_user_password(
ops_test: OpsTest, app_name=MONGOS_APP_NAME
) -> Tuple[str, str]:
secret_uri = await get_application_relation_data(
ops_test, app_name, relation_name="cluster", key="secret-user"
)
Expand All @@ -335,7 +342,9 @@ async def check_mongos(
uri: str = None,
) -> bool:
"""Returns True if mongos is running on the provided unit."""
mongos_client = await get_direct_mongos_client(ops_test, unit_id, auth, app_name, uri)
mongos_client = await get_direct_mongos_client(
ops_test, unit_id, auth, app_name, uri
)

try:
# wait 10 seconds in case the daemon was just started
Expand Down
6 changes: 5 additions & 1 deletion tests/integration/test_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ async def test_waits_for_config_server(ops_test: OpsTest) -> None:
)


@pytest.mark.group(1)
@pytest.mark.abort_on_fail
async def test_mongos_starts_with_config_server(ops_test: OpsTest) -> None:
# prepare sharded cluster
await ops_test.model.wait_for_idle(
Expand Down Expand Up @@ -104,7 +106,9 @@ async def test_user_with_extra_roles(ops_test: OpsTest) -> None:
)
mongos_client.close()
mongos_host = await get_address_of_unit(ops_test, unit_id=0)
test_user_uri = f"mongodb://{TEST_USER_NAME}:{TEST_USER_PWD}@{mongos_host}:{MONGOS_PORT}"
test_user_uri = (
f"mongodb://{TEST_USER_NAME}:{TEST_USER_PWD}@{mongos_host}:{MONGOS_PORT}"
)
mongos_running = await check_mongos(
ops_test,
unit_id=0,
Expand Down

0 comments on commit 7c3bebb

Please sign in to comment.