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

Add workload version to juju status #18

Merged
merged 19 commits into from
Aug 21, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
1 change: 0 additions & 1 deletion metadata.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ description: |
A charm for the matrix synapse chat server.
Synapse is a drop in replacement for other chat servers like Mattermost and Slack.
This charm is useful if you want to spin up your own chat instance.
docs: ""
amandahla marked this conversation as resolved.
Show resolved Hide resolved
issues: https://github.com/canonical/synapse-operator/issues
maintainers:
- launchpad.net/~canonical-is-devops
Expand Down
6 changes: 6 additions & 0 deletions src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from ops.main import main

import actions
import synapse
from charm_state import CharmConfigInvalidError, CharmState
from constants import SYNAPSE_CONTAINER_NAME, SYNAPSE_PORT
from database_observer import DatabaseObserver
Expand Down Expand Up @@ -93,6 +94,11 @@ def _on_pebble_ready(self, event: ops.HookEvent) -> None:
Args:
event: Event triggering after pebble is ready.
"""
try:
synapse_version = synapse.get_version()
self.unit.set_workload_version(synapse_version)
except synapse.APIError as exc:
logger.debug("Cannot set workload version at this time: %s", exc)
self.change_config(event)

def _on_reset_instance_action(self, event: ActionEvent) -> None:
Expand Down
2 changes: 1 addition & 1 deletion src/synapse/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"""Synapse package is used to interact with Synapse instance."""

# Exporting methods to be used for another modules
from .api import APIError, register_user # noqa: F401
from .api import APIError, get_version, register_user # noqa: F401
from .workload import ( # noqa: F401
ExecResult,
WorkloadError,
Expand Down
34 changes: 34 additions & 0 deletions src/synapse/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import hashlib
import hmac
import logging
import re
import typing

import requests
Expand All @@ -20,6 +21,7 @@

SYNAPSE_URL = "http://localhost:8008"
REGISTER_URL = f"{SYNAPSE_URL}/_synapse/admin/v1/register"
VERSION_URL = f"{SYNAPSE_URL}/_synapse/admin/v1/server_version"


class APIError(Exception):
Expand Down Expand Up @@ -149,3 +151,35 @@ def _get_nonce() -> str:
) as exc:
logger.error("Failed to request %s : %s", REGISTER_URL, exc)
raise NetworkError(f"Failed to request {REGISTER_URL}.") from exc


def get_version() -> str:
"""Get version.

Expected API output:
{
"server_version": "0.99.2rc1 (b=develop, abcdef123)",
"python_version": "3.7.8"
}

Returns:
The version returned by Synapse API.

Raises:
NetworkError: if there was an error fetching the version.
"""
try:
res = requests.get(VERSION_URL, timeout=5)
res.raise_for_status()
server_version = res.json()["server_version"]
amandahla marked this conversation as resolved.
Show resolved Hide resolved
version_match = re.search(r"^([^\s(]+)", server_version)
if version_match:
return version_match.group(1)
return server_version
except (
requests.exceptions.ConnectionError,
requests.exceptions.Timeout,
requests.exceptions.HTTPError,
) as exc:
logger.error("Failed to request %s : %s", VERSION_URL, exc)
amandahla marked this conversation as resolved.
Show resolved Hide resolved
raise NetworkError(f"Failed to request {VERSION_URL}.") from exc
2 changes: 1 addition & 1 deletion src/synapse/workload.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def check_ready() -> typing.Dict:
check = Check(CHECK_READY_NAME)
check.override = "replace"
check.level = "ready"
check.tcp = {"port": SYNAPSE_PORT}
check.http = {"url": f"http://localhost:{SYNAPSE_PORT}/_synapse/admin/v1/server_version"}
amandahla marked this conversation as resolved.
Show resolved Hide resolved
# _CheckDict cannot be imported
return check.to_dict() # type: ignore
amandahla marked this conversation as resolved.
Show resolved Hide resolved

Expand Down
27 changes: 27 additions & 0 deletions tests/integration/test_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
# See LICENSE file for licensing details.

"""Integration tests for Synapse charm."""
import json
import logging
import re
import typing

import pytest
Expand Down Expand Up @@ -230,3 +232,28 @@ async def test_register_user_action(
)
assert response.status_code == 200
assert response.json()["access_token"]


async def test_workload_version(
ops_test: OpsTest,
synapse_app: Application,
get_unit_ips: typing.Callable[[str], typing.Awaitable[tuple[str, ...]]],
) -> None:
"""
arrange: a deployed Synapse charm.
act: get status from Juju.
assert: the workload version is set and match the one given by Synapse API request.
"""
_, status, _ = await ops_test.juju("status", "--format", "json")
status = json.loads(status)
juju_workload_version = status["applications"][synapse_app.name]["version"]
assert juju_workload_version
print(synapse_app.data)
for unit_ip in await get_unit_ips(synapse_app.get_statusname):
res = requests.get(
f"http://{unit_ip}:{SYNAPSE_PORT}/_synapse/admin/v1/server_version", timeout=5
)
server_version = res.json()["server_version"]
version_match = re.search(r"^([^\s(]+)", server_version)
assert version_match
assert version_match.group(1) == juju_workload_version
40 changes: 35 additions & 5 deletions tests/unit/test_synapse_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,9 @@ def test_generate_mac():
@mock.patch("synapse.api.requests")
def test_get_nonce_success(mock_requests):
"""
arrange: set User parameters.
act: register the user.
assert: parameters are passed correctly.
arrange: none.
amandahla marked this conversation as resolved.
Show resolved Hide resolved
act: get nonce.
assert: _get_nonce return the correct value.
"""
nonce_value = "nonce_value"
mock_response = mock.MagicMock()
Expand All @@ -102,12 +102,42 @@ def test_get_nonce_success(mock_requests):

def test_get_nonce_error(monkeypatch: pytest.MonkeyPatch):
"""
arrange: set User parameters.
act: register the user.
arrange: none.
amandahla marked this conversation as resolved.
Show resolved Hide resolved
act: get nonce.
assert: NetworkError is raised.
"""
mock_response_error = requests.exceptions.ConnectionError("Connection error")
mock_response = mock.Mock(side_effect=mock_response_error)
monkeypatch.setattr("synapse.api.requests.get", mock_response)
with pytest.raises(synapse.APIError, match="Failed to request"):
synapse.api._get_nonce()


@mock.patch("synapse.api.requests")
def test_get_version_success(mock_requests):
"""
arrange: none.
act: get version.
assert: get_version return the correct value.
"""
extracted_version = "0.99.2rc1"
mock_response = mock.MagicMock()
mock_response.json.return_value = {
"server_version": f"{extracted_version} (b=develop, abcdef123)",
"python_version": "3.7.8",
}
mock_requests.get.return_value = mock_response
assert synapse.api.get_version() == extracted_version


def test_get_version_error(monkeypatch: pytest.MonkeyPatch):
"""
arrange: none.
act: get version.
assert: NetworkError is raised.
"""
mock_response_error = requests.exceptions.ConnectionError("Connection error")
mock_response = mock.Mock(side_effect=mock_response_error)
monkeypatch.setattr("synapse.api.requests.get", mock_response)
with pytest.raises(synapse.APIError, match="Failed to request"):
synapse.api.get_version()