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 8 commits
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
55 changes: 38 additions & 17 deletions src/synapse/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,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"
SYNAPSE_VERSION_REGEX = r"(\d+\.\d+\.\d+(?:\w+)?)\s?"


class APIError(Exception):
Expand All @@ -35,22 +36,30 @@ class APIError(Exception):
"""

def __init__(self, msg: str):
"""Initialize a new instance of the RegisterUserError exception.
"""Initialize a new instance of the APIError exception.

Args:
msg (str): Explanation of the error.
"""
self.msg = msg


class RegisterUserError(APIError):
"""Exception raised when registering user via API fails."""


class NetworkError(APIError):
"""Exception raised when requesting API fails due network issues."""


class GetVersionError(APIError):
"""Exception raised when getting version via API fails."""


class VersionNotFoundError(GetVersionError):
"""Exception raised when version is not found."""


class VersionUnexpectedContentError(GetVersionError):
"""Exception raised when output of getting version is unexpected."""


def register_user(registration_shared_secret: str, user: User) -> None:
"""Register user.

Expand Down Expand Up @@ -172,6 +181,7 @@ def get_version() -> str:

Raises:
NetworkError: if there was an error fetching the version.
GetVersionError: if there was an error while reading version.
"""
try:
session = Session()
Expand All @@ -182,15 +192,26 @@ def get_version() -> str:
session.mount("http://", HTTPAdapter(max_retries=retries))
res = session.get(VERSION_URL, timeout=10)
res.raise_for_status()
server_version = res.json()["server_version"]
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.exception("Failed to request %s : %r", VERSION_URL, exc)
raise NetworkError(f"Failed to request {VERSION_URL}.") from exc
res_json = res.json()
server_version = res_json.get("server_version", None)
amandahla marked this conversation as resolved.
Show resolved Hide resolved
if server_version is None:
# Exception not in docstring because is captured.
amandahla marked this conversation as resolved.
Show resolved Hide resolved
raise VersionNotFoundError( # noqa: DCO053
f"There is no server_version in JSON output: {res_json}"
)
version_match = re.search(SYNAPSE_VERSION_REGEX, server_version)
if not version_match:
# Exception not in docstring because is captured.
amandahla marked this conversation as resolved.
Show resolved Hide resolved
raise VersionUnexpectedContentError( # noqa: DCO053
f"server_version has unexpected content: {server_version}"
)
return version_match.group(1)
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as exc:
logger.exception("Failed to connect to %s: %r", VERSION_URL, exc)
raise NetworkError(f"Failed to connect to {VERSION_URL}.") from exc
except requests.exceptions.HTTPError as exc:
logger.exception("HTTP error from %s: %r", VERSION_URL, exc)
raise NetworkError(f"HTTP error from {VERSION_URL}.") from exc
except GetVersionError as exc:
logger.exception("Failed to get version: %r", exc)
raise GetVersionError(str(exc)) from exc
4 changes: 3 additions & 1 deletion src/synapse/workload.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
SYNAPSE_PORT,
)

from .api import VERSION_URL

logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -77,7 +79,7 @@ def check_ready() -> typing.Dict:
check = Check(CHECK_READY_NAME)
check.override = "replace"
check.level = "ready"
check.http = {"url": f"http://localhost:{SYNAPSE_PORT}/_synapse/admin/v1/server_version"}
check.http = {"url": VERSION_URL}
# _CheckDict cannot be imported
return check.to_dict() # type: ignore
amandahla marked this conversation as resolved.
Show resolved Hide resolved

Expand Down
3 changes: 2 additions & 1 deletion tests/integration/test_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from pytest_operator.plugin import OpsTest

from constants import SYNAPSE_PORT
from synapse.api import SYNAPSE_VERSION_REGEX

