Skip to content

Commit

Permalink
ci: add lxd provider for integration tests
Browse files Browse the repository at this point in the history
  • Loading branch information
skatsaounis committed Mar 29, 2024
1 parent 120cb31 commit 0644053
Show file tree
Hide file tree
Showing 14 changed files with 174 additions and 88 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/pull-request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ jobs:
secrets: inherit
with:
charm-path: maas-region
provider: lxd

pull-request-agent:
name: PR Agent
uses: canonical/observability/.github/workflows/charm-pull-request.yaml@main
secrets: inherit
with:
charm-path: maas-agent
provider: lxd
36 changes: 18 additions & 18 deletions maas-agent/lib/charms/maas_region/v0/maas.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import dataclasses
import json
import logging
from typing import Any, Dict, MutableMapping
from typing import Any, Dict, List, MutableMapping, Union

import ops
from ops.charm import CharmEvents
Expand All @@ -21,7 +21,7 @@

# Increment this PATCH version before using `charmcraft publish-lib` or reset
# to 0 if you are raising the major API version
LIBPATCH = 1
LIBPATCH = 2

DEFAULT_ENDPOINT_NAME = "maas-region"
BUILTIN_JUJU_KEYS = {"ingress-address", "private-address", "egress-subnets"}
Expand All @@ -44,10 +44,10 @@ def load(cls, data: Dict[str, str]) -> Self:
init_vals = {}
for f in dataclasses.fields(cls):
val = data.get(f.name)
init_vals[f.name] = val if f.type == str else json.loads(val)
init_vals[f.name] = val if f.type == str else json.loads(val) # type: ignore
return cls(**init_vals)

def dump(self, databag: MutableMapping[str, str] | None = None) -> None:
def dump(self, databag: Union[MutableMapping[str, str], None] = None) -> None:
"""Write the contents of this model to Juju databag."""
if databag is None:
databag = {}
Expand All @@ -71,7 +71,7 @@ class MaasProviderAppData(MaasDatabag):
"""The schema for the Provider side of this relation."""

api_url: str
regions: list[str]
regions: List[str]
maas_secret_id: str

def get_secret(self, model: ops.Model) -> str:
Expand Down Expand Up @@ -130,12 +130,12 @@ class MaasRegionRequirerEvents(CharmEvents):
class MaasRegionRequirer(Object):
"""Requires-side of the MAAS relation."""

on = MaasRegionRequirerEvents()
on = MaasRegionRequirerEvents() # type: ignore

def __init__(
self,
charm: ops.CharmBase,
key: str | None = None,
key: Union[str, None] = None,
endpoint: str = DEFAULT_ENDPOINT_NAME,
):
super().__init__(charm, key or endpoint)
Expand All @@ -156,7 +156,7 @@ def __init__(
)

@property
def _relation(self) -> ops.Relation | None:
def _relation(self) -> Union[ops.Relation, None]:
# filter out common unhappy relation states
relation = self.model.get_relation(self._endpoint)
return relation if relation and relation.app and relation.data else None
Expand All @@ -176,16 +176,16 @@ def _on_relation_created(self, event: ops.RelationCreatedEvent) -> None:
def _on_relation_broken(self, _event: ops.RelationBrokenEvent) -> None:
self.on.removed.emit()

def get_enroll_data(self) -> MaasProviderAppData | None:
def get_enroll_data(self) -> Union[MaasProviderAppData, None]:
"""Get enrollment data from databag."""
relation = self._relation
if relation:
assert relation.app is not None
try:
databag = relation.data[relation.app]
return MaasProviderAppData.load(databag)
return MaasProviderAppData.load(databag) # type: ignore
except TypeError:
log.debug(f"invalid databag contents: {databag}")
log.debug(f"invalid databag contents: {databag}") # type: ignore
return None

def is_published(self) -> bool:
Expand All @@ -196,7 +196,7 @@ def is_published(self) -> bool:

unit_data = relation.data[self._charm.unit]
try:
MaasRequirerUnitData.load(unit_data)
MaasRequirerUnitData.load(unit_data) # type: ignore
return True
except TypeError:
return False
Expand All @@ -218,18 +218,18 @@ class MaasRegionProvider(Object):
def __init__(
self,
charm: ops.CharmBase,
key: str | None = None,
key: Union[str, None] = None,
endpoint: str = DEFAULT_ENDPOINT_NAME,
):
super().__init__(charm, key or endpoint)
self._charm = charm
self._endpoint = endpoint

