Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
2 changes: 2 additions & 0 deletions .github/workflows/integration_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ jobs:
secrets: inherit
with:
modules: '["test_actions.py", "test_charm.py", "test_s3.py", "test_saml.py", "test_loki.py"]'
channel: 1.34-strict/stable
juju-channel: 3.6/stable
trivy-exit-code: 0
allure-report:
if: ${{ !cancelled() && github.event_name == 'schedule' }}
Expand Down
16 changes: 16 additions & 0 deletions docs/release-notes/artifacts/migrate_to_jubilant.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
version_schema: 2

changes:
- title: Migrated integration tests from pytest-operator to jubilant
author: tonyandrewmeyer
type: minor
description: |
Migrated integration tests to use the jubilant library and
pytest-jubilant plugin instead of pytest-operator.
urls:
pr:
- https://github.com/canonical/indico-operator/pull/3
related_doc:
related_issue:
visibility: internal
highlight: false
187 changes: 97 additions & 90 deletions tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,30 @@

"""Fixtures for Indico charm integration tests."""

import asyncio
import shlex
import subprocess # nosec B404
from pathlib import Path
from secrets import token_hex
from typing import Dict, Union

import pytest_asyncio
import jubilant
import pytest
import yaml
from ops import Application
from pytest import Config, fixture
from pytest_operator.plugin import OpsTest


# Shim: the CI action passes --keep-models (pytest-jubilant 1.x flag) but
# pytest-jubilant 2.x renamed it to --no-juju-teardown.
# Remove this once the action is updated to use --no-juju-teardown.
def pytest_addoption(parser):
"""Register the legacy --keep-models flag.""" # noqa: DCO020
parser.addoption("--keep-models", action="store_true", default=False)


def pytest_configure(config):
"""Translate --keep-models into --no-juju-teardown.""" # noqa: DCO020
if config.getoption("--keep-models", default=False):
config.option.no_juju_teardown = True


@fixture(scope="module", name="external_url")
Expand All @@ -22,7 +37,7 @@ def external_url_fixture():

@fixture(scope="module")
def saml_email(pytestconfig: Config):
"""SAML login email address test argument for SAML integration tests"""
"""SAML login email address test argument for SAML integration tests."""
email = pytestconfig.getoption("--saml-email")
if not email:
raise ValueError("--saml-email argument is required for selected test cases")
Expand All @@ -31,7 +46,7 @@ def saml_email(pytestconfig: Config):

@fixture(scope="module")
def saml_password(pytestconfig: Config):
"""SAML login password test argument for SAML integration tests"""
"""SAML login password test argument for SAML integration tests."""
password = pytestconfig.getoption("--saml-password")
if not password:
raise ValueError("--saml-password argument is required for selected test cases")
Expand All @@ -52,140 +67,132 @@ def app_name_fixture(metadata):

@fixture(scope="module")
def requests_timeout():
"""Provides a global default timeout for HTTP requests"""
"""Provides a global default timeout for HTTP requests."""
yield 15


