Skip to content

Commit

Permalink
Add Synapse Stats Exporter
Browse files Browse the repository at this point in the history
  • Loading branch information
amandahla committed Jan 23, 2024
1 parent 17a081e commit b13def7
Show file tree
Hide file tree
Showing 19 changed files with 302 additions and 216 deletions.
8 changes: 8 additions & 0 deletions src-docs/charm.py.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ Charm for Synapse on kubernetes.
## <kbd>class</kbd> `SynapseCharm`
Charm the service.

<<<<<<< HEAD
<a href="../src/charm.py#L47"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
=======
<a href="../src/charm.py#L45"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
>>>>>>> 86f7b4b (Add Synapse Stats Exporter)
### <kbd>function</kbd> `__init__`

Expand Down Expand Up @@ -87,7 +91,11 @@ Change configuration.

---

<<<<<<< HEAD
<a href="../src/charm.py#L261"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
=======
<a href="../src/charm.py#L193"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
>>>>>>> 86f7b4b (Add Synapse Stats Exporter)
### <kbd>function</kbd> `get_admin_access_token`

Expand Down
7 changes: 4 additions & 3 deletions src-docs/charm_state.py.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ Get charm proxy information from juju charm environment.

---

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

### <kbd>classmethod</kbd> `from_charm`

Expand Down Expand Up @@ -124,6 +124,7 @@ Represent Synapse builtin configuration values.

**Attributes:**

- <b>`admin_access_token`</b>: admin_access_token to configure Mjolnir and Stats Exporter.
- <b>`allow_public_rooms_over_federation`</b>: allow_public_rooms_over_federation config.
- <b>`enable_mjolnir`</b>: enable_mjolnir config.
- <b>`enable_password_config`</b>: enable_password_config config.
Expand All @@ -141,7 +142,7 @@ Represent Synapse builtin configuration values.

---

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

### <kbd>classmethod</kbd> `get_default_notif_from`

Expand All @@ -165,7 +166,7 @@ Set server_name as default value to notif_from.

---

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

### <kbd>classmethod</kbd> `to_yes_or_no`

Expand Down
6 changes: 3 additions & 3 deletions src-docs/mjolnir.py.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ A class representing the Mjolnir plugin for Synapse application.

Mjolnir is a moderation tool for Matrix to be used to protect your server from malicious invites, spam messages etc. See https://github.com/matrix-org/mjolnir/ for more details about it.

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

### <kbd>function</kbd> `__init__`

Expand Down Expand Up @@ -46,7 +46,7 @@ Shortcut for more simple access the model.

---

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

### <kbd>function</kbd> `enable_mjolnir`

Expand Down Expand Up @@ -76,7 +76,7 @@ The required steps to enable Mjolnir are:

---

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

### <kbd>function</kbd> `get_membership_room_id`

Expand Down
26 changes: 22 additions & 4 deletions src-docs/pebble.py.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Initialize the pebble service.

---

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

### <kbd>function</kbd> `change_config`

Expand All @@ -57,7 +57,7 @@ Change the configuration.

---

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

### <kbd>function</kbd> `enable_saml`

Expand All @@ -81,7 +81,7 @@ Enable SAML while receiving on_saml_data_available event.

---

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

### <kbd>function</kbd> `enable_smtp`

Expand Down Expand Up @@ -141,7 +141,25 @@ Replan Synapse NGINX service.

---

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

### <kbd>function</kbd> `replan_stats_exporter`

```python
replan_stats_exporter(container: Container) → None
```

Replan Synapse StatsExporter service.



**Args:**

- <b>`container`</b>: Charm container.

---

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

### <kbd>function</kbd> `reset_instance`