@property
def _relations(self) -> list[ops.Relation]:
def _relations(self) -> List[ops.Relation]:
return self.model.relations[self._endpoint]

def _update_secret(self, relation: ops.Relation, content: dict[str, str]) -> str:
def _update_secret(self, relation: ops.Relation, content: Dict[str, str]) -> str:
label = f"enroll-{relation.name}-{relation.id}.secret"
try:
secret = self.model.get_secret(label=label)
Expand All @@ -242,7 +242,7 @@ def _update_secret(self, relation: ops.Relation, content: dict[str, str]) -> str
secret.grant(relation)
return secret.get_info().id

def publish_enroll_token(self, maas_api: str, regions: list[str], maas_secret: str) -> None:
def publish_enroll_token(self, maas_api: str, regions: List[str], maas_secret: str) -> None:
"""Publish enrollment data.
Args:
Expand All @@ -260,7 +260,7 @@ def publish_enroll_token(self, maas_api: str, regions: list[str], maas_secret: s
)
local_app_databag.dump(relation.data[self.model.app])

def gather_rack_units(self) -> dict[str, ops.model.Unit]:
def gather_rack_units(self) -> Dict[str, ops.model.Unit]:
"""Get a map of Rack units.
Returns:
Expand All @@ -272,7 +272,7 @@ def gather_rack_units(self) -> dict[str, ops.model.Unit]:
continue
for worker_unit in relation.units:
try:
worker_data = MaasRequirerUnitData.load(relation.data[worker_unit])
worker_data = MaasRequirerUnitData.load(relation.data[worker_unit]) # type: ignore
url = worker_data.url
except TypeError as e:
log.debug(f"invalid databag contents: {e}")
Expand Down
5 changes: 3 additions & 2 deletions maas-agent/src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import logging
import socket
from typing import Union

import ops
from charms.grafana_agent.v0 import cos_agent
Expand Down Expand Up @@ -61,7 +62,7 @@ def __init__(self, *args):
# self.framework.observe(self.maas_region.on.removed, self._on_maas_removed)

@property
def version(self) -> str | None:
def version(self) -> Union[str, None]:
"""Reports the current workload version.
Returns:
Expand All @@ -70,7 +71,7 @@ def version(self) -> str | None:
return MaasHelper.get_installed_version()

@property
def maas_id(self) -> str | None:
def maas_id(self) -> Union[str, None]:
"""Reports the MAAS ID.
Returns:
Expand Down
17 changes: 9 additions & 8 deletions maas-agent/src/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import subprocess
from pathlib import Path
from typing import Union

from charms.operator_libs_linux.v2.snap import SnapCache, SnapState

Expand Down Expand Up @@ -38,31 +39,31 @@ def uninstall() -> None:
maas.ensure(SnapState.Absent)

@staticmethod
def get_installed_version() -> str | None:
def get_installed_version() -> Union[str, None]:
"""Get installed version.
Returns:
str | None: version if installed
Union[str, None]: version if installed
"""
maas = SnapCache()[MAAS_SNAP_NAME]
return maas.revision if maas.present else None

@staticmethod
def get_installed_channel() -> str | None:
def get_installed_channel() -> Union[str, None]:
"""Get installed channel.
Returns:
str | None: channel if installed
Union[str, None]: channel if installed
"""
maas = SnapCache()[MAAS_SNAP_NAME]
return maas.channel if maas.present else None

@staticmethod
def get_maas_id() -> str | None:
def get_maas_id() -> Union[str, None]:
"""Get MAAS system ID.
Returns:
str | None: system_id, or None if not present
Union[str, None]: system_id, or None if not present
"""
try:
with MAAS_ID.open() as file:
Expand All @@ -71,11 +72,11 @@ def get_maas_id() -> str | None:
return None

@staticmethod
def get_maas_mode() -> str | None:
def get_maas_mode() -> Union[str, None]:
"""Get MAAS operation mode.
Returns:
str | None: mode, or None if not initialised
Union[str, None]: mode, or None if not initialised
"""
try:
with MAAS_MODE.open() as file:
Expand Down
42 changes: 40 additions & 2 deletions maas-agent/tests/integration/test_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

logger = logging.getLogger(__name__)

METADATA = yaml.safe_load(Path("./metadata.yaml").read_text())
METADATA = yaml.safe_load(Path("./charmcraft.yaml").read_text())
APP_NAME = METADATA["name"]


