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

DPE-1794 Implement COS integration #93

Merged
merged 40 commits into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
84af432
WIP: Initial implementation of COS with a lot of rough edges
shayancanonical Nov 20, 2023
a71cbd1
WIP: rough working prototype of cos integration
shayancanonical Nov 21, 2023
fb501f7
Replace secrets charm lib with published one
shayancanonical Nov 27, 2023
26c99d4
Merge branch 'main' into feature/cos_integration
shayancanonical Nov 27, 2023
bb7f341
WIP
shayancanonical Nov 27, 2023
79a5d90
Fix bug where credentials were passed to router options instead of th…
shayancanonical Nov 28, 2023
efea884
Address PR feedback
shayancanonical Dec 1, 2023
4a20fa7
Address PR feedback + update data_interfaces and cos_agent charm libs
shayancanonical Dec 1, 2023
927cffe
Merge branch 'main' into feature/cos_integration
shayancanonical Dec 1, 2023
05302d9
Add integration test for the exporter endpoint
shayancanonical Dec 4, 2023
feaa8ad
Increase timeout for exporter tests to avoid unnecessary timeout erro…
shayancanonical Dec 4, 2023
426bddf
Skip exporter tests on focal
shayancanonical Dec 4, 2023
88da3a9
Delete extra ) from CI workflow file
shayancanonical Dec 4, 2023
7a241ac
Try removing curly braces from integration test condition in ci.yaml
shayancanonical Dec 4, 2023
5cb2d56
Try using single quotes for matrix variable values in integration tes…
shayancanonical Dec 4, 2023
743604a
Try adding curly braces back + use single quotes instead of double qu…
shayancanonical Dec 4, 2023
0418ba5
Try to exclude matrix entry for exporter tests in focal
shayancanonical Dec 5, 2023
234bf0b
Add comment explaining why we skip exporter tests on focal
shayancanonical Dec 5, 2023
44f00cf
Import latest version of data_secrets lib and test focal compatibilit…
shayancanonical Mar 4, 2024
8027248
Merge branch 'main' into feature/cos_integration
shayancanonical Mar 4, 2024
3235040
Update outdated charm libs
shayancanonical Mar 4, 2024
b215877
Specify series for subordinate charms to avoid conflict with principa…
shayancanonical Mar 5, 2024
6689a35
Merge branch 'main' into feature/cos_integration
shayancanonical Mar 5, 2024
b16525e
Fix typos and issues from merge conflict resolution
shayancanonical Mar 6, 2024
a7e6c77
Reconcile all workloads (router, exporter, tls) in one method
shayancanonical Mar 7, 2024
908a011
Run code format
shayancanonical Mar 7, 2024
8ef349b
Address minor PR feedback
shayancanonical Mar 11, 2024
b0cba95
Update outdated charm libs
shayancanonical Mar 11, 2024
1776402
Address PR feedback
shayancanonical Mar 13, 2024
9d03646
Minor leftover improvements + fix bugs
shayancanonical Mar 13, 2024
d45df31
Fix typo in method call
shayancanonical Mar 13, 2024
635d807
Fix bugs + remove usage of data_secrets and use dynamic usage of secr…
shayancanonical Mar 14, 2024
d5b41ce
Another round of feedback + move abstracted secrets code to a separat…
shayancanonical Mar 14, 2024
bdb40a9
Address PR feedback
shayancanonical Mar 15, 2024
ade2bff
Leftovers cleanup
shayancanonical Mar 15, 2024
917d965
Fix bug introduced during refactor
shayancanonical Mar 15, 2024
6e76961
Address feedback + make database test more resilient by using block_u…
shayancanonical Mar 15, 2024
50044f0
Avoid using reconcile_services wrapper in workload
shayancanonical Mar 18, 2024
9665f3e
Make properties in abstract charm private as they will not be invoked…
shayancanonical Mar 18, 2024
7e49cd7
Instantiate database_provides in abstract_charm for type hinting
shayancanonical Mar 18, 2024
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
59 changes: 34 additions & 25 deletions src/abstract_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,13 @@
import ops
import tenacity

