fdee129fd0
Live gate: the seeded MAIN-opcua driver failed Initialize ('Equipment but no
UnsMappingTable'), so the opc-plc alarm leg could never connect.
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
234 lines
15 KiB
Transact-SQL
234 lines
15 KiB
Transact-SQL
-- docker-dev cluster seed. Idempotent — safe to re-run on every `docker compose up`.
|
|
--
|
|
-- Populates:
|
|
-- ServerCluster MAIN, SITE-A, SITE-B
|
|
-- ClusterNode central-1, central-2 → MAIN
|
|
-- site-a-1, site-a-2 → SITE-A
|
|
-- site-b-1, site-b-2 → SITE-B
|
|
--
|
|
-- ServerCluster.NodeCount + RedundancyMode are coupled by CHECK constraint:
|
|
-- NodeCount=1 ⇒ RedundancyMode='None'
|
|
-- NodeCount=2 ⇒ RedundancyMode∈('Warm','Hot')
|
|
--
|
|
-- Each ClusterNode.ApplicationUri MUST be globally unique (UX_ClusterNode_ApplicationUri).
|
|
-- Convention: urn:OtOpcUa:<NodeId>.
|
|
--
|
|
-- Host = Compose service name (resolves inside the otopcua-dev network).
|
|
-- OpcUaPort stays at the container-internal 4840; the host-side port mapping is in
|
|
-- docker-compose.yml ports: blocks and is irrelevant to ClusterNode rows.
|
|
--
|
|
-- GrpcPort (per-cluster mesh Phase 5): every node's dedicated live-telemetry gRPC listener,
|
|
-- matching Telemetry__GrpcListenPort=4056 set on all six host nodes in docker-compose.yml.
|
|
-- TelemetryNodeSource (central) reads Host + GrpcPort to build its dial-target set
|
|
-- (http://{Host}:{GrpcPort}); a NULL GrpcPort is skipped with a Warning. Mirrors how AkkaPort
|
|
-- was seeded for Phase 1's ClusterNodeAddressReconcilerActor.
|
|
|
|
SET NOCOUNT ON;
|
|
SET XACT_ABORT ON;
|
|
|
|
BEGIN TRANSACTION;
|
|
|
|
------------------------------------------------------------------------------
|
|
-- ServerCluster
|
|
------------------------------------------------------------------------------
|
|
|
|
IF NOT EXISTS (SELECT 1 FROM dbo.ServerCluster WHERE ClusterId = 'MAIN')
|
|
INSERT INTO dbo.ServerCluster
|
|
(ClusterId, Name, Enterprise, Site, NodeCount, RedundancyMode, Enabled, Notes, CreatedBy)
|
|
VALUES
|
|
('MAIN', 'Main cluster', 'zb', 'docker-dev',
|
|
2, 'Warm', 1,
|
|
'docker-dev seed — central-1/central-2 fused admin+driver: UI + deploy singleton + MAIN OPC UA publishers.',
|
|
'docker-dev-seed');
|
|
|
|
IF NOT EXISTS (SELECT 1 FROM dbo.ServerCluster WHERE ClusterId = 'SITE-A')
|
|
INSERT INTO dbo.ServerCluster
|
|
(ClusterId, Name, Enterprise, Site, NodeCount, RedundancyMode, Enabled, Notes, CreatedBy)
|
|
VALUES
|
|
('SITE-A', 'Site A', 'zb', 'site-a',
|
|
2, 'Warm', 1,
|
|
'docker-dev seed — 2-node driver-only, managed by the central cluster over the shared mesh (empty until configured).',
|
|
'docker-dev-seed');
|
|
|
|
IF NOT EXISTS (SELECT 1 FROM dbo.ServerCluster WHERE ClusterId = 'SITE-B')
|
|
INSERT INTO dbo.ServerCluster
|
|
(ClusterId, Name, Enterprise, Site, NodeCount, RedundancyMode, Enabled, Notes, CreatedBy)
|
|
VALUES
|
|
('SITE-B', 'Site B', 'zb', 'site-b',
|
|
2, 'Warm', 1,
|
|
'docker-dev seed — 2-node driver-only, managed by the central cluster over the shared mesh (empty until configured).',
|
|
'docker-dev-seed');
|
|
|
|
------------------------------------------------------------------------------
|
|
-- ClusterNode — central cluster (MAIN UI + deploy singleton + OPC UA publishers)
|
|
--
|
|
-- NodeId is "<compose-service>:4053" so it matches what ClusterRoleInfo +
|
|
-- ConfigPublishCoordinator derive from Akka.Cluster.Get(system).State.Members
|
|
-- (member.Address.Host:Port). NodeDeploymentState.NodeId is FK-bound to
|
|
-- ClusterNode.NodeId; mismatched values cause FK 547 on deploy.
|
|
------------------------------------------------------------------------------
|
|
|
|
IF NOT EXISTS (SELECT 1 FROM dbo.ClusterNode WHERE NodeId = 'central-1:4053')
|
|
INSERT INTO dbo.ClusterNode
|
|
(NodeId, ClusterId, Host, OpcUaPort, DashboardPort, AkkaPort, GrpcPort, ApplicationUri, ServiceLevelBase, Enabled, CreatedBy)
|
|
VALUES ('central-1:4053', 'MAIN', 'central-1', 4840, 8081, 4053, 4056, 'urn:OtOpcUa:central-1', 200, 1, 'docker-dev-seed');
|
|
|
|
IF NOT EXISTS (SELECT 1 FROM dbo.ClusterNode WHERE NodeId = 'central-2:4053')
|
|
INSERT INTO dbo.ClusterNode
|
|
(NodeId, ClusterId, Host, OpcUaPort, DashboardPort, AkkaPort, GrpcPort, ApplicationUri, ServiceLevelBase, Enabled, CreatedBy)
|
|
VALUES ('central-2:4053', 'MAIN', 'central-2', 4840, 8081, 4053, 4056, 'urn:OtOpcUa:central-2', 150, 1, 'docker-dev-seed');
|
|
|
|
------------------------------------------------------------------------------
|
|
-- ClusterNode — site A
|
|
------------------------------------------------------------------------------
|
|
|
|
IF NOT EXISTS (SELECT 1 FROM dbo.ClusterNode WHERE NodeId = 'site-a-1:4053')
|
|
INSERT INTO dbo.ClusterNode
|
|
(NodeId, ClusterId, Host, OpcUaPort, DashboardPort, AkkaPort, GrpcPort, ApplicationUri, ServiceLevelBase, Enabled, CreatedBy)
|
|
VALUES ('site-a-1:4053', 'SITE-A', 'site-a-1', 4840, 8081, 4053, 4056, 'urn:OtOpcUa:site-a-1', 200, 1, 'docker-dev-seed');
|
|
|
|
IF NOT EXISTS (SELECT 1 FROM dbo.ClusterNode WHERE NodeId = 'site-a-2:4053')
|
|
INSERT INTO dbo.ClusterNode
|
|
(NodeId, ClusterId, Host, OpcUaPort, DashboardPort, AkkaPort, GrpcPort, ApplicationUri, ServiceLevelBase, Enabled, CreatedBy)
|
|
VALUES ('site-a-2:4053', 'SITE-A', 'site-a-2', 4840, 8081, 4053, 4056, 'urn:OtOpcUa:site-a-2', 150, 1, 'docker-dev-seed');
|
|
|
|
------------------------------------------------------------------------------
|
|
-- ClusterNode — site B
|
|
------------------------------------------------------------------------------
|
|
|
|
IF NOT EXISTS (SELECT 1 FROM dbo.ClusterNode WHERE NodeId = 'site-b-1:4053')
|
|
INSERT INTO dbo.ClusterNode
|
|
(NodeId, ClusterId, Host, OpcUaPort, DashboardPort, AkkaPort, GrpcPort, ApplicationUri, ServiceLevelBase, Enabled, CreatedBy)
|
|
VALUES ('site-b-1:4053', 'SITE-B', 'site-b-1', 4840, 8081, 4053, 4056, 'urn:OtOpcUa:site-b-1', 200, 1, 'docker-dev-seed');
|
|
|
|
IF NOT EXISTS (SELECT 1 FROM dbo.ClusterNode WHERE NodeId = 'site-b-2:4053')
|
|
INSERT INTO dbo.ClusterNode
|
|
(NodeId, ClusterId, Host, OpcUaPort, DashboardPort, AkkaPort, GrpcPort, ApplicationUri, ServiceLevelBase, Enabled, CreatedBy)
|
|
VALUES ('site-b-2:4053', 'SITE-B', 'site-b-2', 4840, 8081, 4053, 4056, 'urn:OtOpcUa:site-b-2', 150, 1, 'docker-dev-seed');
|
|
|
|
------------------------------------------------------------------------------
|
|
-- Backfill: GrpcPort on rows that predate the column (#493)
|
|
--
|
|
-- Every insert above is INSERT-if-not-exists, which is the right shape for a re-runnable seed
|
|
-- but means a *new nullable* column never reaches rows created by an earlier version of this
|
|
-- file. On a persisted volume that is exactly what happened when Phase 5 added GrpcPort: the
|
|
-- six ClusterNode rows already existed, so they kept NULL, TelemetryNodeSource found no dial
|
|
-- targets, and TelemetryDial:Mode=Grpc silently connected to nothing (throttled Warning per
|
|
-- node — the code degrades gracefully, so nothing failed loudly).
|
|
--
|
|
-- Backfills NULL only. It must not overwrite a port an operator set deliberately on a
|
|
-- long-lived dev volume; a NULL is the unambiguous "this row predates the column" marker.
|
|
--
|
|
-- AkkaPort deliberately has no equivalent here: it is NOT NULL with a default of 4053, so
|
|
-- pre-existing rows already carry a usable value and there is nothing to backfill.
|
|
------------------------------------------------------------------------------
|
|
|
|
UPDATE dbo.ClusterNode
|
|
SET GrpcPort = 4056 -- matches Telemetry__GrpcListenPort on all six nodes in docker-compose.yml
|
|
WHERE GrpcPort IS NULL
|
|
AND CreatedBy = 'docker-dev-seed';
|
|
|
|
------------------------------------------------------------------------------
|
|
-- Galaxy MxAccess gateway — INTENTIONALLY NOT SEEDED (retired 2026-06-15)
|
|
--
|
|
-- This block used to seed a Namespace with Kind='SystemPlatform' + a
|
|
-- 'GalaxyMxGateway' DriverInstance + 3 mirror tags. That is now BROKEN: the
|
|
-- 'SystemPlatform' NamespaceKind member was removed when Galaxy became a
|
|
-- standard Equipment-kind driver (migration 20260613022355
|
|
-- CleanupSystemPlatformNamespaces). cluster-seed runs AFTER the migrator, so
|
|
-- re-inserting a 'SystemPlatform' row re-introduced a Kind the current code
|
|
-- cannot EF-materialize — which 500s the /deployments page AND fails every
|
|
-- publish (ConfigComposer reads db.Namespaces). See the cleanup migration.
|
|
--
|
|
-- If a Galaxy fixture is needed on a fresh rig, author an EQUIPMENT-kind Galaxy
|
|
-- driver via the UNS UI (the 'MAIN-galaxy-eq' pattern) — do NOT re-add a
|
|
-- SystemPlatform namespace here.
|
|
------------------------------------------------------------------------------
|
|
|
|
COMMIT TRANSACTION;
|
|
|
|
------------------------------------------------------------------------------
|
|
-- Summary (logged by sqlcmd output)
|
|
------------------------------------------------------------------------------
|
|
|
|
-- v3: the Namespace table is retired; DriverInstance is folder-scoped (RawFolderId) and Tags
|
|
-- are device-scoped (DeviceId + optional TagGroupId). This seed only creates clusters + nodes +
|
|
-- LDAP role mappings, so the summary reflects just those. Raw-tree config (RawFolder/DriverInstance/
|
|
-- Device/TagGroup/Tag) is authored via the AdminUI /raw page in later batches.
|
|
SELECT ClusterId, Name, NodeCount, RedundancyMode FROM dbo.ServerCluster ORDER BY ClusterId;
|
|
SELECT NodeId, ClusterId, Host, OpcUaPort, ApplicationUri, ServiceLevelBase
|
|
FROM dbo.ClusterNode ORDER BY ClusterId, NodeId;
|
|
|
|
------------------------------------------------------------------------------
|
|
-- LDAP group -> AdminUI role mappings (shared dev GLAuth, 10.100.0.35)
|
|
-- System-wide (ClusterId NULL, IsSystemWide 1). Group keys are the BARE RDN
|
|
-- names the shared ZB.MOM.WW.Auth.Ldap returns (LdapAuthService.ToGroupShortName
|
|
-- = first-RDN value), e.g. memberOf ou=OtOpcUa-Admins,... -> "OtOpcUa-Admins".
|
|
-- Role is stored as the AdminRole enum NAME (HasConversion<string>).
|
|
-- QUOTED_IDENTIFIER ON is required because the table has a filtered unique index.
|
|
------------------------------------------------------------------------------
|
|
SET QUOTED_IDENTIFIER ON;
|
|
SET ANSI_NULLS ON;
|
|
IF NOT EXISTS (SELECT 1 FROM dbo.LdapGroupRoleMapping WHERE LdapGroup = 'OtOpcUa-Admins' AND ClusterId IS NULL)
|
|
INSERT INTO dbo.LdapGroupRoleMapping (Id, LdapGroup, Role, ClusterId, IsSystemWide, CreatedAtUtc, Notes)
|
|
VALUES (NEWID(), 'OtOpcUa-Admins', 'Administrator', NULL, 1, SYSUTCDATETIME(), N'shared-glauth dev seed');
|
|
IF NOT EXISTS (SELECT 1 FROM dbo.LdapGroupRoleMapping WHERE LdapGroup = 'OtOpcUa-Designers' AND ClusterId IS NULL)
|
|
INSERT INTO dbo.LdapGroupRoleMapping (Id, LdapGroup, Role, ClusterId, IsSystemWide, CreatedAtUtc, Notes)
|
|
VALUES (NEWID(), 'OtOpcUa-Designers', 'Designer', NULL, 1, SYSUTCDATETIME(), N'shared-glauth dev seed');
|
|
IF NOT EXISTS (SELECT 1 FROM dbo.LdapGroupRoleMapping WHERE LdapGroup = 'OtOpcUa-Viewers' AND ClusterId IS NULL)
|
|
INSERT INTO dbo.LdapGroupRoleMapping (Id, LdapGroup, Role, ClusterId, IsSystemWide, CreatedAtUtc, Notes)
|
|
VALUES (NEWID(), 'OtOpcUa-Viewers', 'Viewer', NULL, 1, SYSUTCDATETIME(), N'shared-glauth dev seed');
|
|
|
|
SELECT LdapGroup, Role, IsSystemWide FROM dbo.LdapGroupRoleMapping ORDER BY LdapGroup;
|
|
|
|
------------------------------------------------------------------------------
|
|
-- v3 Batch 1 gate: a Modbus driver in cluster MAIN so the docker-dev boot has a
|
|
-- driver to show Connected against the modbus fixture (10.100.0.35:5020). Raw tree:
|
|
-- DriverInstance(RawFolderId NULL) -> Device(endpoint in DeviceConfig) -> Tag(raw).
|
|
-- Idempotent. Address space stays DARK this batch (no variable nodes materialize).
|
|
------------------------------------------------------------------------------
|
|
SET QUOTED_IDENTIFIER ON;
|
|
SET ANSI_NULLS ON;
|
|
IF NOT EXISTS (SELECT 1 FROM dbo.DriverInstance WHERE DriverInstanceId = 'MAIN-modbus')
|
|
INSERT dbo.DriverInstance(DriverInstanceId, ClusterId, RawFolderId, Name, DriverType, DriverConfig, Enabled)
|
|
VALUES ('MAIN-modbus', 'MAIN', NULL, 'pymodbus', 'Modbus',
|
|
N'{ "TimeoutMs": 2000, "AutoReconnect": true, "Probe": { "Enabled": true, "IntervalMs": 5000, "TimeoutMs": 2000, "ProbeAddress": 0 } }', 1);
|
|
IF NOT EXISTS (SELECT 1 FROM dbo.Device WHERE DeviceId = 'MAIN-modbus-dev')
|
|
INSERT dbo.Device(DeviceId, DriverInstanceId, Name, Enabled, DeviceConfig)
|
|
VALUES ('MAIN-modbus-dev', 'MAIN-modbus', 'plc', 1, N'{ "Host": "10.100.0.35", "Port": 5020, "UnitId": 1 }');
|
|
IF NOT EXISTS (SELECT 1 FROM dbo.Tag WHERE TagId = 'MAIN-modbus-hr200')
|
|
INSERT dbo.Tag(TagId, DeviceId, TagGroupId, Name, DataType, AccessLevel, WriteIdempotent, TagConfig)
|
|
VALUES ('MAIN-modbus-hr200', 'MAIN-modbus-dev', NULL, 'HR200', 'UInt16', 'ReadWrite', 1,
|
|
N'{ "region": "HoldingRegisters", "address": 200, "dataType": "UInt16", "writable": true }');
|
|
SELECT DriverInstanceId, ClusterId, DriverType, Enabled FROM dbo.DriverInstance WHERE ClusterId='MAIN';
|
|
|
|
------------------------------------------------------------------------------
|
|
-- #619: OpcUaClient driver against the opc-plc fixture (10.100.0.35:50000, started by
|
|
-- tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.IntegrationTests/Docker with --alm).
|
|
-- Tag identity under v3 is the RawPath; the upstream node is authored in TagConfig.nodeId.
|
|
-- * FastUInt1 (ns=3;s=FastUInt1) is the always-moving VALUE control: proves the driver is connected and
|
|
-- the RawPath -> nodeId table resolves before any alarm assertion is trusted.
|
|
-- * The ALARM tag's nodeId must be the SourceNode opc-plc stamps on its condition events - the driver
|
|
-- matches events on it. It is NOT guessable (and opc-plc was not running when this was authored), so
|
|
-- it is supplied at seed time: browse opc-plc (Client.CLI `browse -r -d 4`, or `alarms --refresh`
|
|
-- which prints each event's source) and set @OpcPlcAlarmSourceNodeId below. Empty = no alarm tag.
|
|
-- Idempotent.
|
|
------------------------------------------------------------------------------
|
|
DECLARE @OpcPlcAlarmSourceNodeId nvarchar(400) = N''; -- e.g. N'ns=3;s=<source from opc-plc>'
|
|
IF NOT EXISTS (SELECT 1 FROM dbo.DriverInstance WHERE DriverInstanceId = 'MAIN-opcua')
|
|
INSERT dbo.DriverInstance(DriverInstanceId, ClusterId, RawFolderId, Name, DriverType, DriverConfig, Enabled)
|
|
VALUES ('MAIN-opcua', 'MAIN', NULL, 'opcplc', 'OpcUaClient',
|
|
N'{ "SecurityPolicy": "None", "SecurityMode": "None", "AuthType": "Anonymous", "AutoAcceptCertificates": true, "TargetNamespaceKind": "SystemPlatform" }', 1);
|
|
IF NOT EXISTS (SELECT 1 FROM dbo.Device WHERE DeviceId = 'MAIN-opcua-dev')
|
|
INSERT dbo.Device(DeviceId, DriverInstanceId, Name, Enabled, DeviceConfig)
|
|
VALUES ('MAIN-opcua-dev', 'MAIN-opcua', 'plc', 1, N'{ "EndpointUrl": "opc.tcp://10.100.0.35:50000" }');
|
|
IF NOT EXISTS (SELECT 1 FROM dbo.Tag WHERE TagId = 'MAIN-opcua-fastuint1')
|
|
INSERT dbo.Tag(TagId, DeviceId, TagGroupId, Name, DataType, AccessLevel, WriteIdempotent, TagConfig)
|
|
VALUES ('MAIN-opcua-fastuint1', 'MAIN-opcua-dev', NULL, 'FastUInt1', 'UInt32', 'Read', 0,
|
|
N'{ "nodeId": "ns=3;s=FastUInt1" }');
|
|
IF LEN(@OpcPlcAlarmSourceNodeId) > 0 AND NOT EXISTS (SELECT 1 FROM dbo.Tag WHERE TagId = 'MAIN-opcua-alarm1')
|
|
INSERT dbo.Tag(TagId, DeviceId, TagGroupId, Name, DataType, AccessLevel, WriteIdempotent, TagConfig)
|
|
VALUES ('MAIN-opcua-alarm1', 'MAIN-opcua-dev', NULL, 'Alarm1', 'Boolean', 'Read', 0,
|
|
N'{ "nodeId": "' + @OpcPlcAlarmSourceNodeId + N'", "alarm": { "alarmType": "AlarmCondition", "severity": 500 } }');
|
|
SELECT DriverInstanceId, ClusterId, DriverType, Enabled FROM dbo.DriverInstance WHERE DriverInstanceId = 'MAIN-opcua';
|