Skip to content

Commit

Permalink
Use AnyCharm for loki integration test (#24)
Browse files Browse the repository at this point in the history
* test commit

* fix integration linting issues

* first draft

* add kubernetes fixture

* integrate with loki

* remove old code

* change print statement

* Changes post testing

* Fix linting issues

* fix mypy issues

* add assert model

* Convert to anycharm

* Change relation name

* Changes after converting to anycharm

* Minor lint issue

* line too long

* line too long

* Added fixtures

* Lint issue

* Lint issue

* Lint issue

* Lint issue

* Removed postgresql fixture

* Address review comments
  • Loading branch information
swetha1654 authored Nov 30, 2024
1 parent 8928b47 commit c53cc70
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 56 deletions.
9 changes: 1 addition & 8 deletions lib/charms/loki_k8s/v0/charm_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,7 @@ def my_server_cert(self) -> Optional[str]:
import os
from contextlib import contextmanager
from pathlib import Path
from typing import (
Callable,
Optional,
Sequence,
Type,
TypeVar,
Union,
)
from typing import Callable, Optional, Sequence, Type, TypeVar, Union

from cosl import JujuTopology
from cosl.loki_logger import LokiHandler # pyright:ignore[reportMissingImports]
Expand Down
47 changes: 46 additions & 1 deletion tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@
"""Fixtures for maubot integration tests."""

import json
from typing import Any, Callable, Coroutine
from pathlib import Path
from typing import Any, AsyncGenerator, Callable, Coroutine

import pytest
import pytest_asyncio
from juju.application import Application
from juju.model import Model
from juju.unit import Unit
from pytest_operator.plugin import OpsTest


Expand Down Expand Up @@ -34,3 +39,43 @@ async def get_unit_ips(application_name: str) -> tuple[Any, ...]:
)

return get_unit_ips


@pytest.fixture(scope="module", name="model")
def model_fixture(ops_test: OpsTest) -> Model:
"""The testing model."""
assert ops_test.model
return ops_test.model


@pytest_asyncio.fixture(scope="module", name="charm")
async def charm_fixture(pytestconfig: pytest.Config, ops_test: OpsTest) -> str | Path:
"""The path to charm."""
charm = pytestconfig.getoption("--charm-file")
if not charm:
charm = await ops_test.build_charm(".")
assert charm, "Charm not built"
return charm
return charm


@pytest_asyncio.fixture(scope="module", name="application")
async def maubot_application_fixture(
model: Model,
charm: str | Path,
pytestconfig: pytest.Config,
) -> AsyncGenerator[Application, None]:
"""Deploy the maubot charm."""
maubot_image = pytestconfig.getoption("--maubot-image")
assert maubot_image
maubot = await model.deploy(f"./{charm}", resources={"maubot-image": maubot_image})

await model.wait_for_idle(timeout=600, status="blocked")

yield maubot


@pytest.fixture(scope="module", name="unit")
def unit_fixture(application: Application) -> Unit:
"""The maubot charm application unit."""
return application.units[0]
123 changes: 76 additions & 47 deletions tests/integration/test_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,52 +15,45 @@

import pytest
import requests
from juju.application import Application
from juju.model import Model
from juju.unit import Unit
from pytest_operator.plugin import OpsTest

logger = logging.getLogger(__name__)


@pytest.mark.abort_on_fail
async def test_build_and_deploy(
ops_test: OpsTest,
pytestconfig: pytest.Config,
model: Model,
application: Application,
):
"""
arrange: set up the test Juju model.
act: build and deploy the Maubot charm, check if is blocked and deploy postgresql.
arrange: deploy Maubot and postgresql and integrate them.
act: send a request to retrieve metadata about maubot.
assert: the Maubot charm becomes active once is integrated with postgresql.
Charm is still active after integrating it with Nginx and the request
is successful.
"""
charm = pytestconfig.getoption("--charm-file")
maubot_image = pytestconfig.getoption("--maubot-image")
assert maubot_image
if not charm:
charm = await ops_test.build_charm(".")
assert ops_test.model
maubot = await ops_test.model.deploy(f"./{charm}", resources={"maubot-image": maubot_image})

