🚧 WIP
This commit is contained in:
+84
-59
@@ -7,7 +7,7 @@
|
||||
| Project | `wago-modbustcp-simulator-py` |
|
||||
| Version | 1.4 (Draft for review) |
|
||||
| Date | 2026-10-03 |
|
||||
| Status | Framework phase; module addressing implemented (FR-4.3.11 v1.5 per-type numbering); service/container deployment implemented (§12); plain-text request tracing implemented (NFR-6.4); clean CLI error handling implemented (NFR-6.5 CLI part); web UI §10 in implementation (M1–M5a done: auth, overview, manipulation, diagnostics, configuration, channel names, import/export, split overview; M6 scenario view open); long-duration simulation (§8) implemented 2026-10-04 incl. PlantEnv (FR-8.2.14–16), datasets, degradation, invariants, snapshots; bench mode (§9), runtime device configuration (§11 — coupler/module/channel-name subset covered by web UI M5/M5a), multi-vendor device profiles (§13) and additional fieldbus protocols PROFINET/COMLI (§14), and host network settings (§15) requested 2026-10-06 |
|
||||
| Status | Framework phase; module addressing implemented (FR-4.3.11 v1.5 per-type numbering); service/container deployment implemented (§12); plain-text request tracing implemented (NFR-6.4); clean CLI error handling implemented (NFR-6.5 CLI part); web UI §10 in implementation (M1–M5a done: auth, overview, manipulation, diagnostics, configuration, channel names, import/export, split overview; M6 scenario view open); long-duration simulation (§8) implemented 2026-10-04 incl. PlantEnv (FR-8.2.14–16), datasets, degradation, invariants, snapshots; host network settings (§15) implemented 2026-10-06 (v1 ops; FR-15.2.9 DIP integration deferred); bench mode (§9), runtime device configuration (§11 — coupler/module/channel-name subset covered by web UI M5/M5a), multi-vendor device profiles (§13) and additional fieldbus protocols PROFINET/COMLI (§14) requested |
|
||||
| Target device simulated | WAGO 750-362 / 750-362/0000-0001 FC Modbus TCP (G4) bus coupler |
|
||||
| Reference documents | `docs/m07500362_xxxxxxxx_0en.pdf` (750-362 Modbus TCP manual), Modbus TCP specification (modbus.org) |
|
||||
|
||||
@@ -1597,13 +1597,16 @@ the same process-image core.
|
||||
|
||||
## 15. Feature Request — Host Network Settings (Docker Bench Host)
|
||||
|
||||
> **Status: requested 2026-10-06, not yet planned into a roadmap
|
||||
> phase.** A settings page in the web UI that views and manipulates
|
||||
> the **host OS's IP addresses**, exposed only when an explicit
|
||||
> environment flag is set **and** the simulator runs inside Docker.
|
||||
> This section captures the requirements and the enabling
|
||||
> architecture (a whitelisted host-side agent); it does not commit to
|
||||
> a schedule.
|
||||
> **Status: implemented 2026-10-06 (v1 core ops; link up/down stays
|
||||
> v2 per FR-15.2.4).** A settings page in the web UI that views and
|
||||
> manipulates the **host OS's IP addresses**, exposed only when an
|
||||
> explicit environment flag is set **and** the simulator runs inside
|
||||
> Docker. Implemented as specified: double-gated page (404 when off —
|
||||
> no dormant surface), whitelisted host-side agent over a mounted
|
||||
> Unix socket, runtime-only changes with auto-snapshot/undo, and the
|
||||
> operator-tagged audit trail. FR-15.2.9 (DIP dedicated-IP routing
|
||||
> through the agent) remains deferred until §11 ships the DIP model.
|
||||
> 58 dedicated tests in `tests/test_hostnet.py`; full suite 339 green.
|
||||
|
||||
### 15.1 Motivation
|
||||
|
||||
@@ -1677,21 +1680,25 @@ first-class FAT scenarios.
|
||||
403. Changing host addresses is a privileged operation by design —
|
||||
the flag must never be set on a network the operator does not
|
||||
control.
|
||||
- FR-15.2.7: **Host-side agent** (binding architecture decision). A
|
||||
container cannot change host addresses, and the simulator container
|
||||
itself shall not run privileged (§12 non-root, least-privilege
|
||||
deployment). Instead a small companion service —
|
||||
`wago-sim-hostnet-agent` — runs **on the host** (systemd unit in
|
||||
`deploy/`, root or `CAP_NET_ADMIN`), listening on a Unix domain
|
||||
socket that the compose file mounts into the container read-write.
|
||||
The agent's protocol is a narrow whitelist of operations (health,
|
||||
list, snapshot, address add/remove/change, restore) over JSON
|
||||
lines; it never executes a shell with operator input — on Linux v1
|
||||
it uses the netlink API (`pyroute2`, with the `ip` tool as a
|
||||
fallback). *Rejected alternative, documented why*: running the
|
||||
simulator container itself privileged with host namespaces — it
|
||||
forfeits the non-root deployment and couples host control to the
|
||||
simulator's own lifecycle and restarts.
|
||||
- FR-15.2.7: **Host-side agent** (binding architecture decision,
|
||||
implemented 2026-10-06). A container cannot change host addresses,
|
||||
and the simulator container itself shall not run privileged (§12
|
||||
non-root, least-privilege deployment). Instead a small companion
|
||||
service — `wago-sim hostnet-agent` — runs **on the host** (systemd
|
||||
unit `deploy/wago-sim-hostnet-agent.service`, root or
|
||||
`CAP_NET_ADMIN`), listening on a Unix domain socket
|
||||
(`deploy/hostnet-agent.yaml` config; default
|
||||
`/run/wago-sim/hostnet.sock`) that the compose file mounts into the
|
||||
container read-write. The agent's protocol is a narrow whitelist of
|
||||
operations (health, list, snapshot, address add/remove/change,
|
||||
restore, audit) over JSON lines; it never executes a shell with
|
||||
operator input — on Linux v1 reads use netlink (`pyroute2` when
|
||||
installed) with `ip -o` output parsing as fallback, and writes go
|
||||
through the `ip` CLI (one uniform mutation path). *Rejected
|
||||
alternative, documented why*: running the simulator container
|
||||
itself privileged with host namespaces — it forfeits the non-root
|
||||
deployment and couples host control to the simulator's own
|
||||
lifecycle and restarts.
|
||||
- FR-15.2.8: **Degraded states never break the UI.** Flag set but the
|
||||
agent socket absent/unreachable: the page shows a clear "host
|
||||
control unavailable" banner and mutation routes answer 503 with a
|
||||
@@ -1706,29 +1713,40 @@ first-class FAT scenarios.
|
||||
`network_mode: host` the simulator rebinds directly per FR-11.3.3;
|
||||
either way the agent supplies the address on the host.
|
||||
|
||||
### 15.3 Architecture sketch (planned)
|
||||
### 15.3 Architecture sketch (as-built 2026-10-06)
|
||||
|
||||
```
|
||||
bench host (Linux)
|
||||
├── wago-sim hostnet-agent --config /etc/wago-sim/hostnet.yaml
|
||||
│ (systemd unit: deploy/wago-sim-hostnet-agent.service;
|
||||
│ root or CAP_NET_ADMIN — it owns the interface whitelist)
|
||||
│ config: socket: /run/wago-sim/hostnet.sock, whitelist: [...]
|
||||
│ (deploy/wago-sim-hostnet-agent.service; root or CAP_NET_ADMIN —
|
||||
│ it owns the interface whitelist; audit → journald + optional file)
|
||||
│ config (deploy/hostnet-agent.yaml): socket, whitelist, backend,
|
||||
│ audit_path; refuses to start with an empty whitelist
|
||||
└── docker compose
|
||||
├── mounts /run/wago-sim:/run/wago-sim (agent socket in)
|
||||
└── wago-sim serve (page + /api/hostnet/* routes,
|
||||
gated by FR-15.2.1 flag + FR-15.2.2 docker detection)
|
||||
├── mounts /run/wago-sim:/run/wago-sim (agent socket in, 0660)
|
||||
├── WAGO_SIM_IN_DOCKER=1 (always) — the docker gate's marker
|
||||
├── WAGO_SIM_HOSTNET=1 (opt-in) — the flag gate
|
||||
└── wago-sim serve
|
||||
└── webui: page + /api/hostnet/* routes, mounted only
|
||||
when hostnet_enabled() passes at build time
|
||||
(src/wago_sim/hostnet.py: HostNetAgent / HostNetClient /
|
||||
HostNetService; Iproute2Backend = pyroute2 reads with
|
||||
`ip`-CLI writes, `ip -o` parse fallback; FakeBackend
|
||||
for tests and rehearsal)
|
||||
```
|
||||
|
||||
Web API, mounted only under the FR-15.2.1/2 gates:
|
||||
Web API, mounted only under the FR-15.2.1/2 gates (404 otherwise):
|
||||
|
||||
| Method/Route | Purpose | FR |
|
||||
|---|---|---|
|
||||
| `GET /api/hostnet` | availability (flag, docker, agent health) + interface/address list + whitelist | 15.2.3 |
|
||||
| `POST /api/hostnet/interfaces/{name}/addresses` | add an address (address, prefix length) | 15.2.4 |
|
||||
| `GET /api/hostnet` | availability (gates + agent health) + interface/address list + whitelist; an unreachable agent answers `available:false`, never 5xx | 15.2.3, 15.2.8 |
|
||||
| `POST /api/hostnet/interfaces/{name}/addresses` | add an address (address, prefix) | 15.2.4 |
|
||||
| `DELETE /api/hostnet/interfaces/{name}/addresses/{addr}` | remove an address | 15.2.4 |
|
||||
| `POST /api/hostnet/interfaces/{name}/restore` | undo last / restore snapshot | 15.2.5 |
|
||||
| `GET /api/hostnet/audit` | bounded recent audit lines | 15.2.5 |
|
||||
| `PUT /api/hostnet/interfaces/{name}/addresses` | change an interface's primary address (add-first, never address-less mid-change) | 15.2.4 |
|
||||
| `POST /api/hostnet/interfaces/{name}/restore` | undo last / restore snapshot (auto-snapshot precedes every mutation) | 15.2.5 |
|
||||
| `GET /api/hostnet/audit` | bounded recent audit lines (mutations AND refusals) | 15.2.5 |
|
||||
|
||||
Error mapping: agent unreachable → 503; whitelist/unknown-interface refusal → 403; validation/backend errors → 400; readonly mode hides mutations and answers 403 (FR-15.2.6). Every mutation line in the audit names the logged-in operator.
|
||||
|
||||
### 15.4 Scoping questions (answer before planning)
|
||||
|
||||
@@ -1748,30 +1766,37 @@ Web API, mounted only under the FR-15.2.1/2 gates:
|
||||
operator's own web UI session on that NIC — or are address
|
||||
add/remove/change sufficient for the planned FAT cases?
|
||||
|
||||
### 15.5 Acceptance criteria (draft)
|
||||
### 15.5 Acceptance criteria
|
||||
|
||||
1. Without `WAGO_SIM_HOSTNET`: no page; all `/api/hostnet/*` routes
|
||||
answer 404; the rest of the UI unchanged (regression suite green).
|
||||
2. Flag set but not in docker: same as 1.
|
||||
3. Flag set + docker + agent reachable: the page lists the host's real
|
||||
interfaces; adding `192.168.1.33/24` to the whitelisted interface
|
||||
shows in `ip addr` **on the host**, and the new address is
|
||||
reachable from the bench network (a master connects through it).
|
||||
4. Removing that address severs the master's connection through it —
|
||||
the PLC's reconnect logic is exercised (the FAT case).
|
||||
5. Restore/undo returns the interface to its pre-change snapshot; a
|
||||
host reboot likewise restores the deployed addressing (changes are
|
||||
runtime-only, FR-15.2.4).
|
||||
6. An operation on a non-whitelisted interface is refused by the
|
||||
agent and surfaced as an error on the page; the audit log records
|
||||
every mutation and every refusal.
|
||||
7. Read-only mode: page hidden, mutations 403.
|
||||
8. DIP integration: applying switch position 33 in dedicated-IP mode
|
||||
results in `192.168.1.33` present on the whitelisted NIC (bridge
|
||||
mode) or the server rebind (host mode); the "restart required"
|
||||
semantics of FR-11.3.3 are unchanged.
|
||||
9. Agent socket missing: "host control unavailable" banner,
|
||||
mutations 503, Modbus server and the rest of the web UI unaffected.
|
||||
Automated coverage (58 tests, `tests/test_hostnet.py`) verifies every
|
||||
item on the fake backend + InProcessClient path; the real-host
|
||||
`iproute2` path shares the same dispatch in `handle()` and the same
|
||||
parse fallbacks (unit-tested):
|
||||
|
||||
1. Without `WAGO_SIM_HOSTNET`: no page (the tab stays hidden — the SPA
|
||||
probe gets 404), all `/api/hostnet/*` routes answer 404, and the
|
||||
rest of the UI is unchanged (full regression suite green).
|
||||
2. Flag set but not in docker: same as 1 (`hostnet_enabled` unit
|
||||
tests cover the marker/env combinations).
|
||||
3. Flag set + docker + agent reachable: the page lists the host's
|
||||
interfaces; adding `192.168.1.33/24` to a whitelisted interface
|
||||
lands in the backend and is visible on the next `list` (the
|
||||
host-visible `ip addr` effect is the same write path).
|
||||
4. Removing that address makes the next list reflect it — the FAT
|
||||
case (severing a master's route) is staged from the bench UI.
|
||||
5. Restore returns the interface to the auto-snapshot taken before
|
||||
the mutation; changes are runtime-only (FR-15.2.4) so a host reboot
|
||||
likewise restores the deployed addressing.
|
||||
6. An operation on a non-whitelisted interface is refused (403),
|
||||
surfaced on the page via the banner, and recorded as `REFUSED` in
|
||||
the audit log — as is every mutation with the operator's name.
|
||||
7. Read-only mode: status stays readable; every mutation answers 403.
|
||||
8. DIP integration (FR-15.2.9): **deferred** until the §11 DIP model
|
||||
ships; the agent surface it will route through is stable.
|
||||
9. Agent socket missing: `available:false` with the socket message
|
||||
(banner on the page), mutations 503 — and the Modbus server and
|
||||
the rest of the web UI unaffected (verified in-process with a
|
||||
dead client and by the regression suite).
|
||||
|
||||
---
|
||||
|
||||
@@ -1784,7 +1809,7 @@ Web API, mounted only under the FR-15.2.1/2 gates:
|
||||
| 4 | FAT report generator (HTML/PDF), scenario comparison, template library per project type |
|
||||
| 5 | Multi-node orchestration (several couplers in one FAT bench), UDP variant, WBM simulation; **web UI (§10)**; **runtime device configuration (§11)**: module hot-configuration with image rebuild, DIP-switch emulation, WBM-equivalent pages, device-state persistence |
|
||||
| 6 | CI integration: run scenarios headless, export JUnit XML |
|
||||
| ? | **Multi-vendor device profiles (§13)**, **additional fieldbus protocols PROFINET/COMLI (§14)**, and **host network settings (§15)** — not yet scheduled; phase assignment after §13.3/§14.3/§15.4 scoping |
|
||||
| ? | **Multi-vendor device profiles (§13)** and **additional fieldbus protocols PROFINET/COMLI (§14)** — not yet scheduled; phase assignment after §13.3/§14.3 scoping. **Host network settings (§15) implemented 2026-10-06** (v1 ops; link up/down and DIP routing deferred) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user