@pytest_asyncio.fixture(scope="module", name="app")
async def app_fixture(
ops_test: OpsTest,
@pytest.fixture(scope="module", name="app")
def app_fixture(
juju: jubilant.Juju,
app_name: str,
pytestconfig: Config,
):
"""Indico charm used for integration testing.

Builds the charm and deploys it and the relations it depends on.
"""
assert ops_test.model
# Deploy relations to speed up overall execution
postgresql_config = {
"plugin_pg_trgm_enable": str(True),
"plugin_unaccent_enable": str(True),
postgresql_config: Dict[str, Union[bool, str]] = {
"plugin_pg_trgm_enable": True,
"plugin_unaccent_enable": True,
"profile": "testing",
}
await asyncio.gather(
ops_test.model.deploy(
"postgresql-k8s", channel="14/edge", config=postgresql_config, trust=True
),
ops_test.model.deploy("redis-k8s", "redis-broker", channel="latest/edge"),
ops_test.model.deploy("redis-k8s", "redis-cache", channel="latest/edge"),
ops_test.model.deploy(
"nginx-ingress-integrator",
channel="latest/edge",
revision=133,
trust=True,
series="focal",
),
juju.deploy("postgresql-k8s", channel="14/edge", config=postgresql_config, trust=True)
juju.deploy("redis-k8s", "redis-broker", channel="latest/edge")
juju.deploy("redis-k8s", "redis-cache", channel="latest/edge")
juju.deploy(
"nginx-ingress-integrator",
channel="latest/edge",
revision=133,
trust=True,
Comment thread
florentianayuwono marked this conversation as resolved.
base="ubuntu@20.04",
)

resources = {
"indico-image": pytestconfig.getoption("--indico-image"),
"indico-nginx-image": pytestconfig.getoption("--indico-nginx-image"),
}

if charm := pytestconfig.getoption("--charm-file"):
application = await ops_test.model.deploy(
f"./{charm}",
resources=resources,
application_name=app_name,
series="focal",
)
juju.deploy(f"./{charm}", app_name, resources=resources, base="ubuntu@20.04")
else:
charm = await ops_test.build_charm(".")
application = await ops_test.model.deploy(
proc = subprocess.run( # nosec B603
shlex.split("charmcraft pack -p ./"),
check=True,
capture_output=True,
text=True,
)
packed = [
line.split()[1]
for line in proc.stderr.strip().splitlines()
if line.startswith("Packed")
]
if not packed:
raise RuntimeError(f"No packed charm found in charmcraft output: {proc.stderr}")
charm = Path(packed[0]).resolve()
juju.deploy(
charm,
app_name,
resources=resources,
application_name=app_name,
config={
"external_plugins": "https://github.com/canonical/flask-multipass-saml-groups/releases/download/1.2.2/flask_multipass_saml_groups-1.2.2-py3-none-any.whl" # noqa: E501 pylint: disable=line-too-long
},
series="focal",
base="ubuntu@20.04",
)

await asyncio.gather(
ops_test.model.add_relation(app_name, "postgresql-k8s"),
ops_test.model.add_relation(f"{app_name}:redis-broker", "redis-broker"),
ops_test.model.add_relation(f"{app_name}:redis-cache", "redis-cache"),
ops_test.model.add_relation(app_name, "nginx-ingress-integrator"),
)
await ops_test.model.wait_for_idle(
apps=[
application.name,
juju.integrate(app_name, "postgresql-k8s")
juju.integrate(f"{app_name}:redis-broker", "redis-broker")
juju.integrate(f"{app_name}:redis-cache", "redis-cache")
juju.integrate(app_name, "nginx-ingress-integrator")
juju.wait(
lambda status: jubilant.all_active(
status,
app_name,
"postgresql-k8s",
"redis-broker",
"redis-cache",
"nginx-ingress-integrator",
],
status="active",
raise_on_error=True,
),
error=jubilant.any_error,
)
yield application
yield app_name


@pytest_asyncio.fixture(scope="module", name="saml_integrator")
async def saml_integrator_fixture(ops_test: OpsTest, app: Application):
@pytest.fixture(scope="module", name="saml_integrator")
def saml_integrator_fixture(juju: jubilant.Juju, app: str):
"""SAML integrator charm used for integration testing."""
assert ops_test.model
saml_config = {
"entity_id": "https://login.staging.ubuntu.com",
"metadata_url": "https://login.staging.ubuntu.com/saml/metadata",
}
saml_integrator = await ops_test.model.deploy(
"saml-integrator", channel="latest/stable", config=saml_config, trust=True
)
await ops_test.model.add_relation(app.name, saml_integrator.name)
await ops_test.model.wait_for_idle(
apps=[saml_integrator.name, app.name], status="active", raise_on_error=True
juju.deploy("saml-integrator", channel="latest/stable", config=saml_config, trust=True)
juju.integrate(app, "saml-integrator")
juju.wait(
lambda status: jubilant.all_active(status, "saml-integrator", app),
error=jubilant.any_error,
)
yield saml_integrator
yield "saml-integrator"


@pytest_asyncio.fixture(scope="module", name="s3_integrator")
async def s3_integrator_fixture(ops_test: OpsTest, app: Application):
"""SAML integrator charm used for integration testing."""
assert ops_test.model
@pytest.fixture(scope="module", name="s3_integrator")
def s3_integrator_fixture(juju: jubilant.Juju, app: str):
"""S3 integrator charm used for integration testing."""
s3_config = {
"bucket": "some-bucket",
"endpoint": "s3.example.com",
}
s3_integrator = await ops_test.model.deploy(
"s3-integrator", channel="latest/edge", config=s3_config
)
await ops_test.model.wait_for_idle(apps=[s3_integrator.name], idle_period=5, status="blocked")
juju.deploy("s3-integrator", channel="latest/edge", config=s3_config)
juju.wait(lambda status: jubilant.all_blocked(status, "s3-integrator"))
params = {"access-key": token_hex(16), "secret-key": token_hex(16)}
# Application actually does have units
action_sync_s3_credentials = await s3_integrator.units[0].run_action( # type: ignore
"sync-s3-credentials", **params
juju.run("s3-integrator/0", "sync-s3-credentials", params=params)
juju.wait(
lambda status: jubilant.all_active(status, "s3-integrator"),
error=jubilant.any_error,
)
await action_sync_s3_credentials.wait()
await ops_test.model.wait_for_idle(
apps=[s3_integrator.name], status="active", raise_on_error=True
juju.integrate(app, "s3-integrator")
juju.wait(
lambda status: jubilant.all_active(status, "s3-integrator", app),
error=jubilant.any_error,
)
await ops_test.model.add_relation(app.name, s3_integrator.name)
await ops_test.model.wait_for_idle(
apps=[s3_integrator.name, app.name], status="active", raise_on_error=True
)
yield s3_integrator
yield "s3-integrator"


@pytest_asyncio.fixture(scope="module", name="loki")
async def loki_fixture(ops_test: OpsTest, app: Application):
@pytest.fixture(scope="module", name="loki")
def loki_fixture(juju: jubilant.Juju, app: str):
"""Loki charm used for integration testing."""
assert ops_test.model
loki = await ops_test.model.deploy(
"loki-k8s", channel="1/edge", trust=True, revision=97, series="focal"
)
await ops_test.model.add_relation(app.name, loki.name)
await ops_test.model.wait_for_idle(
apps=[loki.name, app.name], status="active", raise_on_error=True
juju.deploy("loki-k8s", channel="1/edge", trust=True, revision=97, base="ubuntu@20.04")
juju.integrate(app, "loki-k8s")
juju.wait(
lambda status: jubilant.all_active(status, "loki-k8s", app),
error=jubilant.any_error,
)
yield loki
yield "loki-k8s"
Loading
Loading