From 6d290adb37d303c32edaa52d226c49dd24607bb9 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Tue, 21 Apr 2026 12:02:40 -0400 Subject: [PATCH] =?UTF-8?q?Task=20#220=20=E2=80=94=20AB=20CIP=20+=20S7=20l?= =?UTF-8?q?ive-boot=20verification=20(5/5=20stages=20each)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replicated the Modbus #218 bring-up against the AB CIP + S7 seeds to confirm the factories + seeds shipped in #217 actually work end-to-end. Both pass 5/5 e2e stages with `OpcUaServer:AnonymousRoles=[WriteOperate]` (the #221 knob). ## AB CIP (against ab_server controllogix fixture, port 44818) ``` === AB CIP e2e summary: 5/5 passed === [PASS] Probe [PASS] Driver loopback [PASS] Server bridge (driver → server → client) [PASS] OPC UA write bridge (client → server → driver) [PASS] Subscribe sees change ``` Server log: `DriverInstance abcip-smoke-drv (AbCip) registered + initialized` ✓. ## S7 (against python-snap7 s7_1500 fixture, port 1102) ``` === S7 e2e summary: 5/5 passed === [PASS] Probe [PASS] Driver loopback [PASS] Server bridge [PASS] OPC UA write bridge [PASS] Subscribe sees change ``` Server log: `DriverInstance s7-smoke-drv (S7) registered + initialized` ✓. ## Seed fixes so bring-up is idempotent Live-boot exposed two seed-level papercuts when applying multiple smoke seeds in sequence: 1. **SA credential collision.** `UX_ClusterNodeCredential_Value` is a unique index on `(Kind, Value) WHERE Enabled=1`, so `sa` can only bind to one node at a time. Each seed's DELETE block only dropped the credential tied to ITS node — seeding AbCip after Modbus blew up with `Cannot insert duplicate key` on the sa binding. Added a global `DELETE FROM dbo.ClusterNodeCredential WHERE Kind='SqlLogin' AND Value='sa'` before the per-cluster INSERTs. Production deployments using non-SA logins aren't affected. 2. **DashboardPort 5000 → 15050.** `HealthEndpointsHost` uses `HttpListener`, which rejects port 5000 on Windows without a `netsh http add urlacl` grant or admin rights. 15050 is unreserved + loopback-safe per the HealthEndpointsHost remarks. Applied to all four smoke seeds (Modbus was patched at runtime in #218; now baked into the seed). ## AB Legacy status Not live-boot verified — ab_server PCCC dispatcher is upstream-broken (#222). The factory + seed ship ready for hardware; the seed's DELETE + DashboardPort fixes land in this PR so when real SLC/MicroLogix/PLC-5 arrives the sql just applies. ## Closes #220 Umbrella #209 was already closed; #220 was the final child. Co-Authored-By: Claude Opus 4.7 (1M context) --- scripts/smoke/seed-abcip-smoke.sql | 4 +++- scripts/smoke/seed-ablegacy-smoke.sql | 4 +++- scripts/smoke/seed-modbus-smoke.sql | 11 ++++++++++- scripts/smoke/seed-s7-smoke.sql | 5 ++++- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/scripts/smoke/seed-abcip-smoke.sql b/scripts/smoke/seed-abcip-smoke.sql index f9c9e4d..14028cb 100644 --- a/scripts/smoke/seed-abcip-smoke.sql +++ b/scripts/smoke/seed-abcip-smoke.sql @@ -47,12 +47,14 @@ DELETE FROM dbo.ClusterNodeGenerationState WHERE NodeId = @NodeId; DELETE FROM dbo.ClusterNode WHERE NodeId = @NodeId; DELETE FROM dbo.ServerCluster WHERE ClusterId = @ClusterId; +DELETE FROM dbo.ClusterNodeCredential WHERE Kind = 'SqlLogin' AND Value = 'sa'; + INSERT dbo.ServerCluster(ClusterId, Name, Enterprise, Site, NodeCount, RedundancyMode, Enabled, CreatedBy) VALUES (@ClusterId, 'AB CIP Smoke', 'zb', 'lab', 1, 'None', 1, 'abcip-smoke'); INSERT dbo.ClusterNode(NodeId, ClusterId, RedundancyRole, Host, OpcUaPort, DashboardPort, ApplicationUri, ServiceLevelBase, Enabled, CreatedBy) -VALUES (@NodeId, @ClusterId, 'Primary', 'localhost', 4840, 5000, +VALUES (@NodeId, @ClusterId, 'Primary', 'localhost', 4840, 15050, 'urn:OtOpcUa:abcip-smoke-node', 200, 1, 'abcip-smoke'); INSERT dbo.ClusterNodeCredential(NodeId, Kind, Value, Enabled, CreatedBy) diff --git a/scripts/smoke/seed-ablegacy-smoke.sql b/scripts/smoke/seed-ablegacy-smoke.sql index 1da13d1..2ce007f 100644 --- a/scripts/smoke/seed-ablegacy-smoke.sql +++ b/scripts/smoke/seed-ablegacy-smoke.sql @@ -44,12 +44,14 @@ DELETE FROM dbo.ClusterNodeGenerationState WHERE NodeId = @NodeId; DELETE FROM dbo.ClusterNode WHERE NodeId = @NodeId; DELETE FROM dbo.ServerCluster WHERE ClusterId = @ClusterId; +DELETE FROM dbo.ClusterNodeCredential WHERE Kind = 'SqlLogin' AND Value = 'sa'; + INSERT dbo.ServerCluster(ClusterId, Name, Enterprise, Site, NodeCount, RedundancyMode, Enabled, CreatedBy) VALUES (@ClusterId, 'AB Legacy Smoke', 'zb', 'lab', 1, 'None', 1, 'ablegacy-smoke'); INSERT dbo.ClusterNode(NodeId, ClusterId, RedundancyRole, Host, OpcUaPort, DashboardPort, ApplicationUri, ServiceLevelBase, Enabled, CreatedBy) -VALUES (@NodeId, @ClusterId, 'Primary', 'localhost', 4840, 5000, +VALUES (@NodeId, @ClusterId, 'Primary', 'localhost', 4840, 15050, 'urn:OtOpcUa:ablegacy-smoke-node', 200, 1, 'ablegacy-smoke'); INSERT dbo.ClusterNodeCredential(NodeId, Kind, Value, Enabled, CreatedBy) diff --git a/scripts/smoke/seed-modbus-smoke.sql b/scripts/smoke/seed-modbus-smoke.sql index a2be86d..dcaa91c 100644 --- a/scripts/smoke/seed-modbus-smoke.sql +++ b/scripts/smoke/seed-modbus-smoke.sql @@ -56,13 +56,22 @@ DELETE FROM dbo.ClusterNodeGenerationState WHERE NodeId = @NodeId; DELETE FROM dbo.ClusterNode WHERE NodeId = @NodeId; DELETE FROM dbo.ServerCluster WHERE ClusterId = @ClusterId; +-- `UX_ClusterNodeCredential_Value` is a unique index on (Kind, Value) WHERE +-- Enabled=1, so a `sa` login can only bind to one node at a time. Drop any +-- prior smoke cluster's binding before we claim the login for this one. +DELETE FROM dbo.ClusterNodeCredential WHERE Kind = 'SqlLogin' AND Value = 'sa'; + -- 1. Cluster + Node. INSERT dbo.ServerCluster(ClusterId, Name, Enterprise, Site, NodeCount, RedundancyMode, Enabled, CreatedBy) VALUES (@ClusterId, 'Modbus Smoke', 'zb', 'lab', 1, 'None', 1, 'modbus-smoke'); +-- DashboardPort 15050 rather than 5000 — HttpListener on :5000 requires +-- URL-ACL reservation or admin rights on Windows (HttpListenerException 32). +-- 15000+ ports are unreserved by default. Safe to change back when deploying +-- with a netsh urlacl grant or reverse-proxy fronting :5000. INSERT dbo.ClusterNode(NodeId, ClusterId, RedundancyRole, Host, OpcUaPort, DashboardPort, ApplicationUri, ServiceLevelBase, Enabled, CreatedBy) -VALUES (@NodeId, @ClusterId, 'Primary', 'localhost', 4840, 5000, +VALUES (@NodeId, @ClusterId, 'Primary', 'localhost', 4840, 15050, 'urn:OtOpcUa:modbus-smoke-node', 200, 1, 'modbus-smoke'); -- Bind the SQL login this smoke test connects as to the node identity. The diff --git a/scripts/smoke/seed-s7-smoke.sql b/scripts/smoke/seed-s7-smoke.sql index 6a0e723..98868ee 100644 --- a/scripts/smoke/seed-s7-smoke.sql +++ b/scripts/smoke/seed-s7-smoke.sql @@ -48,13 +48,16 @@ DELETE FROM dbo.ClusterNodeGenerationState WHERE NodeId = @NodeId; DELETE FROM dbo.ClusterNode WHERE NodeId = @NodeId; DELETE FROM dbo.ServerCluster WHERE ClusterId = @ClusterId; +DELETE FROM dbo.ClusterNodeCredential WHERE Kind = 'SqlLogin' AND Value = 'sa'; + INSERT dbo.ServerCluster(ClusterId, Name, Enterprise, Site, NodeCount, RedundancyMode, Enabled, CreatedBy) VALUES (@ClusterId, 'S7 Smoke', 'zb', 'lab', 1, 'None', 1, 's7-smoke'); INSERT dbo.ClusterNode(NodeId, ClusterId, RedundancyRole, Host, OpcUaPort, DashboardPort, ApplicationUri, ServiceLevelBase, Enabled, CreatedBy) -VALUES (@NodeId, @ClusterId, 'Primary', 'localhost', 4840, 5000, +VALUES (@NodeId, @ClusterId, 'Primary', 'localhost', 4840, 15050, 'urn:OtOpcUa:s7-smoke-node', 200, 1, 's7-smoke'); +-- Dashboard moved off :5000 (Windows URL-ACL). INSERT dbo.ClusterNodeCredential(NodeId, Kind, Value, Enabled, CreatedBy) VALUES (@NodeId, 'SqlLogin', 'sa', 1, 's7-smoke'); -- 2.49.1