await ops_test.model.wait_for_idle(timeout=600, status="blocked")

postgresql_k8s = await ops_test.model.deploy("postgresql-k8s", channel="14/stable", trust=True)
await ops_test.model.wait_for_idle(timeout=900)
await ops_test.model.add_relation(maubot.name, postgresql_k8s.name)
await ops_test.model.wait_for_idle(timeout=900, status="active")

nginx_ingress_integrator = await ops_test.model.deploy(
postgresql_k8s = await model.deploy("postgresql-k8s", channel="14/stable", trust=True)
await model.wait_for_idle(timeout=900)
await model.add_relation(application.name, postgresql_k8s.name)
await model.wait_for_idle(timeout=900, status="active")

nginx_ingress_integrator = await model.deploy(
"nginx-ingress-integrator",
channel="edge",
config={
"path-routes": "/",
"service-hostname": "maubot.local",
"service-namespace": ops_test.model.name,
"service-namespace": model.name,
"service-name": "maubot",
},
trust=True,
)
await ops_test.model.add_relation(maubot.name, nginx_ingress_integrator.name)
await model.add_relation(application.name, nginx_ingress_integrator.name)

await ops_test.model.wait_for_idle(timeout=600, status="active")
await model.wait_for_idle(timeout=600, status="active")

response = requests.get(
"http://127.0.0.1/_matrix/maubot/manifest.json",
Expand All @@ -72,7 +65,7 @@ async def test_build_and_deploy(


@pytest.mark.abort_on_fail
async def test_cos_integration(ops_test: OpsTest):
async def test_cos_integration(model: Model):
"""
arrange: deploy Anycharm.
act: integrate Maubot with Anycharm.
Expand Down Expand Up @@ -109,33 +102,75 @@ def peers(self):
"""
),
}
assert ops_test.model
await ops_test.model.deploy(
await model.deploy(
"any-charm",
application_name=any_app_name,
channel="beta",
config={"src-overwrite": json.dumps(any_charm_src_overwrite)},
)

await ops_test.model.add_relation(any_app_name, "maubot:grafana-dashboard")
await ops_test.model.wait_for_idle(status="active")
await model.add_relation(any_app_name, "maubot:grafana-dashboard")
await model.wait_for_idle(status="active")

unit = ops_test.model.applications[any_app_name].units[0]
unit = model.applications[any_app_name].units[0]
action = await unit.run_action("rpc", method="validate_dashboard")
await action.wait()
assert "return" in action.results
assert action.results["return"] == "null"


async def test_create_admin_action_success(ops_test: OpsTest):
@pytest.mark.abort_on_fail
async def test_loki_endpoint(ops_test: OpsTest, model: Model): # pylint: disable=unused-argument
"""
arrange: after Maubot is deployed and relations established
act: any-loki is deployed and joins the relation
assert: pebble plan inside maubot has logging endpoint.
"""
any_app_name = "any-loki"
loki_lib_url = (
"https://github.com/canonical/loki-k8s-operator/raw/refs/heads/main"
"/lib/charms/loki_k8s/v1/loki_push_api.py"
)
loki_lib = requests.get(loki_lib_url, timeout=10).text
any_charm_src_overwrite = {
"loki_push_api.py": loki_lib,
"any_charm.py": textwrap.dedent(
"""\
from loki_push_api import LokiPushApiProvider
from any_charm_base import AnyCharmBase
class AnyCharm(AnyCharmBase):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.loki_provider = LokiPushApiProvider(self, relation_name="provide-logging")
def get_relation_id(self):
relation = self.model.get_relation("provide-logging")
return relation.id
"""
),
}
await model.deploy(
"any-charm",
application_name=any_app_name,
channel="beta",
config={"src-overwrite": json.dumps(any_charm_src_overwrite), "python-packages": "cosl"},
)

await model.add_relation(any_app_name, "maubot:logging")
await model.wait_for_idle(status="active")
exit_code, stdout, stderr = await ops_test.juju(
"ssh", "--container", "maubot", "maubot/0", "pebble", "plan"
)
assert exit_code == 0, f"Command failed with exit code {exit_code} and stderr: {stderr}"
assert "loki" in stdout, f"'loki' not found in pebble plan:\n{stdout}"


async def test_create_admin_action_success(unit: Unit):
"""
arrange: Maubot charm integrated with PostgreSQL.
act: run the create-admin action.
assert: the action results contains a password.
"""
name = "test"
assert ops_test.model
unit = ops_test.model.applications["maubot"].units[0]

action = await unit.run_action("create-admin", name=name)
await action.wait()
Expand All @@ -159,15 +194,12 @@ async def test_create_admin_action_success(ops_test: OpsTest):
pytest.param("test", "test already exists", id="user_exists"),
],
)
async def test_create_admin_action_failed(name: str, expected_message: str, ops_test: OpsTest):
async def test_create_admin_action_failed(name: str, expected_message: str, unit: Unit):
"""
arrange: Maubot charm integrated with PostgreSQL.
act: run the create-admin action.
assert: the action results fails.
"""
assert ops_test.model
unit = ops_test.model.applications["maubot"].units[0]