Expand All @@ -25,9 +25,47 @@ async def test_build_and_deploy(ops_test: OpsTest):
# Build and deploy charm from local source folder
charm = await ops_test.build_charm(".")

# Deploy the charm and wait for active/idle status
# Deploy the charm and wait for waiting/idle status
await asyncio.gather(
ops_test.model.deploy(charm, application_name=APP_NAME),
ops_test.model.wait_for_idle(
apps=[APP_NAME], status="waiting", raise_on_blocked=True, timeout=1000
),
)


@pytest.mark.abort_on_fail
async def test_region_integration(ops_test: OpsTest):
"""Verify that the charm integrates with the database.
Assert that the charm is active if the integration is established.
"""
# Build and deploy region charm from local source folder
region_charm = await ops_test.build_charm("../maas-region/")

# Deploy the region charm and wait for active/idle status
await asyncio.gather(
ops_test.model.deploy(region_charm, application_name="maas-region"),
ops_test.model.wait_for_idle(
apps=["maas-region"], status="waiting", raise_on_blocked=True, timeout=1000
),
)

await asyncio.gather(
ops_test.model.deploy(
"postgresql",
application_name="postgresql",
channel="14/stable",
trust=True,
),
ops_test.model.wait_for_idle(
apps=["postgresql"], status="active", raise_on_blocked=True, timeout=1000
),
)

await asyncio.gather(
ops_test.model.integrate("maas-region", "postgresql"),
ops_test.model.integrate(f"{APP_NAME}", "maas-region"),
ops_test.model.wait_for_idle(
apps=[APP_NAME], status="active", raise_on_blocked=True, timeout=1000
),
Expand Down
3 changes: 2 additions & 1 deletion maas-agent/tests/unit/test_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import socket
import unittest
from typing import List
from unittest.mock import patch

import ops
Expand Down Expand Up @@ -54,7 +55,7 @@ def setUp(self):
self.harness.begin()
self.addCleanup(self.harness.cleanup)

def _enroll(self, rel_id: int, regions: list[str]):
def _enroll(self, rel_id: int, regions: List[str]):
secret_id = self.harness.add_model_secret(
self.remote_app, {"maas-secret": self.maas_secret}
)
Expand Down
1 change: 0 additions & 1 deletion maas-agent/tests/unit/test_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ def test_set_not_running(self, mock_snap):


class TestHelperFiles(unittest.TestCase):

@patch("pathlib.Path.open", new_callable=lambda: mock_open(read_data="maas-id\n"))
def test_get_maas_id(self, _):
self.assertEqual(MaasHelper.get_maas_id(), "maas-id")
Expand Down
22 changes: 15 additions & 7 deletions maas-agent/tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[tox]
no_package = True
skip_missing_interpreters = True
env_list = format, lint, static, unit
env_list = format, lint, static-{charm,lib}, unit, scenario
min_version = 4.0.0

[vars]
Expand All @@ -30,7 +30,7 @@ deps =
ruff
commands =
black {[vars]all_path}
ruff --fix {[vars]all_path}
ruff check --fix {[vars]all_path}

[testenv:lint]
description = Check code against coding style standards
Expand All @@ -43,7 +43,7 @@ commands =
# and uncomment the following line
# codespell {[vars]lib_path}
codespell {tox_root}
ruff {[vars]all_path}
ruff check {[vars]all_path}
black --check --diff {[vars]all_path}

[testenv:unit]
Expand All @@ -62,13 +62,21 @@ commands =
{[vars]tests_path}/unit
coverage report

[testenv:static]
description = Run static type checks
[testenv:static-{charm,lib}]
description = Run static analysis checks
deps =
pyright
-r {tox_root}/requirements.txt
typing-extensions
charm: -r{toxinidir}/requirements.txt
lib: ops
lib: jinja2
unit: {[testenv:unit]deps}
integration: {[testenv:integration]deps}
commands =
pyright {posargs}
charm: pyright --pythonversion 3.8 {[vars]src_path} {posargs}
lib: pyright --pythonversion 3.8 {[vars]lib_path} {posargs}
lib: /usr/bin/env sh -c 'for m in $(git diff main --name-only --line-prefix=`git rev-parse --show-toplevel`/ {[vars]lib_path}); do if ! git diff main $m | grep -q "+LIBPATCH\|+LIBAPI"; then echo "You forgot to bump the version on $m!"; exit 1; fi; done'
allowlist_externals = /usr/bin/env

[testenv:integration]
description = Run integration tests
Expand Down
Loading

0 comments on commit 0644053

Please sign in to comment.