import constants
import container
import lifecycle
import logrotate
import machine_upgrade
import relations.cos
import relations.database_provides
import relations.database_requires
import relations.secrets
import server_exceptions
import upgrade
import workload
Expand All @@ -29,8 +28,6 @@
class MySQLRouterCharm(ops.CharmBase, abc.ABC):
"""MySQL Router charm"""

_COS_PEER_RELATION_NAME = "cos"

def __init__(self, *args) -> None:
super().__init__(*args)
# Instantiate before registering other event observers
Expand Down Expand Up @@ -62,12 +59,6 @@ def __init__(self, *args) -> None:
self._upgrade_relation_created,
)

self.cos_secrets = relations.secrets.RelationSecrets(
self,
self._COS_PEER_RELATION_NAME,
unit_secret_fields=[constants.MONITORING_PASSWORD_KEY],
)

@property
@abc.abstractmethod
def _subordinate_relation_endpoint_names(self) -> typing.Optional[typing.Iterable[str]]:
Expand All @@ -76,12 +67,6 @@ def _subordinate_relation_endpoint_names(self) -> typing.Optional[typing.Iterabl
Does NOT include relations where charm is principal
"""

@property
def _tls_certificate_saved(self) -> bool:
"""Whether a TLS certificate is available to use"""
# TODO VM TLS: Remove property after implementing TLS on machine charm
return False

@property
@abc.abstractmethod
def _container(self) -> container.Container:
Expand All @@ -97,6 +82,11 @@ def _upgrade(self) -> typing.Optional[upgrade.Upgrade]:
def _logrotate(self) -> logrotate.LogRotate:
"""logrotate"""

@property
@abc.abstractmethod
def _cos(self) -> relations.cos.COSRelation:
"""COS"""

Comment on lines +86 to +90
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is relations/cos.py vm specific?

if not, this method shouldn't be abstract

if so, the type here should be an abstract implementation, not a concrete implementation (feel free to ignore for now, but keep in mind for k8s)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

related to #93 (comment)

@property
@abc.abstractmethod
def _read_write_endpoint(self) -> str:
Expand All @@ -107,6 +97,31 @@ def _read_write_endpoint(self) -> str:
def _read_only_endpoint(self) -> str:
"""MySQL Router read-only endpoint"""

@property
def tls_certificate_saved(self) -> bool:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this be private method?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

related to #93 (comment)

"""Whether a TLS certificate is available to use"""
# TODO VM TLS: Remove property after implementing TLS on machine_charm
shayancanonical marked this conversation as resolved.
Show resolved Hide resolved
return False

@property
def tls_key(self) -> str:
"""Custom TLS key"""
# TODO VM TLS: Remove property after implementing TLS on machine_charm
return None

@property
def tls_certificate(self) -> str:
"""Custom TLS certificate"""
# TODO VM TLS: Remove property after implementing TLS on machine_charm
return None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not in this pr but when this lands in k8s, I think these should be moved to tls.py

similar to https://github.com/canonical/mysql-router-k8s-operator/blob/d43a43481b7f966db340fa994a93cb18cca592dd/src/kubernetes_charm.py#L53


def cos_exporter_config(self, event) -> relations.cos.ExporterConfig:
shayancanonical marked this conversation as resolved.
Show resolved Hide resolved
"""Returns the exporter config for MySQLRouter exporter if cos relation exists"""
cos_relation_exists = self._cos.relation_exists and not self._cos.is_relation_breaking(
event
)
return self._cos.exporter_user_config if cos_relation_exists else None

def get_workload(self, *, event):
"""MySQL Router workload"""
if connection_info := self._database_requires.get_connection_info(event=event):
Expand All @@ -118,7 +133,7 @@ def get_workload(self, *, event):
charm_=self,
)
return self._workload_type(
container_=self._container, logrotate_=self._logrotate, cos=self._cos
container_=self._container, logrotate_=self._logrotate, cos=self._cos, charm_=self
shayancanonical marked this conversation as resolved.
Show resolved Hide resolved
)

@staticmethod
Expand Down Expand Up @@ -230,7 +245,7 @@ def reconcile(self, event=None) -> None: # noqa: C901
return
if self._upgrade.unit_state == "outdated":
if self._upgrade.authorized:
self._upgrade.upgrade_unit(workload_=workload_, tls=False)
self._upgrade.upgrade_unit(workload_=workload_, tls=self.tls_certificate_saved)
else:
self.set_status(event=event)
logger.debug("Waiting to upgrade")
Expand Down Expand Up @@ -265,13 +280,7 @@ def reconcile(self, event=None) -> None: # noqa: C901
shell=workload_.shell,
)
if workload_.container_ready:
cos_relation_exists = (
self._cos.relation_exists and not self._cos.is_relation_breaking(event)
)
workload_.reconcile(
unit_name=self.unit.name,
exporter_config=self._cos.exporter_user_info if cos_relation_exists else None,
)
workload_.reconcile_services(event)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use inputs instead of passing the entire charm instance with indirect inputs

(so that the dependencies are explicit & reduced)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

passing inputs is acceptable when calling _reconcile from abstract_charm.py. these inputs are values collected from tls and cos at the moment

however, i am not a fan of tls files import cos just so it can pass in the necessary inputs

solution proposed in this PR:

  • abstract_charm exposes properties that expose these inputs (since abstract_charm already can import all of the relevant files)
  • pass a reference to the charm into the workload class, and use above properties to retrieve inputs

as an alternative, i am ok with tls files calling the properties on the charm class (as it will have the reference to the charm object) rather than a wrapper (_reconcile_services)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as an alternative

yeah, my suggestion was to call the reconcile event handler from tls code (similar to https://github.com/canonical/mysql-router-k8s-operator/blob/d43a43481b7f966db340fa994a93cb18cca592dd/src/relations/tls.py#L136)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed in 50044f0

# Empty waiting status means we're waiting for database requires relation before
# starting workload
if not workload_.status or workload_.status == ops.WaitingStatus():
Expand Down
6 changes: 0 additions & 6 deletions src/constants.py

This file was deleted.

2 changes: 2 additions & 0 deletions src/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ def update_mysql_router_exporter_service(
enabled: Whether MySQL Router exporter service is enabled
config: The configuration for MySQL Router exporter
"""
if enabled and not config:
raise ValueError("Missing MySQL Router exporter config")

@abc.abstractmethod
def upgrade(self, unit: ops.Unit) -> None:
Expand Down
8 changes: 6 additions & 2 deletions src/machine_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def __init__(self, *args) -> None:
super().__init__(*args)
# DEPRECATED shared-db: Enable legacy "mysql-shared" interface
self._database_provides = relations.database_providers_wrapper.RelationEndpoint(self)
self._cos = relations.cos.COSRelation(self, self._container)
self._cos_relation = relations.cos.COSRelation(self, self._container)

self._authenticated_workload_type = socket_workload.AuthenticatedSocketWorkload
self.framework.observe(self.on.install, self._on_install)
Expand Down Expand Up @@ -61,6 +61,10 @@ def _upgrade(self) -> typing.Optional[machine_upgrade.Upgrade]:
def _logrotate(self) -> machine_logrotate.LogRotate:
return machine_logrotate.LogRotate(container_=self._container)

@property
def _cos(self) -> relations.cos.COSRelation:
return self._cos_relation

@property
def _read_write_endpoint(self) -> str:
return f'file://{self._container.path("/run/mysqlrouter/mysql.sock")}'
Expand Down Expand Up @@ -107,7 +111,7 @@ def _on_force_upgrade_action(self, event: ops.ActionEvent) -> None:
logger.debug("Forcing upgrade")
event.log(f"Forcefully upgrading {self.unit.name}")
self._upgrade.upgrade_unit(
workload_=self.get_workload(event=None), tls=self._tls_certificate_saved
workload_=self.get_workload(event=None), tls=self.tls_certificate_saved
)
self.reconcile()
event.set_results({"result": f"Forcefully upgraded {self.unit.name}"})
Expand Down
25 changes: 16 additions & 9 deletions src/relations/cos.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@

import ops
from charms.grafana_agent.v0.cos_agent import COSAgentProvider
from relations.secrets import UNIT_SCOPE

import constants
import container
import relations.secrets
import utils
from snap import _SNAP_NAME

Expand All @@ -22,6 +21,7 @@
logger = logging.getLogger(__name__)

MONITORING_USERNAME = "monitoring"
MONITORING_PASSWORD_KEY = "monitoring-password"
shayancanonical marked this conversation as resolved.
Show resolved Hide resolved


@dataclass
Expand All @@ -39,6 +39,7 @@ class COSRelation:
_EXPORTER_PORT = "49152"
_HTTP_SERVER_PORT = "8443"
_NAME = "cos-agent"
_PEER_RELATION_NAME = "cos"

def __init__(self, charm_: "abstract_charm.MySQLRouterCharm", container_: container.Container):
self._interface = COSAgentProvider(
Expand All @@ -63,9 +64,15 @@ def __init__(self, charm_: "abstract_charm.MySQLRouterCharm", container_: contai
charm_.reconcile,
)

self._secrets = relations.secrets.RelationSecrets(
charm_,
self._PEER_RELATION_NAME,
unit_secret_fields=[MONITORING_PASSWORD_KEY],
)

@property
def exporter_user_info(self) -> dict:
"""Returns user info needed for the router exporter service."""
def exporter_user_config(self) -> dict:
"""Returns user config needed for the router exporter service."""
return ExporterConfig(
url=f"https://127.0.0.1:{self._HTTP_SERVER_PORT}",
username=MONITORING_USERNAME,
Expand All @@ -79,21 +86,21 @@ def relation_exists(self) -> bool:

def _get_monitoring_password(self) -> str:
"""Gets the monitoring password from unit peer data, or generate and cache it."""
shayancanonical marked this conversation as resolved.
Show resolved Hide resolved
monitoring_password = self.charm.cos_secrets.get_secret(
UNIT_SCOPE, constants.MONITORING_PASSWORD_KEY
monitoring_password = self._secrets.get_secret(
relations.secrets.UNIT_SCOPE, MONITORING_PASSWORD_KEY
)
if monitoring_password:
return monitoring_password

monitoring_password = utils.generate_password()
self.charm.cos_secrets.set_secret(
UNIT_SCOPE, constants.MONITORING_PASSWORD_KEY, monitoring_password
self._secrets.set_secret(
relations.secrets.UNIT_SCOPE, MONITORING_PASSWORD_KEY, monitoring_password
)
return monitoring_password

def _reset_monitoring_password(self) -> None:
"""Reset the monitoring password from unit peer data."""
self.charm.cos_secrets.set_secret(UNIT_SCOPE, constants.MONITORING_PASSWORD_KEY, None)
self._secrets.set_secret(relations.secrets.UNIT_SCOPE, MONITORING_PASSWORD_KEY, None)

def is_relation_breaking(self, event) -> bool:
"""Whether relation will be broken after the current event is handled."""
Expand Down
5 changes: 1 addition & 4 deletions src/snap.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,17 +179,14 @@ def update_mysql_router_service(self, *, enabled: bool, tls: bool = None) -> Non
raise NotImplementedError # TODO VM TLS

if enabled:
_snap.set({"mysqlrouter.extra-options": f"--extra-config {self.rest_api_config_file}"})
_snap.start([self._SERVICE_NAME], enable=True)
else:
_snap.unset("mysqlrouter.extra-options")
_snap.stop([self._SERVICE_NAME], disable=True)

def update_mysql_router_exporter_service(
self, *, enabled: bool, config: "relations.cos.ExporterConfig" = None
) -> None:
if enabled and not config:
raise ValueError("Missing MySQL Router exporter config")
super().update_mysql_router_exporter_service(enabled=enabled, config=config)

if enabled:
_snap.set(
Expand Down
Loading
Loading