action = await unit.run_action("create-admin", name=name)
await action.wait()

Expand All @@ -178,7 +210,8 @@ async def test_create_admin_action_failed(name: str, expected_message: str, ops_

@pytest.mark.abort_on_fail
async def test_public_url_config(
ops_test: OpsTest,
model: Model,
application: Application,
):
"""
arrange: Maubot is active and paths.json contains default value.
Expand All @@ -196,10 +229,8 @@ async def test_public_url_config(
assert "api_path" in data
assert data["api_path"] == "/_matrix/maubot/v1"

assert ops_test.model
application = ops_test.model.applications["maubot"]
await application.set_config({"public-url": "http://foo.com/internal/"})
await ops_test.model.wait_for_idle(timeout=600, status="active")
await model.wait_for_idle(timeout=600, status="active")

response = requests.get(
"http://127.0.0.1/_matrix/maubot/paths.json",
Expand All @@ -212,7 +243,7 @@ async def test_public_url_config(
assert data["api_path"] == "/internal/_matrix/maubot/v1"


async def test_register_client_account_action_success(ops_test: OpsTest):
async def test_register_client_account_action_success(unit: Unit, model: Model):
"""
arrange: Maubot charm integrated with PostgreSQL and AnyCharm(matrix-auth)
and admin user is created.
Expand All @@ -221,8 +252,6 @@ async def test_register_client_account_action_success(ops_test: OpsTest):
"""
# create user
name = secrets.token_urlsafe(5)
assert ops_test.model
unit = ops_test.model.applications["maubot"].units[0]
action = await unit.run_action("create-admin", name=name)
await action.wait()
assert "password" in action.results
Expand All @@ -239,7 +268,7 @@ async def test_register_client_account_action_success(ops_test: OpsTest):
# setting public_baseurl to an URL that Maubot can access
# in production environment, this is the external URL accessed by clients
matrix_server_name = "test1"
await ops_test.model.deploy(
await model.deploy(
"synapse",
application_name="synapse",
channel="latest/edge",
Expand All @@ -248,9 +277,9 @@ async def test_register_client_account_action_success(ops_test: OpsTest):
"public_baseurl": "http://synapse-0.synapse-endpoints.testing.svc.cluster.local:8080/",
},
)
await ops_test.model.wait_for_idle(status="active")
await ops_test.model.add_relation("synapse:matrix-auth", "maubot:matrix-auth")
await ops_test.model.wait_for_idle(status="active")
await model.wait_for_idle(status="active")
await model.add_relation("synapse:matrix-auth", "maubot:matrix-auth")
await model.wait_for_idle(status="active")

# run the action
account_name = secrets.token_urlsafe(5).lower()
Expand Down

0 comments on commit c53cc70

Please sign in to comment.