Skip to content

Commit

Permalink
Deploy environment for integration tests with COS (#185)
Browse files Browse the repository at this point in the history
  • Loading branch information
sudeephb authored Mar 7, 2024
1 parent 86e3505 commit de73fe2
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 0 deletions.
31 changes: 31 additions & 0 deletions tests/integration/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Copyright 2024 Canonical Ltd.
# See LICENSE file for licensing details.

import pytest


def pytest_addoption(parser):
parser.addoption(
"--series",
type=str.lower,
default="jammy",
choices=["focal", "jammy"],
help="Set series for the machine units",
)
parser.addoption(
"--channel",
type=str,
default="edge",
choices=["edge", "candidate", "stable"],
help="Charmhub channel to use during charms deployment",
)


@pytest.fixture(scope="module")
def series(request):
return request.config.getoption("--series")


@pytest.fixture(scope="module")
def channel(request):
return request.config.getoption("--channel")
21 changes: 21 additions & 0 deletions tests/integration/offers-overlay.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
applications:
alertmanager:
offers:
alertmanager-karma-dashboard:
endpoints:
- karma-dashboard
grafana:
offers:
grafana-dashboards:
endpoints:
- grafana-dashboard
loki:
offers:
loki-logging:
endpoints:
- logging
prometheus:
offers:
prometheus-receive-remote-write:
endpoints:
- receive-remote-write
4 changes: 4 additions & 0 deletions tests/integration/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
jinja2
juju~=3.3.0 # must be compatible with the juju CLI version installed by CI
pytest
pytest-operator
123 changes: 123 additions & 0 deletions tests/integration/test_cos_integration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#!/usr/bin/env python3
# Copyright 2024 Canonical Ltd.
# See LICENSE file for licensing details.

import asyncio
import logging
import os
import subprocess
from pathlib import Path

import pytest
from juju.controller import Controller
from pytest_operator.plugin import OpsTest
from utils import get_or_add_model

logger = logging.getLogger(__name__)

LXD_CTL_NAME = os.environ.get("LXD_CONTROLLER")
K8S_CTL_NAME = os.environ.get("K8S_CONTROLLER")

MODEL_CONFIG = {"logging-config": "<root>=WARNING; unit=DEBUG"}


@pytest.mark.abort_on_fail
async def test_setup_and_deploy(ops_test: OpsTest, series, channel):
"""Setup models and then deploy Hardware Observer and COS."""
if LXD_CTL_NAME is None or K8S_CTL_NAME is None:
pytest.fail("LXD_CONTROLLER and K8S_CONTROLLER env variables should be provided")

# The current model name is generated by pytest-operator from the test name + random suffix.
# Use the same model name in both controllers.
k8s_mdl_name = lxd_mdl_name = ops_test.model_name

# Assuming a lxd controller is ready and its name is stored in $LXD_CONTROLLER.
lxd_ctl = Controller()
await lxd_ctl.connect(LXD_CTL_NAME)
lxd_mdl = await get_or_add_model(ops_test, lxd_ctl, lxd_mdl_name)
await lxd_mdl.set_config(MODEL_CONFIG)

# Assuming a k8s controller is ready and its name is stored in $K8S_CONTROLLER.
k8s_ctl = Controller()
await k8s_ctl.connect(K8S_CTL_NAME)
k8s_mdl = await get_or_add_model(ops_test, k8s_ctl, k8s_mdl_name)
await k8s_mdl.set_config(MODEL_CONFIG)

await _deploy_cos(channel, k8s_mdl)

await _deploy_hardware_observer(series, channel, lxd_mdl)

await _add_cross_controller_relations(k8s_ctl, lxd_ctl, k8s_mdl, lxd_mdl)

# This verifies that the cross-controller relation with COS is successful
assert lxd_mdl.applications["grafana-agent"].status == "active"


async def _deploy_cos(channel, model):
"""Deploy COS on the existing k8s cloud."""
await model.deploy(
"cos-lite",
channel=channel,
trust=True,
overlays=[str(Path(__file__).parent.resolve() / "offers-overlay.yaml")],
)


async def _deploy_hardware_observer(series, channel, model):
"""Deploy Hardware Observer and Grafana Agent on the existing lxd cloud."""
await asyncio.gather(
# Principal Ubuntu
model.deploy(
"ubuntu",
num_units=1,
series=series,
channel=channel,
),
# Hardware Observer
model.deploy("hardware-observer", series=series, num_units=0, channel=channel),
# Grafana Agent
model.deploy(
"grafana-agent",
num_units=0,
series=series,
channel=channel,
),
)

await model.add_relation("ubuntu:juju-info", "hardware-observer:general-info")
await model.add_relation("hardware-observer:cos-agent", "grafana-agent:cos-agent")
await model.add_relation("ubuntu:juju-info", "grafana-agent:juju-info")

await model.block_until(lambda: model.applications["hardware-observer"].status == "active")


async def _add_cross_controller_relations(k8s_ctl, lxd_ctl, k8s_mdl, lxd_mdl):
"""Add relations between Grafana Agent and COS."""
cos_saas_names = ["prometheus-receive-remote-write", "loki-logging", "grafana-dashboards"]
for saas in cos_saas_names:
# Using juju cli since Model.consume() from libjuju causes error.
# https://github.com/juju/python-libjuju/issues/1031
cmd = [
"juju",
"consume",
"--model",
f"{lxd_ctl.controller_name}:{k8s_mdl.name}",
f"{k8s_ctl.controller_name}:admin/{k8s_mdl.name}.{saas}",
]
subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
await lxd_mdl.add_relation("grafana-agent", saas),

# `idle_period` needs to be greater than the scrape interval to make sure metrics ingested.
await asyncio.gather(
# First, we wait for the critical phase to pass with raise_on_error=False.
# (In CI, using github runners, we often see unreproducible hook failures.)
lxd_mdl.wait_for_idle(timeout=1800, idle_period=180, raise_on_error=False),
k8s_mdl.wait_for_idle(timeout=1800, idle_period=180, raise_on_error=False),
)

await asyncio.gather(
# Then we wait for "active", without raise_on_error=False, so the test fails sooner in case
# there is a persistent error status.
lxd_mdl.wait_for_idle(status="active", timeout=7200, idle_period=180),
k8s_mdl.wait_for_idle(status="active", timeout=7200, idle_period=180),
)
23 changes: 23 additions & 0 deletions tests/integration/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Copyright 2024 Canonical Ltd.
# See LICENSE file for licensing details.

import logging

from juju.controller import Controller
from juju.model import Model
from pytest_operator.plugin import OpsTest

logger = logging.getLogger(__name__)


async def get_or_add_model(ops_test: OpsTest, controller: Controller, model_name: str) -> Model:
# Pytest Operator provides a --model option. If provided, model with that name will be used.
# So, we need to check if it already exists.
if model_name not in await controller.get_models():
await controller.add_model(model_name)
ctl_name = controller.controller_name
await ops_test.track_model(
f"{ctl_name}-{model_name}", cloud_name=ctl_name, model_name=model_name, keep=False
)

return await controller.get_model(model_name)
10 changes: 10 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,13 @@ deps =
passenv =
REDFISH_USERNAME
REDFISH_PASSWORD

[testenv:integration]
description = Run integration tests with COS
deps =
-r {toxinidir}/tests/integration/requirements.txt
passenv =
K8S_CONTROLLER
LXD_CONTROLLER
commands =
pytest {toxinidir}/tests/integration {posargs:-s -vv --log-cli-level=INFO}

0 comments on commit de73fe2

Please sign in to comment.