Skip to content

Commit

Permalink
ISD-2788 Add mas database integration (#612)
Browse files Browse the repository at this point in the history
  • Loading branch information
Thanhphan1147 authored Dec 7, 2024
1 parent a4c4395 commit 4a8d20a
Show file tree
Hide file tree
Showing 28 changed files with 356 additions and 281 deletions.
2 changes: 1 addition & 1 deletion .trivyignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,4 @@ CVE-2024-24788
CVE-2024-52804
# Fix ongoing:
# https://github.com/element-hq/synapse/pull/17985
CVE-2024-53981
CVE-2024-53981
4 changes: 4 additions & 0 deletions metadata.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ requires:
interface: postgresql_client
limit: 1
optional: true
mas-database:
interface: postgresql_client
limit: 1
optional: false
ingress:
interface: ingress
limit: 2
Expand Down
4 changes: 2 additions & 2 deletions src-docs/charm_state.py.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ State of the Charm.

<a href="../src/charm_state.py#L71"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

## <kbd>function</kbd> `inject_charm_state`
## <kbd>function</kbd> `validate_charm_state`

```python
inject_charm_state(
validate_charm_state(
method: Callable[[~C, ~E, ForwardRef('CharmState')], NoneType]
) → Callable[[~C, ~E], NoneType]
```
Expand Down
79 changes: 45 additions & 34 deletions src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,16 @@
import synapse
from admin_access_token import AdminAccessTokenService
from backup_observer import BackupObserver
from charm_state import CharmBaseWithState, CharmState, inject_charm_state
from database_observer import DatabaseObserver
from database_observer import DatabaseObserver, SynapseDatabaseObserver
from matrix_auth_observer import MatrixAuthObserver
from media_observer import MediaObserver
from mjolnir import Mjolnir
from observability import Observability
from redis_observer import RedisObserver
from smtp_observer import SMTPObserver
from state.charm_state import CharmState
from state.mas import MAS_DATABASE_INTEGRATION_NAME, MAS_DATABASE_NAME, MASConfiguration
from state.validation import CharmBaseWithState, validate_charm_state
from user import User

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -60,7 +62,12 @@ def __init__(self, *args: typing.Any) -> None:
self._backup = BackupObserver(self)
self._matrix_auth = MatrixAuthObserver(self)
self._media = MediaObserver(self)
self._database = DatabaseObserver(self, relation_name=synapse.SYNAPSE_DB_RELATION_NAME)
self._database = SynapseDatabaseObserver(
self, relation_name=synapse.SYNAPSE_DB_RELATION_NAME, database_name=self.app.name
)
self._mas_database = DatabaseObserver(
self, relation_name=MAS_DATABASE_INTEGRATION_NAME, database_name=MAS_DATABASE_NAME
)
self._smtp = SMTPObserver(self)
self._redis = RedisObserver(self)
self.token_service = AdminAccessTokenService(app=self.app, model=self.model)
Expand Down Expand Up @@ -276,13 +283,12 @@ def _set_workload_version(self) -> None:
except synapse.APIError as exc:
logger.debug("Cannot set workload version at this time: %s", exc)

@inject_charm_state
def _on_config_changed(self, _: ops.HookEvent, charm_state: CharmState) -> None:
"""Handle changed configuration.
@validate_charm_state
def _on_config_changed(self, _: ops.HookEvent) -> None:
"""Handle changed configuration."""
charm_state = self.build_charm_state()
MASConfiguration.validate(self)

Args:
charm_state: The charm state.
"""
logger.debug("Found %d peer unit(s).", self.peer_units_total())
if charm_state.redis_config is None and self.peer_units_total() > 1:
logger.debug("More than 1 peer unit found. Redis is required.")
Expand All @@ -292,14 +298,16 @@ def _on_config_changed(self, _: ops.HookEvent, charm_state: CharmState) -> None:
self.reconcile(charm_state)
self._set_workload_version()

@inject_charm_state
def _on_relation_departed(self, event: RelationDepartedEvent, charm_state: CharmState) -> None:
@validate_charm_state
def _on_relation_departed(self, event: RelationDepartedEvent) -> None:
"""Handle Synapse peer relation departed event.
Args:
event: relation departed event.
charm_state: The charm state.
"""
charm_state = self.build_charm_state()
MASConfiguration.validate(self)

if event.departing_unit == self.unit:
# there is no action for the departing unit
return
Expand All @@ -323,13 +331,12 @@ def peer_units_total(self) -> int:
"""
return self.app.planned_units()

@inject_charm_state
def _on_synapse_pebble_ready(self, _: ops.HookEvent, charm_state: CharmState) -> None:
"""Handle synapse pebble ready event.
@validate_charm_state
def _on_synapse_pebble_ready(self, _: ops.HookEvent) -> None:
"""Handle synapse pebble ready event."""
charm_state = self.build_charm_state()
MASConfiguration.validate(self)

Args:
charm_state: The charm state.
"""
logger.debug("Found %d peer unit(s).", self.peer_units_total())
if charm_state.redis_config is None and self.peer_units_total() > 1:
logger.debug("More than 1 peer unit found. Redis is required.")
Expand Down Expand Up @@ -431,8 +438,8 @@ def get_signing_key(self) -> typing.Optional[str]:
del peer_relation[0].data[self.app]["secret-signing-id"]
return None

@inject_charm_state
def _on_leader_elected(self, _: ops.HookEvent, charm_state: CharmState) -> None:
@validate_charm_state
def _on_leader_elected(self, _: ops.HookEvent) -> None:
"""Handle Synapse leader elected event.
This event handler will reconcile Synapse configuration after the following
Expand All @@ -441,10 +448,10 @@ def _on_leader_elected(self, _: ops.HookEvent, charm_state: CharmState) -> None:
- When the leader, for any reason, has changed so the leader unit will be the main.
Once the peer data (main_unit_id) is changed, other units will emit reconcile and be
properly configured.
Args:
charm_state: The charm state.
"""
charm_state = self.build_charm_state()
MASConfiguration.validate(self)

# assuming that this event will be fired only at the setup phase
# check if main is already set if not, this unit will be the main
if not self.unit.is_leader():
Expand All @@ -458,8 +465,8 @@ def _on_leader_elected(self, _: ops.HookEvent, charm_state: CharmState) -> None:
logger.debug("_on_leader_elected emitting reconcile")
self.reconcile(charm_state)

@inject_charm_state
def _on_relation_changed(self, _: ops.HookEvent, charm_state: CharmState) -> None:
@validate_charm_state
def _on_relation_changed(self, _: ops.HookEvent) -> None:
"""Handle Synapse peer relation changed event.
This event handler will reconcile Synapse configuration and NGINX after the following
Expand All @@ -469,10 +476,10 @@ def _on_relation_changed(self, _: ops.HookEvent, charm_state: CharmState) -> Non
must be restarted by design.
- Main unit has changed. The instance_map, stream_writers and NGINX configuration should be
updated and all remaining units restarted.
Args:
charm_state: The charm state.
"""
charm_state = self.build_charm_state()
MASConfiguration.validate(self)

logger.debug("_on_relation_changed emitting reconcile")
self.reconcile(charm_state)

Expand All @@ -496,14 +503,16 @@ def _on_register_user_action(self, event: ActionEvent) -> None:
results = {"register-user": True, "user-password": user.password}
event.set_results(results)

@inject_charm_state
def _on_promote_user_admin_action(self, event: ActionEvent, charm_state: CharmState) -> None:
@validate_charm_state
def _on_promote_user_admin_action(self, event: ActionEvent) -> None:
"""Promote user admin and report action result.
Args:
event: Event triggering the promote user admin action.
charm_state: The charm state.
"""
charm_state = self.build_charm_state()
MASConfiguration.validate(self)

results = {
"promote-user-admin": False,
}
Expand All @@ -528,14 +537,16 @@ def _on_promote_user_admin_action(self, event: ActionEvent, charm_state: CharmSt
return
event.set_results(results)

@inject_charm_state
def _on_anonymize_user_action(self, event: ActionEvent, charm_state: CharmState) -> None:
@validate_charm_state
def _on_anonymize_user_action(self, event: ActionEvent) -> None:
"""Anonymize user and report action result.
Args:
event: Event triggering the anonymize user action.
charm_state: The charm state.
"""
charm_state = self.build_charm_state()
MASConfiguration.validate(self)

results = {
"anonymize-user": False,
}
Expand Down
73 changes: 40 additions & 33 deletions src/database_observer.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,32 @@
)
from ops.framework import Object

import synapse
from charm_state import CharmBaseWithState, CharmState, inject_charm_state
from charm_types import DatasourcePostgreSQL
from database_client import DatabaseClient
from state.mas import MASConfiguration
from state.validation import CharmBaseWithState, validate_charm_state

logger = logging.getLogger(__name__)


class DatabaseObserver(Object):
"""The Database relation observer."""

def __init__(self, charm: CharmBaseWithState, relation_name: str) -> None:
def __init__(self, charm: CharmBaseWithState, relation_name: str, database_name: str) -> None:
"""Initialize the observer and register event handlers.
Args:
charm: The parent charm to attach the observer to.
relation_name: The name of the relation to observe.
database_name: The database name.
"""
super().__init__(charm, f"{relation_name}-observer")
self._charm = charm
# SUPERUSER is required to update pg_database
self.database = DatabaseRequires(
self._charm,
relation_name=relation_name,
database_name=self._charm.app.name,
database_name=database_name,
extra_user_roles="SUPERUSER",
)
self.framework.observe(self.database.on.database_created, self._on_database_created)
Expand All @@ -54,35 +55,21 @@ def get_charm(self) -> CharmBaseWithState:
"""
return self._charm

@inject_charm_state
def _on_database_created(self, _: DatabaseCreatedEvent, charm_state: CharmState) -> None:
"""Handle database created.
Args:
charm_state: The charm state.
"""
self.model.unit.status = ops.MaintenanceStatus("Preparing the database")
# In case of psycopg2.Error, Juju will set ErrorStatus
# See discussion here:
# https://github.com/canonical/synapse-operator/pull/13#discussion_r1253285244
datasource = self.get_relation_as_datasource()
db_client = DatabaseClient(datasource=datasource)
if self.database.relation_name == synapse.SYNAPSE_DB_RELATION_NAME:
db_client.prepare()
logger.debug("_on_database_created emitting reconcile")
self.get_charm().reconcile(charm_state)

@inject_charm_state
def _on_endpoints_changed(
self, _: DatabaseEndpointsChangedEvent, charm_state: CharmState
) -> None:
"""Handle endpoints change.
Args:
charm_state: The charm state.
"""
logger.debug("_on_endpoints_changed emitting reconcile")
self.get_charm().reconcile(charm_state)
@validate_charm_state
def _on_database_created(self, _: DatabaseCreatedEvent) -> None:
"""Handle database created."""
charm = self.get_charm()
charm_state = charm.build_charm_state()
MASConfiguration.validate(charm)
charm.reconcile(charm_state)

@validate_charm_state
def _on_endpoints_changed(self, _: DatabaseEndpointsChangedEvent) -> None:
"""Handle endpoints change."""
charm = self.get_charm()
charm_state = charm.build_charm_state()
MASConfiguration.validate(charm)
charm.reconcile(charm_state)

def get_relation_as_datasource(self) -> typing.Optional[DatasourcePostgreSQL]:
"""Get database data from relation.
Expand All @@ -107,3 +94,23 @@ def get_relation_as_datasource(self) -> typing.Optional[DatasourcePostgreSQL]:
port=endpoint.split(":")[1],
db=self._charm.app.name,
)


class SynapseDatabaseObserver(DatabaseObserver):
"""The database relation observer."""

@validate_charm_state
def _on_database_created(self, _: DatabaseCreatedEvent) -> None:
"""Handle database created events."""
charm = self.get_charm()
charm_state = charm.build_charm_state()
MASConfiguration.validate(charm)
charm.reconcile(charm_state)
self.model.unit.status = ops.MaintenanceStatus("Preparing the database")
# In case of psycopg2.Error, Juju will set ErrorStatus
# See discussion here:
# https://github.com/canonical/synapse-operator/pull/13#discussion_r1253285244
datasource = self.get_relation_as_datasource()
db_client = DatabaseClient(datasource=datasource)
db_client.prepare()
charm.reconcile(charm_state)
30 changes: 15 additions & 15 deletions src/matrix_auth_observer.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
from ops.framework import Object

import synapse
from charm_state import CharmBaseWithState, CharmState, inject_charm_state
from state.charm_state import CharmState
from state.mas import MASConfiguration
from state.validation import CharmBaseWithState, validate_charm_state

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -142,22 +144,20 @@ def _matrix_auth_relation_updated(
return False
return True

@inject_charm_state
def _on_matrix_auth_relation_changed(self, _: ops.EventBase, charm_state: CharmState) -> None:
"""Handle matrix-auth request received event.
Args:
charm_state: The charm state.
"""
@validate_charm_state
def _on_matrix_auth_relation_changed(self, _: ops.EventBase) -> None:
"""Handle matrix-auth request received event."""
charm = self.get_charm()
charm_state = charm.build_charm_state()
MASConfiguration.validate(charm)
logger.debug("_on_matrix_auth_relation_changed emitting reconcile")
self._charm.reconcile(charm_state)

@inject_charm_state
def _on_matrix_auth_relation_departed(self, _: ops.EventBase, charm_state: CharmState) -> None:
"""Handle matrix-auth relation departed event.
Args:
charm_state: The charm state.
"""
@validate_charm_state
def _on_matrix_auth_relation_departed(self, _: ops.EventBase) -> None:
"""Handle matrix-auth relation departed event."""
charm = self.get_charm()
charm_state = charm.build_charm_state()
MASConfiguration.validate(charm)
logger.debug("_on_matrix_auth_relation_departed emitting reconcile")
self._charm.reconcile(charm_state)
Loading

0 comments on commit 4a8d20a

Please sign in to comment.