Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
4d43483
refactor(tracing): de-pydantic the vendored charm libs
claude Jun 10, 2026
c5454f3
refactor(tracing): promote depydantic'd files from vendor/ to private…
tonyandrewmeyer Jun 13, 2026
24461d2
chore(tracing): satisfy ruff --preview on de-pydantic'd modules
tonyandrewmeyer Jun 20, 2026
e726dc1
chore(tracing): satisfy ruff format --preview on de-pydantic'd modules
tonyandrewmeyer Jun 22, 2026
8a586c1
ref(tracing): drop unused requirer-side surface from depydantic'd libs
tonyandrewmeyer Jun 26, 2026
1839515
ref(tracing): fold duplicated databag helpers into _databag module
tonyandrewmeyer Jun 26, 2026
9ae9003
ref(tracing): write requirer databag via ops.Relation.save
tonyandrewmeyer Jun 26, 2026
1333304
ref(tracing): use 'import ops' + ops.X everywhere in depydantic'd libs
tonyandrewmeyer Jun 26, 2026
4828fb2
docs(tracing): trim de-vendoring history from depydantic'd module hea…
tonyandrewmeyer Jun 26, 2026
a8fdbe7
docs(tracing): point depydantic'd module headers at canonical.com sch…
tonyandrewmeyer Jun 26, 2026
c42ae8f
ref(tracing): inline certificate_transfer databag read, drop dataclas…
tonyandrewmeyer Jun 26, 2026
ad7c218
ref(tracing): drop CertificateTransferRequires wrapper, observe juju …
tonyandrewmeyer Jun 26, 2026
461a6df
ref(tracing): drop TracingEndpointRequirer wrapper, drive the tracing…
tonyandrewmeyer Jun 26, 2026
a8f3672
test(tracing): pin dataclasses against upstream charmlibs schemas
tonyandrewmeyer Jun 26, 2026
b1e873f
test(tracing): pin tracing/cert_transfer behaviour against upstream i…
tonyandrewmeyer Jun 26, 2026
fe7b15b
ref(tracing): drop _databag helper, recurse nested dataclasses in Rel…
tonyandrewmeyer Jun 27, 2026
b25d3f7
ref(tracing): model certificate_transfer databag, load via Relation.load
tonyandrewmeyer Jun 27, 2026
35f7d09
test(tracing): add gated integration tests and upstream canary
tonyandrewmeyer Jun 27, 2026
a73642c
test(tracing): port branch tests to kubectl_port_forward
tonyandrewmeyer Jul 5, 2026
3645509
feat(tracing): handshake v1 on certificate_transfer, fall back to v0
tonyandrewmeyer Jul 5, 2026
8b0d377
ci: retry all snap installs with jittered backoff
tonyandrewmeyer Jul 5, 2026
d68d50c
test(tracing): settle full model before re-integrating in relation-churn
tonyandrewmeyer Jul 6, 2026
996896b
test(tracing): drop flaky span check from leader-only test
tonyandrewmeyer Jul 6, 2026
cb04503
ci: drop snap install retry loops
tonyandrewmeyer Jul 6, 2026
3929d10
ci(tracing): SHA-pin actions/checkout in upstream-schemas workflow
tonyandrewmeyer Jul 14, 2026
c709080
chore(tracing): use ruff: ignore in test_upstream_schemas.py
tonyandrewmeyer Aug 30, 2026
6ecc01d
split(model): drop Relation.load recursive coercion, moved to relatio…
claude Sep 9, 2026
2205c3e
Apply suggestion from @tonyandrewmeyer
tonyandrewmeyer Sep 10, 2026
21aa54e
Apply suggestion from @tonyandrewmeyer
tonyandrewmeyer Sep 10, 2026
05eb956
split(test): drop the nginx-image canary, moved to its own branch
tonyandrewmeyer Sep 10, 2026
bd74cbb
docs(test): drop the rev-143 gate from the tracing docstring
tonyandrewmeyer Sep 10, 2026
3143ebd
docs(test): the tracing tests are not gated on anything
tonyandrewmeyer Sep 10, 2026
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
3 changes: 2 additions & 1 deletion .github/workflows/smoke.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ jobs:
continue-on-error: ${{ matrix.juju-channel == '4/stable' && matrix.preset == 'microk8s' }}

