-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Deploy environment for integration tests with COS (#185)
- Loading branch information
Showing
6 changed files
with
212 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters