test(infra): port-NAT opc-plc fixture for the NAT browse tests; wire OPCUA_NAT_ENDPOINT into CI (#813)
Docker/nat/docker-compose.yml (stack /opt/otopcua-opcuaclient-nat on 10.100.0.35, project: lmxopcua label): opc-plc 2.14.10 advertising --ph=otopcua-opc-plc-nat-private.invalid on internal 50000, published on 50010, so every advertised URL is unreachable on host AND port. Deployed; NatPublishedEndpointBrowseTests 3/3 against opc.tcp://10.100.0.35:50010 (and the secure case positive once, with the fixture cert temporarily trusted). CI: integration-fixtures.sh starts a throwaway otopcua-ci-opcplc-nat (50110->50000) and exports OPCUA_NAT_ENDPOINT; v2-e2e.yml points it at the shared stack. infra/README.md, scripts/ci/README.md and the fixture README document it.
This commit is contained in:
@@ -49,6 +49,9 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
E2E_PROJECTS: >-
|
E2E_PROJECTS: >-
|
||||||
Driver.Modbus Driver.S7 Driver.OpcUaClient Driver.OpcUaClient.Browser
|
Driver.Modbus Driver.S7 Driver.OpcUaClient Driver.OpcUaClient.Browser
|
||||||
|
# #813: the port-NAT opc-plc stack (/opt/otopcua-opcuaclient-nat). Not in the probe's required ports —
|
||||||
|
# NatPublishedEndpointBrowseTests TCP-probe it themselves and skip if it is down.
|
||||||
|
OPCUA_NAT_ENDPOINT: opc.tcp://10.100.0.35:50010
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
for p in $E2E_PROJECTS; do
|
for p in $E2E_PROJECTS; do
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
# it gets no slower or flakier. This workflow builds ONLY the five fixture-backed *.IntegrationTests
|
# it gets no slower or flakier. This workflow builds ONLY the five fixture-backed *.IntegrationTests
|
||||||
# projects and runs them against THROWAWAY containers that the job itself creates and removes
|
# projects and runs them against THROWAWAY containers that the job itself creates and removes
|
||||||
# (scripts/ci/integration-fixtures.sh): SQL Server 2022, PostgreSQL 17 (seeded), QuestDB and the public
|
# (scripts/ci/integration-fixtures.sh): SQL Server 2022, PostgreSQL 17 (seeded), QuestDB and the public
|
||||||
# opc-plc image. Nothing here touches the shared 10.100.0.35 host, so CI does not depend on that box.
|
# opc-plc image (twice: plain, and a port-NAT copy behind OPCUA_NAT_ENDPOINT for the #806/#813 NAT browse tests).
|
||||||
|
# Nothing here touches the shared 10.100.0.35 host, so CI does not depend on that box.
|
||||||
#
|
#
|
||||||
# Explicit `docker run` steps rather than a `services:` block: whether the Gitea act_runner honours
|
# Explicit `docker run` steps rather than a `services:` block: whether the Gitea act_runner honours
|
||||||
# `services:` (and makes service ports reachable from the job) is not documented in infra/README.md §10,
|
# `services:` (and makes service ports reachable from the job) is not documented in infra/README.md §10,
|
||||||
@@ -79,7 +80,7 @@ jobs:
|
|||||||
if: failure()
|
if: failure()
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
for c in otopcua-ci-mssql otopcua-ci-pg otopcua-ci-questdb otopcua-ci-opcplc; do
|
for c in otopcua-ci-mssql otopcua-ci-pg otopcua-ci-questdb otopcua-ci-opcplc otopcua-ci-opcplc-nat; do
|
||||||
echo "=== $c ==="; docker logs --tail 40 "$c" 2>&1 || true
|
echo "=== $c ==="; docker logs --tail 40 "$c" 2>&1 || true
|
||||||
done
|
done
|
||||||
- name: stop fixtures
|
- name: stop fixtures
|
||||||
|
|||||||
@@ -276,7 +276,7 @@ central SQL Server.
|
|||||||
|
|
||||||
> **Migrated 2026-04-28**: Docker config + host moved off this dev VM (DESKTOP-6JL3KKO) onto the shared Linux Docker host (`DOCKER`, 10.100.0.35) so the dev VM could shed WSL2/Hyper-V and have its GPU re-attached via ESXi passthrough. Docker Desktop is no longer installed here. All checked-in `appsettings.json` defaults, fixture-class default endpoints, and `e2e-config.sample.json` were rewritten to target `10.100.0.35`. See `docs/v2/dev-environment.md` for the full rewrite (header dated 2026-04-28).
|
> **Migrated 2026-04-28**: Docker config + host moved off this dev VM (DESKTOP-6JL3KKO) onto the shared Linux Docker host (`DOCKER`, 10.100.0.35) so the dev VM could shed WSL2/Hyper-V and have its GPU re-attached via ESXi passthrough. Docker Desktop is no longer installed here. All checked-in `appsettings.json` defaults, fixture-class default endpoints, and `e2e-config.sample.json` were rewritten to target `10.100.0.35`. See `docs/v2/dev-environment.md` for the full rewrite (header dated 2026-04-28).
|
||||||
|
|
||||||
> ⚠️ **The `project: lmxopcua` label is aspirational, not universal.** This note used to claim every fixture compose file carries it. As of 2026-07-24 only the **MQTT** fixture actually does — `grep -rn "labels:" tests --include=*.yml` matches `Driver.Mqtt.IntegrationTests/Docker/docker-compose.yml` and nothing else. So `docker ps --filter label=project=lmxopcua` returns the MQTT fixture alone, not the fleet. Add the label when you touch an older fixture; until then enumerate by container name.
|
> ⚠️ **The `project: lmxopcua` label is aspirational, not universal.** This note used to claim every fixture compose file carries it. As of 2026-07-24 only the **MQTT** fixture actually does (joined 2026-11-10 by the OpcUaClient port-NAT fixture, `Docker/nat/`, #813) — `grep -rn "labels:" tests --include=*.yml` matches `Driver.Mqtt.IntegrationTests/Docker/docker-compose.yml` and nothing else. So `docker ps --filter label=project=lmxopcua` returns the MQTT fixture alone, not the fleet. Add the label when you touch an older fixture; until then enumerate by container name.
|
||||||
|
|
||||||
Docker workloads run on a shared Linux host at **`10.100.0.35`** — not on this VM. Stacks live at `/opt/otopcua-<driver>/` on the host and carry the `project=lmxopcua` label so they're discoverable via `docker ps --filter label=project=lmxopcua`.
|
Docker workloads run on a shared Linux host at **`10.100.0.35`** — not on this VM. Stacks live at `/opt/otopcua-<driver>/` on the host and carry the `project=lmxopcua` label so they're discoverable via `docker ps --filter label=project=lmxopcua`.
|
||||||
|
|
||||||
|
|||||||
+18
-5
@@ -37,7 +37,7 @@ ssh dohertj2@10.100.0.35 'docker ps --filter label=project=lmxopcua --format "{{
|
|||||||
| Stack dir | Purpose |
|
| Stack dir | Purpose |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `/opt/otopcua-mssql` | central SQL (always-on) |
|
| `/opt/otopcua-mssql` | central SQL (always-on) |
|
||||||
| `/opt/otopcua-modbus` · `/opt/otopcua-abcip` · `/opt/otopcua-s7` · `/opt/otopcua-opcuaclient` · `/opt/otopcua-mqtt` · `/opt/otopcua-mtconnect` · `/opt/otopcua-focas` | driver fixtures |
|
| `/opt/otopcua-modbus` · `/opt/otopcua-abcip` · `/opt/otopcua-s7` · `/opt/otopcua-opcuaclient` · `/opt/otopcua-opcuaclient-nat` · `/opt/otopcua-mqtt` · `/opt/otopcua-mtconnect` · `/opt/otopcua-focas` | driver fixtures |
|
||||||
| `~/otopcua-ablegacy` | driver fixture (user-owned) |
|
| `~/otopcua-ablegacy` | driver fixture (user-owned) |
|
||||||
| `~/otopcua-harness` | Host.IntegrationTests real-mode deps (SQL + GLAuth) — see §4 |
|
| `~/otopcua-harness` | Host.IntegrationTests real-mode deps (SQL + GLAuth) — see §4 |
|
||||||
|
|
||||||
@@ -68,13 +68,14 @@ when it isn't running. Bring one up, `dotnet test`, tear down. Repo compose live
|
|||||||
| AB Legacy (PCCC) | `otopcua-ab-server` (PCCC mode) | `10.100.0.35:44818` | `AB_LEGACY_COMPOSE_PROFILE=<…> docker compose up -d` (stack `~/otopcua-ablegacy`) |
|
| AB Legacy (PCCC) | `otopcua-ab-server` (PCCC mode) | `10.100.0.35:44818` | `AB_LEGACY_COMPOSE_PROFILE=<…> docker compose up -d` (stack `~/otopcua-ablegacy`) |
|
||||||
| S7 | `otopcua-python-snap7` | `10.100.0.35:1102` | `docker compose --profile s7_1500 up -d` |
|
| S7 | `otopcua-python-snap7` | `10.100.0.35:1102` | `docker compose --profile s7_1500 up -d` |
|
||||||
| OpcUaClient | `mcr.microsoft.com/iotedge/opc-plc:2.14.10` | `opc.tcp://10.100.0.35:50000` | `docker compose up -d` |
|
| OpcUaClient | `mcr.microsoft.com/iotedge/opc-plc:2.14.10` | `opc.tcp://10.100.0.35:50000` | `docker compose up -d` |
|
||||||
|
| OpcUaClient — port NAT (#813) | same opc-plc image; advertises `opc.tcp://otopcua-opc-plc-nat-private.invalid:50000` | `opc.tcp://10.100.0.35:50010` (`OPCUA_NAT_ENDPOINT`, no default) | `docker compose up -d` (stack `/opt/otopcua-opcuaclient-nat`; rsync the repo's OpcUaClient `Docker/nat/` dir first) |
|
||||||
| FOCAS | `otopcua-focas-fixture` (vendored focas-mock, built on the host) | `10.100.0.35:8193` (published) | `docker compose up -d --build` (stack `/opt/otopcua-focas`; rsync the repo's FOCAS `Docker/` dir first) |
|
| FOCAS | `otopcua-focas-fixture` (vendored focas-mock, built on the host) | `10.100.0.35:8193` (published) | `docker compose up -d --build` (stack `/opt/otopcua-focas`; rsync the repo's FOCAS `Docker/` dir first) |
|
||||||
| MQTT | `eclipse-mosquitto:2.0.22` (+ a publisher sidecar) | `10.100.0.35:8883` TLS+auth · `:1883` plaintext+auth | `MQTT_FIXTURE_USERNAME=otopcua MQTT_FIXTURE_PASSWORD=<pw> docker compose up -d` (stack `/opt/otopcua-mqtt`) |
|
| MQTT | `eclipse-mosquitto:2.0.22` (+ a publisher sidecar) | `10.100.0.35:8883` TLS+auth · `:1883` plaintext+auth | `MQTT_FIXTURE_USERNAME=otopcua MQTT_FIXTURE_PASSWORD=<pw> docker compose up -d` (stack `/opt/otopcua-mqtt`) |
|
||||||
| MQTT — Sparkplug B | `otopcua-sparkplug-sim` (project-owned C# edge-node simulator) | same broker; group **`OtOpcUaSim`**, edge nodes **`EdgeA`** / **`EdgeB`**, `EdgeA` device **`Filler1`**, ~2 s cadence | `docker compose --profile sparkplug up -d` (same stack). Answers rebirth NCMDs; restart it to force a fresh birth |
|
| MQTT — Sparkplug B | `otopcua-sparkplug-sim` (project-owned C# edge-node simulator) | same broker; group **`OtOpcUaSim`**, edge nodes **`EdgeA`** / **`EdgeB`**, `EdgeA` device **`Filler1`**, ~2 s cadence | `docker compose --profile sparkplug up -d` (same stack). Answers rebirth NCMDs; restart it to force a fresh birth |
|
||||||
| MTConnect | `mtconnect/agent:2.7.0.12` + `python:3.13-alpine` adapter | `http://10.100.0.35:5000` | `docker compose up -d --wait` |
|
| MTConnect | `mtconnect/agent:2.7.0.12` + `python:3.13-alpine` adapter | `http://10.100.0.35:5000` | `docker compose up -d --wait` |
|
||||||
|
|
||||||
**Endpoint overrides** point a suite at a real PLC instead of the sim:
|
**Endpoint overrides** point a suite at a real PLC instead of the sim:
|
||||||
`MODBUS_SIM_ENDPOINT` · `AB_SERVER_ENDPOINT` · `S7_SIM_ENDPOINT` · `OPCUA_SIM_ENDPOINT` ·
|
`MODBUS_SIM_ENDPOINT` · `AB_SERVER_ENDPOINT` · `S7_SIM_ENDPOINT` · `OPCUA_SIM_ENDPOINT` · `OPCUA_NAT_ENDPOINT` (opt-in, no default) ·
|
||||||
`MQTT_FIXTURE_ENDPOINT` / `MQTT_FIXTURE_PLAIN_ENDPOINT` · `MTCONNECT_AGENT_ENDPOINT` · `OTOPCUA_FOCAS_SIM_ENDPOINT`.
|
`MQTT_FIXTURE_ENDPOINT` / `MQTT_FIXTURE_PLAIN_ENDPOINT` · `MTCONNECT_AGENT_ENDPOINT` · `OTOPCUA_FOCAS_SIM_ENDPOINT`.
|
||||||
|
|
||||||
> **`otopcua-focas-sim` on the host is NOT ours (#651).** It is ignitionoee's mock (created by that
|
> **`otopcua-focas-sim` on the host is NOT ours (#651).** It is ignitionoee's mock (created by that
|
||||||
@@ -91,7 +92,17 @@ when it isn't running. Bring one up, `dotnet test`, tear down. Repo compose live
|
|||||||
> CA + server cert/key, gitignored) and `MQTT_FIXTURE_USERNAME`/`MQTT_FIXTURE_PASSWORD` are exported
|
> CA + server cert/key, gitignored) and `MQTT_FIXTURE_USERNAME`/`MQTT_FIXTURE_PASSWORD` are exported
|
||||||
> at `docker compose up`. The live suite additionally wants `MQTT_FIXTURE_CA_CERT` pointing at a local
|
> at `docker compose up`. The live suite additionally wants `MQTT_FIXTURE_CA_CERT` pointing at a local
|
||||||
> copy of the CA: `scp dohertj2@10.100.0.35:/opt/otopcua-mqtt/secrets/ca.crt /tmp/mqtt-fixture-ca.crt`.
|
> copy of the CA: `scp dohertj2@10.100.0.35:/opt/otopcua-mqtt/secrets/ca.crt /tmp/mqtt-fixture-ca.crt`.
|
||||||
> It is also the only fixture whose services carry the `project: lmxopcua` label (see CLAUDE.md).
|
> It and the OpcUaClient NAT fixture are the only ones whose compose files carry the `project: lmxopcua` label (see CLAUDE.md).
|
||||||
|
|
||||||
|
> **The OpcUaClient NAT fixture exists for what it ADVERTISES (#806/#813).** `--ph` sets a private `.invalid`
|
||||||
|
> host (RFC 6761: never resolves, so no DNS search domain can make it reachable) and the published port 50010 differs
|
||||||
|
> from the internal 50000, so every GetEndpoints URL is unreachable on both host and port. Only
|
||||||
|
> `NatPublishedEndpointBrowseTests` (Browser.IntegrationTests, `Fixture=opc-plc-nat`) use it, and each asserts that
|
||||||
|
> premise before testing the shared `OpcUaEndpointUrlRewriter` (configured host:port win, advertised path kept):
|
||||||
|
> `OPCUA_NAT_ENDPOINT=opc.tcp://10.100.0.35:50010 dotnet test tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser.IntegrationTests --filter Fixture=opc-plc-nat`.
|
||||||
|
> A separate stack so it never touches `otopcua-opc-plc`; it carries the `project: lmxopcua` label. The secure case
|
||||||
|
> turns positive with `OPCUA_NAT_SERVER_CERT_TRUSTED=1` once the container's cert is in the browse PKI
|
||||||
|
> (`docs/drivers/OpcUaClient.md` §"Endpoint address"). CI runs its own throwaway copy (`otopcua-ci-opcplc-nat`, §10).
|
||||||
|
|
||||||
> **MTConnect is a TWO-service stack.** The `mtconnect/agent` image ships no simulator, so a lone
|
> **MTConnect is a TWO-service stack.** The `mtconnect/agent` image ships no simulator, so a lone
|
||||||
> Agent answers `/probe` and reports every observation `UNAVAILABLE` forever. The `adapter` service
|
> Agent answers `/probe` and reports every observation `UNAVAILABLE` forever. The `adapter` service
|
||||||
@@ -261,6 +272,8 @@ Things that are not obvious (#606):
|
|||||||
|
|
||||||
`v2-integration.yml` runs the fixture-backed Sql / Historian (SQL Server, QuestDB) / OpcUaClient integration
|
`v2-integration.yml` runs the fixture-backed Sql / Historian (SQL Server, QuestDB) / OpcUaClient integration
|
||||||
projects against throwaway containers the job starts itself (`scripts/ci/integration-fixtures.sh`; not the
|
projects against throwaway containers the job starts itself (`scripts/ci/integration-fixtures.sh`; not the
|
||||||
shared 10.100.0.35 host), floor `MIN_EXECUTED=440`. `v2-e2e.yml` is manual/weekly and skips cleanly unless
|
shared 10.100.0.35 host), floor `MIN_EXECUTED=440`; that includes a port-NAT opc-plc (`otopcua-ci-opcplc-nat`,
|
||||||
10.100.0.35 is reachable from the runner. Each has its own concurrency group; see
|
host port 50110, exported as `OPCUA_NAT_ENDPOINT`) for the NAT browse tests (#813). `v2-e2e.yml` is manual/weekly and
|
||||||
|
skips cleanly unless 10.100.0.35 is reachable from the runner; it sets `OPCUA_NAT_ENDPOINT=opc.tcp://10.100.0.35:50010`
|
||||||
|
(the shared NAT stack, not a probe-required port — its tests skip if it is down). Each has its own concurrency group; see
|
||||||
[`scripts/ci/README.md`](../scripts/ci/README.md).
|
[`scripts/ci/README.md`](../scripts/ci/README.md).
|
||||||
|
|||||||
@@ -67,7 +67,8 @@ Neither touches `v2-ci.yml`; each has its own concurrency group (`lmxopcua-v2-in
|
|||||||
|
|
||||||
- **`v2-integration.yml`** (push to `master`, path-filtered PRs, manual; `timeout-minutes: 60`).
|
- **`v2-integration.yml`** (push to `master`, path-filtered PRs, manual; `timeout-minutes: 60`).
|
||||||
`integration-fixtures.sh up` starts throwaway SQL Server 2022, PostgreSQL 17 (seeded from `seed-pg.sql`),
|
`integration-fixtures.sh up` starts throwaway SQL Server 2022, PostgreSQL 17 (seeded from `seed-pg.sql`),
|
||||||
QuestDB and opc-plc (`otopcua-ci-*`, non-default host ports), exports the env vars the fixtures read, and
|
QuestDB and opc-plc — plus a port-NAT opc-plc (`otopcua-ci-opcplc-nat`, host 50110 → 50000, private `.invalid`
|
||||||
|
advertised host, `OPCUA_NAT_ENDPOINT`, #813) — (`otopcua-ci-*`, non-default host ports), exports the env vars the fixtures read, and
|
||||||
`down` removes them. It uses plain `docker run`, not `services:` (act_runner support is undocumented and
|
`down` removes them. It uses plain `docker run`, not `services:` (act_runner support is undocumented and
|
||||||
Postgres needs a seed step); the only runner requirement is a docker daemon. Runs the Sql, Historian.SqlServer,
|
Postgres needs a seed step); the only runner requirement is a docker daemon. Runs the Sql, Historian.SqlServer,
|
||||||
Historian.QuestDb, OpcUaClient and OpcUaClient.Browser `*.IntegrationTests` with `MIN_EXECUTED=440`.
|
Historian.QuestDb, OpcUaClient and OpcUaClient.Browser `*.IntegrationTests` with `MIN_EXECUTED=440`.
|
||||||
@@ -77,5 +78,6 @@ Neither touches `v2-ci.yml`; each has its own concurrency group (`lmxopcua-v2-in
|
|||||||
- **`v2-e2e.yml`** (manual + weekly cron only; `timeout-minutes: 90`). `probe-e2e-fixtures.sh` TCP-probes
|
- **`v2-e2e.yml`** (manual + weekly cron only; `timeout-minutes: 90`). `probe-e2e-fixtures.sh` TCP-probes
|
||||||
10.100.0.35 ports 5020/1102/50000; if any is closed every later step is skipped and the run is green with a
|
10.100.0.35 ports 5020/1102/50000; if any is closed every later step is skipped and the run is green with a
|
||||||
notice. Otherwise runs the Modbus, S7, OpcUaClient and OpcUaClient.Browser integration projects against the
|
notice. Otherwise runs the Modbus, S7, OpcUaClient and OpcUaClient.Browser integration projects against the
|
||||||
shared fleet with `MIN_EXECUTED=35` (local: 42 executed / 38 skipped; Modbus skips the profiles that are not
|
shared fleet with `MIN_EXECUTED=35` (`OPCUA_NAT_ENDPOINT` points at the shared NAT stack on :50010; its 3 tests
|
||||||
|
skip if it is down, so it is not a probe-required port) (local: 42 executed / 38 skipped; Modbus skips the profiles that are not
|
||||||
up). No test carries `Category=E2E` today, so the zero-match guard is this executed floor.
|
up). No test carries `Category=E2E` today, so the zero-match guard is this executed floor.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
#
|
#
|
||||||
# integration-fixtures.sh — throwaway, self-contained fixtures for the v2-integration workflow (#757).
|
# integration-fixtures.sh — throwaway, self-contained fixtures for the v2-integration workflow (#757).
|
||||||
#
|
#
|
||||||
# up start SQL Server, PostgreSQL (seeded), QuestDB and opc-plc as containers named
|
# up start SQL Server, PostgreSQL (seeded), QuestDB, opc-plc and a port-NAT opc-plc as containers named
|
||||||
# otopcua-ci-*, wait until each is usable, and print the env exports the
|
# otopcua-ci-*, wait until each is usable, and print the env exports the
|
||||||
# *.IntegrationTests fixtures read (also appended to $GITHUB_ENV when set).
|
# *.IntegrationTests fixtures read (also appended to $GITHUB_ENV when set).
|
||||||
# down remove those containers (idempotent; touches nothing else).
|
# down remove those containers (idempotent; touches nothing else).
|
||||||
@@ -21,10 +21,13 @@ PG_PORT="${CI_PG_PORT:-15440}"
|
|||||||
QDB_PORT="${CI_QDB_PORT:-18812}"
|
QDB_PORT="${CI_QDB_PORT:-18812}"
|
||||||
QDB_HTTP_PORT="${CI_QDB_HTTP_PORT:-19000}"
|
QDB_HTTP_PORT="${CI_QDB_HTTP_PORT:-19000}"
|
||||||
OPC_PORT="${CI_OPC_PORT:-50100}"
|
OPC_PORT="${CI_OPC_PORT:-50100}"
|
||||||
|
# #813 NAT opc-plc: published on OPC_NAT_PORT, listening on 50000 inside, advertising a private `.invalid`
|
||||||
|
# host — so its GetEndpoints URLs are unreachable on BOTH host and port (NatPublishedEndpointBrowseTests).
|
||||||
|
OPC_NAT_PORT="${CI_OPC_NAT_PORT:-50110}"
|
||||||
# Throwaway-container-only credentials (never used against anything else).
|
# Throwaway-container-only credentials (never used against anything else).
|
||||||
SA_PASSWORD="${CI_SA_PASSWORD:-Ci_throwaway_Pw1}"
|
SA_PASSWORD="${CI_SA_PASSWORD:-Ci_throwaway_Pw1}"
|
||||||
PG_PASSWORD="${CI_PG_PASSWORD:-Ci_throwaway_pg1}"
|
PG_PASSWORD="${CI_PG_PASSWORD:-Ci_throwaway_pg1}"
|
||||||
NAMES=(otopcua-ci-mssql otopcua-ci-pg otopcua-ci-questdb otopcua-ci-opcplc)
|
NAMES=(otopcua-ci-mssql otopcua-ci-pg otopcua-ci-questdb otopcua-ci-opcplc otopcua-ci-opcplc-nat)
|
||||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
|
||||||
fixture_host() {
|
fixture_host() {
|
||||||
@@ -59,6 +62,9 @@ up() {
|
|||||||
questdb/questdb:10.0.1 >/dev/null
|
questdb/questdb:10.0.1 >/dev/null
|
||||||
docker run -d --name otopcua-ci-opcplc -p "$OPC_PORT:$OPC_PORT" \
|
docker run -d --name otopcua-ci-opcplc -p "$OPC_PORT:$OPC_PORT" \
|
||||||
mcr.microsoft.com/iotedge/opc-plc:2.14.10 --pn="$OPC_PORT" --ut --aa --alm >/dev/null
|
mcr.microsoft.com/iotedge/opc-plc:2.14.10 --pn="$OPC_PORT" --ut --aa --alm >/dev/null
|
||||||
|
# Mirrors tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.IntegrationTests/Docker/nat/docker-compose.yml.
|
||||||
|
docker run -d --name otopcua-ci-opcplc-nat --hostname otopcua-ci-opcplc-nat-private -p "$OPC_NAT_PORT:50000" \
|
||||||
|
mcr.microsoft.com/iotedge/opc-plc:2.14.10 --pn=50000 --ph=otopcua-ci-opcplc-nat-private.invalid --ut --aa >/dev/null
|
||||||
|
|
||||||
wait_for "postgres" 120 docker exec otopcua-ci-pg pg_isready -U postgres -d sqlpollfixture
|
wait_for "postgres" 120 docker exec otopcua-ci-pg pg_isready -U postgres -d sqlpollfixture
|
||||||
# pg_isready can answer during initdb's temp server; require a real query on the final server.
|
# pg_isready can answer during initdb's temp server; require a real query on the final server.
|
||||||
@@ -73,6 +79,7 @@ up() {
|
|||||||
wait_for "sql server" 300 docker exec otopcua-ci-mssql "$sqlcmd" -C -S localhost -U sa -P "$SA_PASSWORD" -Q 'SELECT 1'
|
wait_for "sql server" 300 docker exec otopcua-ci-mssql "$sqlcmd" -C -S localhost -U sa -P "$SA_PASSWORD" -Q 'SELECT 1'
|
||||||
wait_for "questdb" 120 curl -fsS "http://$(fixture_host):$QDB_HTTP_PORT/exec?query=select%201"
|
wait_for "questdb" 120 curl -fsS "http://$(fixture_host):$QDB_HTTP_PORT/exec?query=select%201"
|
||||||
wait_for "opc-plc" 120 bash -c "exec 3<>/dev/tcp/$(fixture_host)/$OPC_PORT"
|
wait_for "opc-plc" 120 bash -c "exec 3<>/dev/tcp/$(fixture_host)/$OPC_PORT"
|
||||||
|
wait_for "opc-plc (NAT)" 120 bash -c "exec 3<>/dev/tcp/$(fixture_host)/$OPC_NAT_PORT"
|
||||||
|
|
||||||
local h; h="$(fixture_host)"
|
local h; h="$(fixture_host)"
|
||||||
local -a exports=(
|
local -a exports=(
|
||||||
@@ -84,6 +91,7 @@ up() {
|
|||||||
"HISTORIAN_SQL_TEST_CONNECTION=Server=$h,$SQL_PORT;User Id=sa;Password=$SA_PASSWORD;TrustServerCertificate=True;Encrypt=False"
|
"HISTORIAN_SQL_TEST_CONNECTION=Server=$h,$SQL_PORT;User Id=sa;Password=$SA_PASSWORD;TrustServerCertificate=True;Encrypt=False"
|
||||||
"HISTORIAN_QUESTDB_TEST_CONNECTION=Host=$h;Port=$QDB_PORT;Username=admin;Password=quest;Database=qdb;ServerCompatibilityMode=NoTypeLoading"
|
"HISTORIAN_QUESTDB_TEST_CONNECTION=Host=$h;Port=$QDB_PORT;Username=admin;Password=quest;Database=qdb;ServerCompatibilityMode=NoTypeLoading"
|
||||||
"OPCUA_SIM_ENDPOINT=opc.tcp://$h:$OPC_PORT"
|
"OPCUA_SIM_ENDPOINT=opc.tcp://$h:$OPC_PORT"
|
||||||
|
"OPCUA_NAT_ENDPOINT=opc.tcp://$h:$OPC_NAT_PORT"
|
||||||
)
|
)
|
||||||
for e in "${exports[@]}"; do
|
for e in "${exports[@]}"; do
|
||||||
echo "export \"$e\""
|
echo "export \"$e\""
|
||||||
|
|||||||
+5
-1
@@ -14,8 +14,12 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser.IntegrationTests;
|
|||||||
/// whose internal port differs from the published one, so its GetEndpoints advertise an address this machine
|
/// whose internal port differs from the published one, so its GetEndpoints advertise an address this machine
|
||||||
/// cannot reach:
|
/// cannot reach:
|
||||||
/// <code>
|
/// <code>
|
||||||
|
/// # shared docker host (#813): compose in Driver.OpcUaClient.IntegrationTests/Docker/nat/
|
||||||
|
/// ssh dohertj2@10.100.0.35 'cd /opt/otopcua-opcuaclient-nat && docker compose up -d'
|
||||||
|
/// OPCUA_NAT_ENDPOINT=opc.tcp://10.100.0.35:50010 dotnet test --filter Fixture=opc-plc-nat
|
||||||
|
/// # or locally / in CI (scripts/ci/integration-fixtures.sh starts the same shape as otopcua-ci-opcplc-nat):
|
||||||
/// docker run -d --rm --name otopcua-806-nat --hostname otopcua-806-nat-parity -p 32781:50000 \
|
/// docker run -d --rm --name otopcua-806-nat --hostname otopcua-806-nat-parity -p 32781:50000 \
|
||||||
/// mcr.microsoft.com/iotedge/opc-plc:2.14.10 --pn=50000 --autoaccept --ut
|
/// mcr.microsoft.com/iotedge/opc-plc:2.14.10 --pn=50000 --ph=otopcua-806-nat-parity.invalid --autoaccept --ut
|
||||||
/// OPCUA_NAT_ENDPOINT=opc.tcp://127.0.0.1:32781 dotnet test --filter Fixture=opc-plc-nat
|
/// OPCUA_NAT_ENDPOINT=opc.tcp://127.0.0.1:32781 dotnet test --filter Fixture=opc-plc-nat
|
||||||
/// </code>
|
/// </code>
|
||||||
/// Skipped unless <c>OPCUA_NAT_ENDPOINT</c> is set and accepts TCP. Every test first proves the premise (the
|
/// Skipped unless <c>OPCUA_NAT_ENDPOINT</c> is set and accepts TCP. Every test first proves the premise (the
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ here since opc-plc ships pre-containerized.
|
|||||||
| File | Purpose |
|
| File | Purpose |
|
||||||
|---|---|
|
|---|---|
|
||||||
| [`docker-compose.yml`](docker-compose.yml) | Service definition for `otopcua-opc-plc` — image pin, port map, command flags. |
|
| [`docker-compose.yml`](docker-compose.yml) | Service definition for `otopcua-opc-plc` — image pin, port map, command flags. |
|
||||||
|
| [`nat/docker-compose.yml`](nat/docker-compose.yml) | `otopcua-opc-plc-nat` — a SEPARATE stack (`/opt/otopcua-opcuaclient-nat` on the shared host): opc-plc advertising a private `.invalid` host on internal port 50000, published on 50010. For `NatPublishedEndpointBrowseTests` (`OPCUA_NAT_ENDPOINT=opc.tcp://10.100.0.35:50010`, #806/#813); see the compose file's header. |
|
||||||
| (this file) | How to run it. |
|
| (this file) | How to run it. |
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
# opc-plc behind a port NAT — the #806/#813 fixture for NatPublishedEndpointBrowseTests
|
||||||
|
# (tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser.IntegrationTests, Fixture=opc-plc-nat).
|
||||||
|
#
|
||||||
|
# The point of this stack is what the server ADVERTISES, not what it simulates. opc-plc builds its
|
||||||
|
# GetEndpoints EndpointUrl from its PLC hostname (--ph; default = the container's short hostname) + its
|
||||||
|
# listen port, so with
|
||||||
|
# * a private hostname that cannot resolve from any client (named explicitly via --ph: `.invalid` is
|
||||||
|
# reserved by RFC 6761 and guaranteed never to resolve, so no resolver search domain or wildcard can make
|
||||||
|
# the premise pass; the container `hostname:` alone would advertise the bare short name), and
|
||||||
|
# * a published host port (50010) different from the internal listen port (50000),
|
||||||
|
# every advertised endpoint is opc.tcp://otopcua-opc-plc-nat-private.invalid:50000/ — unreachable on BOTH host
|
||||||
|
# and port. A client that follows the advertised URL fails DNS; one that keeps the operator-configured
|
||||||
|
# host:port (OpcUaEndpointUrlRewriter) reaches the server. The tests assert that premise first.
|
||||||
|
#
|
||||||
|
# A SEPARATE stack from ../docker-compose.yml (deployed to /opt/otopcua-opcuaclient-nat, not
|
||||||
|
# /opt/otopcua-opcuaclient) so bringing it up/down never touches the plain otopcua-opc-plc fixture.
|
||||||
|
#
|
||||||
|
# rsync -av tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.IntegrationTests/Docker/nat/ \
|
||||||
|
# dohertj2@10.100.0.35:/opt/otopcua-opcuaclient-nat/
|
||||||
|
# ssh dohertj2@10.100.0.35 'cd /opt/otopcua-opcuaclient-nat && docker compose up -d'
|
||||||
|
# OPCUA_NAT_ENDPOINT=opc.tcp://10.100.0.35:50010 dotnet test \
|
||||||
|
# tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser.IntegrationTests --filter Fixture=opc-plc-nat
|
||||||
|
services:
|
||||||
|
opc-plc-nat:
|
||||||
|
image: mcr.microsoft.com/iotedge/opc-plc:2.14.10
|
||||||
|
container_name: otopcua-opc-plc-nat
|
||||||
|
hostname: otopcua-opc-plc-nat-private
|
||||||
|
restart: unless-stopped
|
||||||
|
labels:
|
||||||
|
project: lmxopcua
|
||||||
|
ports:
|
||||||
|
# published 50010 -> internal 50000: the port half of the NAT. Do NOT make these equal.
|
||||||
|
- "50010:50000"
|
||||||
|
command:
|
||||||
|
- "--pn=50000"
|
||||||
|
# --ph: the host part of every advertised EndpointUrl (and the server cert's DNS name).
|
||||||
|
- "--ph=otopcua-opc-plc-nat-private.invalid"
|
||||||
|
# --ut: also advertise SecurityPolicy=None (the secure policies stay advertised; the secure test uses them).
|
||||||
|
- "--ut"
|
||||||
|
# --aa: accept the browser's self-signed client cert. Trust in the OTHER direction (the browser trusting
|
||||||
|
# this server's cert) is deliberately not automatic — that is what the secure test pins.
|
||||||
|
- "--aa"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "netstat -an | grep -q ':50000.*LISTEN' || exit 1"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 2s
|
||||||
|
retries: 10
|
||||||
|
start_period: 10s
|
||||||
Reference in New Issue
Block a user