steps:
- run: sudo snap install --classic concierge
- name: Install Concierge
run: sudo snap install --classic concierge
- run: >
sudo concierge prepare
--juju-channel=${{ matrix.juju-channel }}
Expand Down
33 changes: 33 additions & 0 deletions .github/workflows/tracing-upstream-schemas.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Tracing upstream schema drift

# Opt-in drift check: compares ``ops_tracing``'s de-pydantic'd dataclasses
# against the canonical/charmlibs schemas at HEAD. Needs network access and
# pydantic, so it's limited to PRs/pushes that touch the tracing tree.

on:
push:
branches:
- main
paths:
- 'tracing/**'
- '.github/workflows/tracing-upstream-schemas.yaml'
pull_request:
paths:
- 'tracing/**'
- '.github/workflows/tracing-upstream-schemas.yaml'
workflow_dispatch:

permissions: {}

jobs:
upstream-schemas:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
- run: uv tool install tox --with tox-uv
- run: uv python install 3.12
- name: Run tracing upstream schema drift check
run: cd tracing && tox -e upstream-schemas
148 changes: 148 additions & 0 deletions test/integration/test_tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
# limitations under the License.


"""Integration tests for ops_tracing."""

from __future__ import annotations

import json
Expand Down Expand Up @@ -96,6 +98,152 @@ def test_with_tls(build_tracing_charm: Callable[[], str], tracing_juju: jubilant
# about.


def test_buffer_replay_before_provider_ready(
build_tracing_charm: Callable[[], str], tracing_juju: jubilant.Juju
):
"""Spans emitted before the tracing relation is up are buffered and replayed.

Exercises ops_tracing._buffer end-to-end: an action fires while no tracing
relation exists, then tempo is integrated, and the buffered span must land.
"""
charm_path = build_tracing_charm()
tracing_juju.deploy(charm_path)
tracing_juju.wait(lambda status: jubilant.all_active(status, 'test-tracing'))

checkpoint = time.time()
arg_value = 'buffered-arg'
tracing_juju.run('test-tracing/0', 'one', params={'arg': arg_value})

tracing_juju.integrate('test-tracing', 'tempo')
tracing_juju.wait(jubilant.all_active)

with kubectl_port_forward(tracing_juju.model, 'svc/tempo-worker', 3200) as endpoint:
spans = wait_spans(
endpoint,
ready=lambda spans: 'custom trace on any action' in str(spans),
since=checkpoint,
timeout=180,
)
names = [span['name'] for span in spans]
assert 'custom trace on any action' in names, (
f'buffered action span never replayed; saw: {names}'
)
action_span = next(span for span in spans if span['name'] == 'custom trace on any action')
assert arg_value in json.dumps(action_span)


def test_relation_churn(build_tracing_charm: Callable[[], str], tracing_juju: jubilant.Juju):
"""Removing and re-adding the tracing relation leaves the requirer healthy.

Guards the new direct-juju-event observation in the Tracing wrapper (no
longer mediated by TracingEndpointRequirer): -broken must tear cleanly
and -joined must re-arm export.
"""
charm_path = build_tracing_charm()
tracing_juju.deploy(charm_path)
tracing_juju.integrate('test-tracing', 'tempo')
tracing_juju.wait(jubilant.all_active)

tracing_juju.cli('remove-relation', 'test-tracing:charm-tracing', 'tempo:tracing')
# Waiting only for test-tracing races the re-integrate below: test-tracing's
# -relation-departed hook finishes in ~1s, but the relation stays "dying" on
# the controller until tempo has also processed its -broken hook, and
# `juju integrate` refuses "relation ... is dying, but not yet removed
# (already exists)" during that window. Wait for the whole model to settle
# so both sides have torn down before we re-integrate.
tracing_juju.wait(jubilant.all_active)

tracing_juju.integrate('test-tracing', 'tempo')
tracing_juju.wait(jubilant.all_active)

checkpoint = time.time()
arg_value = 'post-churn-arg'
tracing_juju.run('test-tracing/0', 'one', params={'arg': arg_value})

with kubectl_port_forward(tracing_juju.model, 'svc/tempo-worker', 3200) as endpoint:
spans = wait_spans(
endpoint,
ready=lambda spans: arg_value in json.dumps(spans),
since=checkpoint,
)
assert 'custom trace on any action' in [span['name'] for span in spans], (
'spans did not flow after re-integrating charm-tracing'
)


def test_ca_rotation(build_tracing_charm: Callable[[], str], tracing_juju: jubilant.Juju):
"""A CA cert removed mid-life and re-added must continue to be honoured.

Pins the inlined certificate_transfer read path: -broken must clear the
trusted CA and -changed on re-integration must re-load it without
restarting the requirer.
"""
_xfail_on_k8s_juju4(tracing_juju, JUJU4_K8S_SECRET_RBAC_BUG)
charm_path = build_tracing_charm()
tracing_juju.deploy('self-signed-certificates')
tracing_juju.integrate('tempo:certificates', 'self-signed-certificates')
tracing_juju.wait(jubilant.all_active)

tracing_juju.deploy(charm_path)
tracing_juju.integrate('test-tracing', 'self-signed-certificates')
tracing_juju.integrate('test-tracing', 'tempo')
tracing_juju.wait(jubilant.all_active)

# tempo terminates TLS (self-signed-certificates is related to tempo, not
# tempo-worker), so we query the coordinator, not the worker.
with kubectl_port_forward(tracing_juju.model, 'svc/tempo', 3200) as endpoint:
wait_spans(endpoint, ready=lambda spans: 'ops.main' in str(spans), https=True)

# Rotate the CA on the provider; the requirer must pick up the new cert
# via certificate_transfer -changed without restarting the export pipeline.
tracing_juju.run('self-signed-certificates/0', 'rotate-private-key')
tracing_juju.wait(jubilant.all_active)

checkpoint = time.time()
arg_value = 'post-rotation-arg'
tracing_juju.run('test-tracing/0', 'one', params={'arg': arg_value})

spans = wait_spans(
endpoint,
ready=lambda spans: arg_value in json.dumps(spans),
since=checkpoint,
https=True,
)
assert 'custom trace on any action' in [span['name'] for span in spans], (
'spans did not flow over TLS after CA was rotated'
)


def test_only_leader_writes_requirer_databag(
build_tracing_charm: Callable[[], str], tracing_juju: jubilant.Juju
):
"""Followers must not crash, and only the leader writes the requirer databag.

The conformance suite pins this against the wire model; this test verifies
it survives an actual two-unit Juju deployment with the new Tracing
wrapper driving the relation.
"""
charm_path = build_tracing_charm()
tracing_juju.deploy(charm_path, num_units=2)
tracing_juju.integrate('test-tracing', 'tempo')
# If a non-leader-aware requirer tried to write the app databag on the
# follower unit, Juju would fail the follower's hook and jubilant.all_active
# would time out here — so reaching all_active with two units is itself the
# follower-does-not-crash invariant. The show-unit check below verifies the
# positive: the leader did populate the app databag.
tracing_juju.wait(jubilant.all_active)

# Show-unit dumps each unit's relation data; the application section under
# the charm-tracing endpoint is what the leader populated.
raw = tracing_juju.cli('show-unit', 'test-tracing/0', '--format=json')
data = json.loads(raw)
relations = data['test-tracing/0']['relation-info']
charm_tracing = next(r for r in relations if r['endpoint'] == 'charm-tracing')
assert charm_tracing['application-data'], (
'leader unit should have populated the charm-tracing app databag'
)


def wait_spans(
endpoint: tuple[str, int],
ready: Callable[[list[dict[str, Any]]], bool],
Expand Down
16 changes: 0 additions & 16 deletions tracing/ops_tracing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,6 @@
have relations like these. If the names of the relations differ from this recipe, please
adjust the code on the rest of this page to your relation names.

.. hint::
Make sure to include the Rust build packages in your ``charmcraft.yaml``, because
this library depends on ``pydantic-core`` via ``pydantic``.

.. code-block:: yaml

parts:
charm:
plugin: charm
source: .
build-packages:
- cargo

If you're migrating from the ``charm-tracing`` charm lib, this configuration is
likely already in place.

In your charm, add and initialise the ``Tracing`` object.::

import ops
Expand Down
Loading
Loading