# caused by pytest fixtures
# pylint: disable=too-many-arguments
Expand Down Expand Up @@ -253,6 +254,6 @@ async def test_workload_version(
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)
version_match = re.search(SYNAPSE_VERSION_REGEX, server_version)
assert version_match
assert version_match.group(1) == juju_workload_version
8 changes: 4 additions & 4 deletions tests/unit/test_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

def test_synapse_pebble_layer(harness_server_name_configured: Harness) -> None:
"""
arrange: none
arrange: charm deployed.
act: start the Synapse charm, set Synapse container to be ready and set server_name.
assert: Synapse charm should submit the correct Synapse pebble layer to pebble.
"""
Expand Down Expand Up @@ -53,7 +53,7 @@ def test_synapse_pebble_layer(harness_server_name_configured: Harness) -> None:
)
def test_synapse_migrate_config_error(harness_server_name_configured: Harness) -> None:
"""
arrange: none
arrange: charm deployed.
act: start the Synapse charm, set Synapse container to be ready and set server_name.
assert: Synapse charm should be blocked by error on migrate_config command.
"""
Expand All @@ -64,7 +64,7 @@ def test_synapse_migrate_config_error(harness_server_name_configured: Harness) -

def test_container_down(harness_server_name_configured: Harness) -> None:
"""
arrange: none
arrange: charm deployed.
act: start the Synapse charm, set server_name, set Synapse container to be down
and then try to change report_stats.
assert: Synapse charm should submit the correct status.
Expand All @@ -78,7 +78,7 @@ def test_container_down(harness_server_name_configured: Harness) -> None:

def test_server_name_empty(harness: Harness) -> None:
"""
arrange: none
arrange: charm deployed.
act: start the Synapse charm and set Synapse container to be ready.
assert: Synapse charm waits for server_name to be set.
"""
Expand Down
27 changes: 21 additions & 6 deletions tests/unit/test_synapse_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def test_generate_mac():
@mock.patch("synapse.api.requests")
def test_get_nonce_success(mock_requests):
"""
arrange: none.
arrange: mock request to get nonce returning value.
act: get nonce.
assert: _get_nonce return the correct value.
"""
Expand All @@ -102,7 +102,7 @@ def test_get_nonce_success(mock_requests):

def test_get_nonce_error(monkeypatch: pytest.MonkeyPatch):
"""
arrange: none.
arrange: mock request to get nonce returning error.
act: get nonce.
assert: NetworkError is raised.
"""
Expand All @@ -116,7 +116,7 @@ def test_get_nonce_error(monkeypatch: pytest.MonkeyPatch):
@mock.patch("synapse.api.Session")
def test_get_version_success(mock_session):
amandahla marked this conversation as resolved.
Show resolved Hide resolved
"""
arrange: none.
arrange: mock request to get version returning value.
act: get version.
assert: get_version return the correct value.
"""
Expand All @@ -131,9 +131,9 @@ def test_get_version_success(mock_session):


@mock.patch("synapse.api.Session")
def test_get_version_error(mock_session):
def test_get_version_connection_error(mock_session):
"""
arrange: none.
arrange: mock request to get version returning error.
act: get version.
assert: NetworkError is raised.
"""
Expand All @@ -142,5 +142,20 @@ def test_get_version_error(mock_session):
mock_response_error = requests.exceptions.ConnectionError("Connection error")
mock_response.json.side_effect = mock_response_error
mock_session_instance.get.return_value = mock_response
with pytest.raises(synapse.APIError, match="Failed to request"):
with pytest.raises(synapse.APIError, match="Failed to connect to"):
synapse.api.get_version()


@mock.patch("synapse.api.Session")
def test_get_version_regex_error(mock_session):
"""
arrange: mock request to get version returning invalid content.
act: get version.
assert: get_version return the correct value.
"""
mock_session_instance = mock_session.return_value
mock_response = mock.Mock()
mock_response.json.return_value = {"server_version": "foo"}
mock_session_instance.get.return_value = mock_response
with pytest.raises(synapse.APIError, match="server_version has unexpected content"):
synapse.api.get_version()