Expand Down
15 changes: 5 additions & 10 deletions src/actions/register_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,14 @@ def register_user(
User with password registered.
"""
try:
registration_shared_secret = synapse.get_registration_shared_secret(container=container)
if registration_shared_secret is None:
raise RegisterUserError(
"registration_shared_secret was not found, please check the logs"
)
user = User(username=username, admin=admin)
access_token = synapse.register_user(
registration_shared_secret=registration_shared_secret,
user=user,
user = synapse.create_user(
container=container,
username=username,
admin=admin,
admin_access_token=admin_access_token,
server=server,
)
user.access_token = access_token
assert user
return user
except (ValidationError, synapse.APIError) as exc:
raise RegisterUserError(str(exc)) from exc
162 changes: 90 additions & 72 deletions src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

import logging
import typing
from secrets import token_hex

import ops
from charms.nginx_ingress_integrator.v0.nginx_route import require_nginx_route
Expand Down Expand Up @@ -100,6 +99,7 @@ def __init__(self, *args: typing.Any) -> None:
self.on.promote_user_admin_action, self._on_promote_user_admin_action
)
self.framework.observe(self.on.anonymize_user_action, self._on_anonymize_user_action)
self.framework.observe(self.on.start, self._on_start)

def change_config(self) -> None:
"""Change configuration."""
Expand All @@ -109,7 +109,7 @@ def change_config(self) -> None:
return
self.model.unit.status = ops.MaintenanceStatus("Configuring Synapse")
try:
self.pebble_service.change_config(container)
self.pebble_service.change_config(container=container)
except PebbleServiceError as exc:
self.model.unit.status = ops.BlockedStatus(str(exc))
return
Expand Down Expand Up @@ -183,6 +183,94 @@ def _on_synapse_nginx_pebble_ready(self, _: ops.HookEvent) -> None:
self.pebble_service.replan_nginx(container)
self._set_unit_status()

def _get_peer_relation(self) -> typing.Optional[ops.Relation]:
"""Get peer relation.
Returns:
Relation or not if is not found.
"""
peer_relation = self.model.get_relation(PEER_RELATION_NAME)
return peer_relation

def get_admin_access_token(self) -> typing.Optional[str]:
"""Get admin access token.
Returns:
admin access token or None if fails.
"""
peer_relation = self._get_peer_relation()
if not peer_relation:
logger.error(
"Failed to get admin access token: no peer relation %s found", PEER_RELATION_NAME
)
return None
admin_access_token = None
if JUJU_HAS_SECRETS:
secret_id = peer_relation.data[self.app].get(SECRET_ID)
if secret_id:
try:
secret = self.model.get_secret(id=secret_id)
admin_access_token = secret.get_content().get(SECRET_KEY)
return admin_access_token
except ops.model.SecretNotFoundError as exc:
logger.exception("Failed to get secret id %s: %s", secret_id, str(exc))
del peer_relation.data[self.app][SECRET_ID]
return None
else:
# There is no Secrets support and none relation data was created
# So lets create the user and store its token in the peer relation
secret_value = peer_relation.data[self.app].get(SECRET_KEY)
if secret_value:
return secret_value
return None

def _start_synapse_stats_exporter(self) -> None:
"""Start Synapse Stats Exporter in Synapse container."""
container = self.unit.get_container(synapse.SYNAPSE_CONTAINER_NAME)
if not container.can_connect():
logger.error("Failed to connect to container while starting Synapse Stats Exporter")
return
self.pebble_service.replan_stats_exporter(container)

def _set_admin_access_token(self, access_token: str) -> None:
"""Set admin access token.
Args:
access_token: token to save in the secret.
"""
peer_relation = self._get_peer_relation()
if not peer_relation:
logger.error(
"Failed to get admin access token: no peer relation %s found", PEER_RELATION_NAME
)
return
if JUJU_HAS_SECRETS:
logger.debug("Adding secret")
secret = self.app.add_secret({SECRET_KEY: access_token})
peer_relation.data[self.app].update({SECRET_ID: secret.id})
else:
logger.debug("Adding peer data")
peer_relation.data[self.app].update({SECRET_KEY: access_token})

def _on_start(self, _: ops.HookEvent) -> None:
"""Handle start event."""
if self.get_admin_access_token():
self._start_synapse_stats_exporter()
return
# Since Synapse is ready, the charm can create the admin access token
container = self.unit.get_container(synapse.SYNAPSE_CONTAINER_NAME)
if not container.can_connect():
self.unit.status = ops.MaintenanceStatus("Waiting for Synapse pebble")
return
admin_user = synapse.create_admin_user(container)
if not admin_user:
logger.error("Failed to create admin user.")
return
self._set_admin_access_token(admin_user.access_token)
self._charm_state.synapse_config.admin_access_token = admin_user.access_token
# Stats exporter needs access token so the charm will start it here
self._start_synapse_stats_exporter()

def _on_reset_instance_action(self, event: ActionEvent) -> None:
"""Reset instance and report action result.
Expand Down Expand Up @@ -236,76 +324,6 @@ def _on_register_user_action(self, event: ActionEvent) -> None:
results = {"register-user": True, "user-password": user.password}
event.set_results(results)

def _create_admin_user(self) -> typing.Optional[User]:
"""Create admin user.
Returns:
Admin user with token to be used in Synapse API requests or None if fails.
"""
container = self.unit.get_container(synapse.SYNAPSE_CONTAINER_NAME)
if not container.can_connect():
logger.error("Failed to connect to the container")
return None
# The username is random because if the user exists, register_user will try to get the
# access_token.
# But to do that it needs an admin user and we don't have one yet.
# So, to be on the safe side, the user name is randomly generated and if for any reason
# there is no access token on peer data/secret, another user will be created.
#
# Using 16 to create a random value but to be secure against brute-force attacks,
# please check the docs:
# https://docs.python.org/3/library/secrets.html#how-many-bytes-should-tokens-use
username = token_hex(16)
return actions.register_user(container, username, True)

def get_admin_access_token(self) -> typing.Optional[str]:
"""Get admin access token.
Returns:
admin access token or None if fails.
"""
peer_relation = self.model.get_relation(PEER_RELATION_NAME)
if not peer_relation:
logger.error(
"Failed to get admin access token: no peer relation %s found", PEER_RELATION_NAME
)
return None
admin_access_token = None
if JUJU_HAS_SECRETS:
secret_id = peer_relation.data[self.app].get(SECRET_ID)
if secret_id:
try:
secret = self.model.get_secret(id=secret_id)
admin_access_token = secret.get_content().get(SECRET_KEY)
except ops.model.SecretNotFoundError as exc:
logger.exception("Failed to get secret id %s: %s", secret_id, str(exc))
del peer_relation.data[self.app][SECRET_ID]
return None
else:
# There is Secrets support but none was created
# So lets create the user and store its token in the secret
admin_user = self._create_admin_user()
if not admin_user:
return None
logger.debug("Adding secret")
secret = self.app.add_secret({SECRET_KEY: admin_user.access_token})
peer_relation.data[self.app].update({SECRET_ID: secret.id})
admin_access_token = admin_user.access_token
else:
# There is no Secrets support and none relation data was created
# So lets create the user and store its token in the peer relation
secret_value = peer_relation.data[self.app].get(SECRET_KEY)
if secret_value:
admin_access_token = secret_value
else:
admin_user = self._create_admin_user()
if not admin_user:
return None
logger.debug("Adding peer data")
peer_relation.data[self.app].update({SECRET_KEY: admin_user.access_token})
admin_access_token = admin_user.access_token
return admin_access_token

def _on_promote_user_admin_action(self, event: ActionEvent) -> None:
"""Promote user admin and report action result.
Expand Down
2 changes: 2 additions & 0 deletions src/charm_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class SynapseConfig(BaseModel): # pylint: disable=too-few-public-methods
"""Represent Synapse builtin configuration values.
Attributes:
admin_access_token: admin_access_token to configure Mjolnir and Stats Exporter.
allow_public_rooms_over_federation: allow_public_rooms_over_federation config.
enable_mjolnir: enable_mjolnir config.
enable_password_config: enable_password_config config.
Expand All @@ -70,6 +71,7 @@ class SynapseConfig(BaseModel): # pylint: disable=too-few-public-methods
trusted_key_servers: trusted_key_servers config.
"""

admin_access_token: str | None = Field(None)
allow_public_rooms_over_federation: bool = False
enable_mjolnir: bool = False
enable_password_config: bool = True
Expand Down
Loading

0 comments on commit b13def7

Please sign in to comment.