Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 4 additions & 1 deletion .github/workflows/integration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,7 @@ jobs:
- run: tox -e integration -- --log-cli-level=INFO -s "$MATRIX_TEST"
env:
MATRIX_TEST: ${{ matrix.test }}
timeout-minutes: 20 # Juju 4 gets stuck while destroying the model
# A backstop for Juju 4 getting stuck while destroying the model. The
# tracing tests each deploy the tempo stack into their own fresh
# model, so that file needs longer than the others.
timeout-minutes: ${{ matrix.test == 'test/integration/test_tracing.py' && 40 || 20 }}
25 changes: 21 additions & 4 deletions test/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,27 @@ def _xfail_on_k8s_juju4(juju: jubilant.Juju, reason: str) -> None:


@pytest.fixture
def tracing_juju(juju: jubilant.Juju) -> Generator[jubilant.Juju]:
"""Make a Juju model with the tracing part of COS ready."""
def tracing_juju() -> Generator[jubilant.Juju]:
"""Make a fresh Juju model with the tracing part of COS ready.

Unlike the module-scoped `juju` fixture, this makes a new model for each
test: the tracing tests each deploy the same charm under test and
reconfigure the tempo stack (for example, enabling TLS), so a model cannot
be shared between them.
"""
with jubilant.temp_model() as juju:
juju.wait_timeout = 900
# The charm under test only retries sending buffered trace data on a
# later dispatch, so make update-status fire frequently to bound how
# long the tests wait for spans to arrive.
juju.cli('model-config', 'update-status-hook-interval=1m')
_deploy_tracing_stack(juju)
yield juju
print(juju.debug_log())


def _deploy_tracing_stack(juju: jubilant.Juju) -> None:
"""Deploy tempo, its worker, and the minio/s3 storage it requires."""
deploy_tempo(juju)
deploy_tempo_worker(juju)
minio_config = {'access-key': 'accesskey', 'secret-key': 'mysoverysecretkey'}
Expand Down Expand Up @@ -182,8 +201,6 @@ def tracing_juju(juju: jubilant.Juju) -> Generator[jubilant.Juju]:

juju.wait(jubilant.all_active)

yield juju


@pytest.fixture(scope='session')
def tracing_charm_dir(pytestconfig: pytest.Config) -> Generator[pathlib.Path]:
Expand Down
34 changes: 23 additions & 11 deletions test/integration/test_relation.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from __future__ import annotations

import json
import time
from collections.abc import Callable

import jubilant
Expand Down Expand Up @@ -58,15 +59,26 @@ def test_relation_units(build_relation_charm: Callable[[], str], juju: jubilant.
peer_units.remove(f'{charm_name}/0')
db_units = set(status.get_units(db))
ingress_units = set(status.get_units(ingress))
task = juju.run(f'{charm_name}/0', 'get-units')
assert task.success, task.message
ops_units = json.loads(task.results['units'])

# The keys in the action results are the endpoint names, and the values are
# the unit names received from Juju.
assert all(unit.startswith('test-db/') for unit in ops_units['db'])
assert all(unit.startswith('test-ingress/') for unit in ops_units['ingress'])
assert all(unit.startswith('test-relation/') for unit in ops_units['peer'])
assert set(ops_units['db']) == db_units
assert set(ops_units['ingress']) == ingress_units
assert set(ops_units['peer']) == peer_units
# Relation membership propagates to each unit asynchronously, even after
# all units report active, so retry until Juju has caught up.
deadline = time.time() + 300
while True:
task = juju.run(f'{charm_name}/0', 'get-units')
assert task.success, task.message
# The keys in the action results are the endpoint names, and the
# values are the unit names received from Juju. An endpoint whose
# relation has no remote units yet is absent entirely.
ops_units: dict[str, list[str]] = json.loads(task.results['units'])
complete = (
set(ops_units.get('db', [])) == db_units
and set(ops_units.get('ingress', [])) == ingress_units
and set(ops_units.get('peer', [])) == peer_units
)
if complete or time.time() > deadline:
break
time.sleep(10)

assert set(ops_units.get('db', [])) == db_units
assert set(ops_units.get('ingress', [])) == ingress_units
assert set(ops_units.get('peer', [])) == peer_units
17 changes: 14 additions & 3 deletions test/integration/test_tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,23 @@ def test_with_tls(build_tracing_charm: Callable[[], str], tracing_juju: jubilant
# Query the tempo coordinator: it terminates TLS (self-signed-certificates
# is related to tempo, not tempo-worker), so the worker's 3200 still
# speaks plain HTTP and causes a TLS handshake failure on a TLS client.
#
# After TLS is enabled, tempo's ingester ring can take minutes to re-form,
# and until it does the collector rejects spans ("empty ring"). The charm
# buffers rejected spans and only retries on a later dispatch, such as
# update-status, so allow enough time for a retry to land.
with kubectl_port_forward(tracing_juju.model, 'svc/tempo', 3200) as endpoint:
spans = wait_spans(endpoint, ready=lambda spans: 'ops.main' in str(spans), https=True)
spans = wait_spans(
endpoint, ready=lambda spans: 'ops.main' in str(spans), https=True, timeout=600
)
assert 'ops.main' in [span['name'] for span in spans]

event_names = [event['name'] for event in get_events(spans)]
assert 'StartEvent' in event_names
# Unlike test_direct_connection, don't assert on events from specific
# early dispatches (such as StartEvent): the exporter only waits a bounded
# time at the end of each dispatch, so trace data produced while the
# collector is rejecting requests ("empty ring") can be legitimately
# dropped. Receiving any ops.main span over HTTPS is what this test is
# about.


def wait_spans(
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ commands =
[testenv:integration]
description = Run a suite of integration tests.
dependency_groups = integration
commands = pytest -vvv --tb native {toxinidir}/test/integration/ {posargs}
commands = pytest -vvv --tb native {posargs:{toxinidir}/test/integration/}

[testenv:draft-release]
description = Make a draft release
Expand Down
Loading
Loading