Phase 3 PR 33 — DriverHostStatus entity + migration (LMX #7 data layer) #32

Merged
dohertj2 merged 1 commits from phase-3-pr33-driverhoststatus-entity into v2 2026-04-18 15:43:39 -04:00
Owner

First data-layer piece of LMX follow-up #7 (per-AppEngine Admin dashboard drill-down). Adds the table the server-side publisher writes to and the Admin UI will poll. No publisher or page yet — that's PR 34.

Schema

DriverHostStatus, composite PK (NodeId, DriverInstanceId, HostName):

Column Type Notes
NodeId nvarchar(64) Server node running the driver.
DriverInstanceId nvarchar(64) Matches IDriver.DriverInstanceId.
HostName nvarchar(256) Driver-side host id (Galaxy Platform/AppEngine, Modbus host:port, etc).
State nvarchar(16) DriverHostState enum persisted as string (readable in DBA tools).
StateChangedUtc datetime2(3) Last state transition.
LastSeenUtc datetime2(3) Advances on every publisher heartbeat — Admin uses now - LastSeen > threshold to flag stale rows from a crashed Server.
Detail nvarchar(1024)? Exception message when Faulted.

Indexes: IX_DriverHostStatus_Node (per-cluster drill-down via join on ClusterNode.ClusterId), IX_DriverHostStatus_LastSeen (stale-row query).

Design notes

  • One row per (server node, driver, host) — a redundant 2-node cluster with one Galaxy driver reporting 3 platforms produces 6 rows, not 3, because each server node owns its own runtime view. The composite key lets both views coexist without shadowing each other.
  • No FK to ClusterNode — a Server may start reporting host status before its ClusterNode row exists (first-boot bootstrap). We'd rather keep the row than drop it; Admin left-joins on NodeId when rendering.
  • DriverHostState enum lives in Configuration.Enums/, not reusing Core.Abstractions.HostState. Keeps Configuration free of driver-runtime deps — both Admin + Server reference Configuration, and pulling in Core.Abstractions to every Admin build would be unnecessary weight. Publisher translates on the way in.
  • String-backed enum (HasConversion<string>()). DBAs see 'Faulted' / 'Running' in queries, not 3 / 1.

Tests

DriverHostStatusTests (3 new SchemaCompliance cases using the shared fixture DB):

  • Composite key allows same (host, driver) across different nodes + same (node, host) across different drivers.
  • Upsert-in-place pattern (fetch → mutate → save) produces one row, not a PK-violation duplicate — the pattern the publisher will use.
  • State enum persists as the string literal, verified by raw ADO.NET read.

SchemaComplianceTests.All_expected_tables_exist extended to include the new table.

Test posture

  • Configuration.Tests SchemaCompliance: 10 pass / 0 fail (7 prior + 3 new).
  • Configuration build clean — 0 warnings, 0 errors.
  • No Server or Admin code changes in this PR.

Next

PR 34 — server-side publisher hosted service (subscribes to each driver's OnHostStatusChanged + periodic heartbeat, upserts rows) + Admin UI /hosts page + background poller pattern mirroring FleetStatusPoller.

First data-layer piece of LMX follow-up #7 (per-AppEngine Admin dashboard drill-down). Adds the table the server-side publisher writes to and the Admin UI will poll. No publisher or page yet — that's PR 34. ## Schema `DriverHostStatus`, composite PK `(NodeId, DriverInstanceId, HostName)`: | Column | Type | Notes | | --- | --- | --- | | NodeId | nvarchar(64) | Server node running the driver. | | DriverInstanceId | nvarchar(64) | Matches `IDriver.DriverInstanceId`. | | HostName | nvarchar(256) | Driver-side host id (Galaxy Platform/AppEngine, Modbus `host:port`, etc). | | State | nvarchar(16) | `DriverHostState` enum persisted as string (readable in DBA tools). | | StateChangedUtc | datetime2(3) | Last state transition. | | LastSeenUtc | datetime2(3) | Advances on every publisher heartbeat — Admin uses `now - LastSeen > threshold` to flag stale rows from a crashed Server. | | Detail | nvarchar(1024)? | Exception message when Faulted. | Indexes: `IX_DriverHostStatus_Node` (per-cluster drill-down via join on ClusterNode.ClusterId), `IX_DriverHostStatus_LastSeen` (stale-row query). ## Design notes - **One row per `(server node, driver, host)`** — a redundant 2-node cluster with one Galaxy driver reporting 3 platforms produces 6 rows, not 3, because each server node owns its own runtime view. The composite key lets both views coexist without shadowing each other. - **No FK to `ClusterNode`** — a Server may start reporting host status before its `ClusterNode` row exists (first-boot bootstrap). We'd rather keep the row than drop it; Admin left-joins on NodeId when rendering. - **`DriverHostState` enum lives in `Configuration.Enums/`**, not reusing `Core.Abstractions.HostState`. Keeps Configuration free of driver-runtime deps — both Admin + Server reference Configuration, and pulling in `Core.Abstractions` to every Admin build would be unnecessary weight. Publisher translates on the way in. - **String-backed enum** (`HasConversion<string>()`). DBAs see `'Faulted'` / `'Running'` in queries, not `3` / `1`. ## Tests `DriverHostStatusTests` (3 new SchemaCompliance cases using the shared fixture DB): - Composite key allows same (host, driver) across different nodes + same (node, host) across different drivers. - Upsert-in-place pattern (fetch → mutate → save) produces one row, not a PK-violation duplicate — the pattern the publisher will use. - State enum persists as the string literal, verified by raw ADO.NET read. `SchemaComplianceTests.All_expected_tables_exist` extended to include the new table. ## Test posture - Configuration.Tests SchemaCompliance: **10 pass / 0 fail** (7 prior + 3 new). - Configuration build clean — 0 warnings, 0 errors. - No Server or Admin code changes in this PR. ## Next PR 34 — server-side publisher hosted service (subscribes to each driver's `OnHostStatusChanged` + periodic heartbeat, upserts rows) + Admin UI `/hosts` page + background poller pattern mirroring `FleetStatusPoller`.
dohertj2 added 1 commit 2026-04-18 15:43:36 -04:00
DriverHostState enum lives in Configuration.Enums/ rather than reusing Core.Abstractions.HostState so the Configuration project stays free of driver-runtime dependencies (it's referenced by both the Admin process and the Server process, so pulling in the driver-abstractions assembly to every Admin build would be unnecessary weight). The server-side publisher hosted service (follow-up PR 34) will translate HostStatusChangedEventArgs.NewState to this enum on every transition.
No foreign key to ClusterNode — a Server may start reporting host status before its ClusterNode row exists (first-boot bootstrap), and we'd rather keep the status row than drop it. The Admin-side service that renders the dashboard will left-join on NodeId when presenting. Two indexes declared: IX_DriverHostStatus_Node drives the per-cluster drill-down (Admin UI joins ClusterNode on ClusterId to pick which NodeIds to fetch), IX_DriverHostStatus_LastSeen drives the stale-row query (now - LastSeen > threshold).
EF migration AddDriverHostStatus creates the table + PK + both indexes. Model snapshot updated. SchemaComplianceTests expected-tables list extended. DriverHostStatusTests (3 new cases, category SchemaCompliance, uses the shared fixture DB): composite key allows same (host, driver) across different nodes AND same (node, host) across different drivers — both real-world cases the publisher needs to support; upsert-in-place pattern (fetch-by-composite-PK, mutate, save) produces one row not two — the pattern the publisher will use; State enum persists as string not int — reading the DB via ADO.NET returns 'Faulted' not '3'.
Configuration.Tests SchemaCompliance suite: 10 pass / 0 fail (7 prior + 3 new). Configuration build clean. No Server or Admin code changes yet — publisher + /hosts page are PR 34.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dohertj2 merged commit a7764e50f3 into v2 2026-04-18 15:43:39 -04:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dohertj2/lmxopcua#32