Files
lmxopcua/docs/drivers/Mqtt-Test-Fixture.md
T
Joseph Doherty 290117660d
v2-ci / build (push) Successful in 6m26s
v2-ci / unit-tests (push) Failing after 18m0s
docs: truth sweep — reconcile stale deferment/archreview/plan records with shipped state
- deferment.md: node ACLs no longer authored-but-inert (#520 shipped through stage 7, opt-in Security:Acl:Enabled); failure-visibility trio mostly closed (01/S-1 + 03/S4 fixed, 06/S-1 partial); bulkhead + WriteIdempotentAttribute closed by R2-02; Sql Postgres+Oracle dialects shipped (d54f3c83); akka-hosting partials verified superseded
- archreview/00-OVERALL.md: staleness banner on the carried-open block (04/C-1→R2-05, 03/S2+S3→R2-08, 03/P1→R2-07, driver fleet→R2-09/R2-11/#457, hygiene→R2-12, trio per above; what genuinely remains from theme 4)
- archreview/plans/STATUS.md: carved out the true exceptions to "every Round-2 live gate GREEN" (R2-03 T11/T12 deferred-live, R2-10 T10 breaker-OPEN never forced); #459 closed by mesh Phase 7 drill D4 (c50ebcf7)
- archreview/plans/INTEGRATION-SWEEP-STATUS.md: "follow-ups unstarted" → all 7 shipped as PRs #446–#452
- archreview/06-gateway-integrations.md: 06/S-1 STILL OPEN → PARTIAL (fail-closed advise + unconfirmed meter; residual cross-repo)
- 2026-07-31-apikey-acl-enforcement-design.md: status → implemented through stage 7, #520 CLOSED 2026-08-01
- 2026-07-21-per-cluster-mesh-design.md: "Phases 1–7 not started" → PROGRAM COMPLETE 2026-07-24 (Phase 7 c50ebcf7)
- 2026-07-20-localdb-phase2-live-gate.md: merged as 06b5d81b; open design question shipped by mesh Phase 6 (checks 3/4 still never re-run on the three-mesh topology)
- 2026-07-20-localdb-phase1-progress.md: Tasks 10–16 REMAIN → Phase 1 complete, live-gated 8/8, merged c957db52
- 2026-08-06-clusterclient-to-grpc-tracking.md: defect 4 OPEN → fixed by c0475f2d + e54883a8, merged @ b360239e
- 2026-08-06-clusterclient-to-grpc.md.tasks.json: Task 15 pending → completed (Phase 4 gate PASS 8/8 @ 0b194003, record d0ac6fc5)
- 2026-07-22-mesh-phase2-live-gate.md: DriverStatusPanel orphan → wired into /hosts by #546 (9420f8ef)
- 2026-07-21-issue-485-live-gate.md: pointer from the "NOT fixed here" finding to the #486 fix (DriverHostActor.cs:1696-1717 + FetchAndCache mirror)
- 2026-08-05-sql-postgres-oracle.md: chrome/AdminUI gate not deferred — PASSED 2026-08-05, found + fixed the Test-Connect provider gate (4e461951)
- v2-ga-lab-gates-plan.md / v2-release-readiness.md / live-hardware-validation-runbooks.md: FOCAS live-CNC smoke #54 RESOLVED on a real 31i-B 2026-06-25 (cnc_rdparam caveat preserved)
- Mqtt-Test-Fixture.md: STATE/DCMD "not implemented" → shipped + live-gated (#508 2a7d1167, #511 857a10b4)
- 2026-08-01-continuous-historization-live-gate.md: poison-tag FIFO pin "filed, not fixed" → #538 fixed same day (e7fbe977, gate 4bdce910; Capacity default now 500k)
- 2026-05-26-akka-hosting-alignment-plan.md.tasks.json: F8/F9/F10/F13/F14 partial → completed, superseded by the v3 address-space rework (Phase7Composer/ScriptedAlarmActor deleted; evaluators/SDK sinks/security policies production-wired)

Claude-Session: https://claude.ai/code/session_014WNM4vjoVksyyBraTXSZE1
2026-08-07 02:02:54 -04:00

7.6 KiB

MQTT test fixture

Coverage map + gap inventory for the MQTT driver's harness.

TL;DR: the unit suite (581 tests) carries the mapping, indexing, Sparkplug state-machine and failure-classification logic; the live suite (15 tests) proves the parts only a real broker can prove — TLS, real authentication, CA pinning in both directions, retained-message seeding, that a rejected CONNACK is actually surfaced, and the Sparkplug birth/alias/rebirth/death path against a real edge-node simulator.

What the fixture is

eclipse-mosquitto:2.0.22 plus a second container of the same image running publisher/publish.sh (the image ships mosquitto_pub, so no bespoke image is needed), plus — behind the sparkplug compose profile — otopcua-sparkplug-sim, a project-owned C# Sparkplug-B edge-node simulator (tests/Drivers/….Driver.Mqtt.IntegrationTests/SparkplugSimulator/). Compose lives at tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Mqtt.IntegrationTests/Docker/; the deployed stack is /opt/otopcua-mqtt on the shared Docker host.

Two authenticated listeners, no anonymous fallback (allow_anonymous false) — deliberate: an anonymous broker would let a broken auth config pass every test.

Listener Endpoint Env var
TLS + auth 10.100.0.35:8883 MQTT_FIXTURE_ENDPOINT
plaintext + auth 10.100.0.35:1883 MQTT_FIXTURE_PLAIN_ENDPOINT

MqttFixture gates the suite on MQTT_FIXTURE_ENDPOINT / MQTT_FIXTURE_USERNAME / MQTT_FIXTURE_PASSWORD (+ MQTT_FIXTURE_CA_CERT, MQTT_FIXTURE_TOPIC_PREFIX) and skips cleanly when they are absent, so the suite is safe to run offline.

Unlike every other fixture it needs generated material first — ./gen-fixture-material.sh writes the gitignored secrets/ (password file, CA, server cert/key). The broker will not start without it. Bring-up and the CA-fetch recipe are in infra/README.md §3.

Published topics

Topic Shape Retain Why it exists
otopcua/fixture/retained/seed {"value":42.5,…} ✅ published exactly once The only way to prove retained seeding. On a republished topic a retained seed is indistinguishable from a fast live publish
otopcua/fixture/line1/temperature {"value":…,"unit":"C","seq":n} ✅ JSON + JSONPath extraction
otopcua/fixture/line1/counter {"value":n} ✅ Monotonic, for ordering
otopcua/fixture/line1/state RUNNING (bare text) ✅ Non-JSON Raw/Scalar payloads
otopcua/fixture/noise/chatter JSON ❌ Never authored as a tag — a broker carrying unauthored traffic is the normal case and the driver must stay silent about it

What it actually covers

The 7 live tests: TLS+auth connect; connect pinned to the fixture CA; connect pinned to a foreign CA is rejected (the falsifiability control — a pinning test that only ever passes proves nothing); wrong password is rejected and surfaced (the MQTTnet-5 silent-Healthy defect); plain subscribe→value under a RawPath; retained message seeds a value on subscribe; and an unauthored topic stays silent while an authored tag keeps flowing.

The Sparkplug simulator

Group OtOpcUaSim; edge nodes EdgeA and EdgeB; EdgeA additionally publishes device Filler1. Node metrics Temperature (Float), Pressure (Double), Count (Int32), Running (Boolean), Serial (String) plus the mandatory bdSeq and Node Control/Rebirth — the last one deliberately carries a /, so it exercises the "a metric name is not a topic segment" rule. Device metrics: Temperature (Float), FillCount (Int64), Jammed (Boolean). Cadence ~2 s.

It subscribes to spBv1.0/OtOpcUaSim/NCMD/{node} and re-births on a rebirth request, so it exercises the real NCMD round trip rather than simulating it. Births are not retained (per spec), so docker restart otopcua-sparkplug-sim is the way to force a fresh birth on demand:

ssh dohertj2@10.100.0.35 'cd /opt/otopcua-mqtt && docker compose --profile sparkplug up -d'
ssh dohertj2@10.100.0.35 'docker restart otopcua-sparkplug-sim'   # force a birth
ssh dohertj2@10.100.0.35 'docker logs -f otopcua-sparkplug-sim'

Live-gated end-to-end (2026-07-24, P1 milestone): deployed on the docker-dev rig against this fixture, both tags resolved and served changing values through OPC UA, and the AdminUI #-observation picker rendered the real topic tree. Re-gated for P2 (2026-07-25) against the Sparkplug simulator — see Mqtt.md.

What it does NOT cover

1. Sparkplug primary-host STATE, and write-through

The simulator never observes a Host Application STATE, because the driver never publishes one (ActAsPrimaryHost is unimplemented). Nor is there any DCMD/NCMD command coverage: rebirth is the only NCMD the driver ever sends, and IWritable is deferred.

Sparkplug seq-gap injection is unit-proven only — the simulator always publishes a well-formed monotonic seq, so no live test drives the gap → rebirth path end-to-end.

2. Broker-side failure injection

No test kills the broker mid-session, partitions the network, or forces a SUBACK failure against a real broker. Reconnect, backoff and per-topic degradation are unit-proven only.

3. Wildcard tags against a live broker

The by-filter indexing fix that keeps wildcard tags alive across a reconnect is covered by unit tests; no live test authors a +/# tag and bounces the connection.

4. QoS 2 delivery semantics

The fixture publishes at QoS 0/1. QoS 2's exactly-once handshake is never exercised end-to-end.

5. Scale

One publisher, five topics, a ~2 s cadence. Nothing probes the 50 000-node browse cap, the 1 MiB payload cap, or thousands of authored tags.

6. Redundant-pair client-id collision

Found by hand during the P1 gate (a fixed clientId makes pair nodes evict each other), not by a test. No automated coverage asserts that two concurrent drivers coexist.

When to trust the MQTT fixture, when to reach for a real broker

Question Fixture answers it?
Does TLS + auth work, and does bad auth fail loudly? ✅ yes
Is a retained message seeded on subscribe? ✅ yes
Does JSONPath extraction + coercion behave? ✅ yes (unit + live)
Does the driver ignore unauthored traffic? ✅ yes
Does it survive a broker restart / flapping link? ❌ unit-only
Does it hold up at plant tag counts? ❌ no
Does a Sparkplug birth → alias → DATA → death cycle work? ✅ yes (live, vs. the simulator)
Does an NCMD rebirth request actually get answered? ✅ yes (the simulator subscribes + re-births)
Sparkplug seq-gap → rebirth ❌ unit-only (the sim never skips a seq)
Sparkplug primary-host STATE / DCMD writes ✅ both shipped + live-gated (corrected 2026-08-07, truth sweep) — NCMD/DCMD write-through as Gitea #508 (merge 2a7d1167, 2026-07-31, 21/21 live) and primary-host STATE + Last Will as Gitea #511 (merge 857a10b4, 2026-07-31, 5/5 live)

Follow-up candidates

  1. Broker-restart / SUBACK-failure live tests (closes gap 2).
  2. A live wildcard-across-reconnect test (closes gap 3).
  3. A concurrent-two-driver test pinning the client-id hazard (closes gap 6).
  4. A seq-gap injection switch on the simulator, closing the last unit-only Sparkplug leg.

Key fixture / config files

  • tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Mqtt.IntegrationTests/Docker/docker-compose.yml
  • tests/Drivers/…/Docker/mosquitto.conf · Docker/gen-fixture-material.sh · Docker/publisher/publish.sh
  • tests/Drivers/…/MqttFixture.cs · PlainMqttLiveTests.cs
  • infra/README.md §3 — bring-up, endpoints, CA fetch