diff --git a/docker-dev/README.md b/docker-dev/README.md index bd51c1c07..cae3aeaf4 100644 --- a/docker-dev/README.md +++ b/docker-dev/README.md @@ -251,5 +251,6 @@ The full six-node host stack therefore needs roughly **6 GiB** of Docker Desktop - Main: `opc.tcp://localhost:4840` (central-1), `opc.tcp://localhost:4841` (central-2) - Site A: `opc.tcp://localhost:4842` (site-a-1), `opc.tcp://localhost:4843` (site-a-2) - Site B: `opc.tcp://localhost:4844` (site-b-1), `opc.tcp://localhost:4845` (site-b-2) +- Each node serves its own `ApplicationUri` (`urn:OtOpcUa:`, the same as its seeded `ClusterNode.ApplicationUri`) and lists its pair partner in `Server.ServerArray` (`OpcUa__ApplicationUri` / `OpcUa__PeerApplicationUris__0`). Before lmxopcua#801 every node served the compiled default `urn:OtOpcUa` with a one-entry `ServerArray`. `Server.ServerRedundancy.RedundancySupport` still reads `None` on every node, by design until #717. The healthy pair's ServiceLevel is 250 (Primary, the oldest Up `driver` member) and 240 (Secondary). Read it with `otopcua-cli redundancy -u opc.tcp://localhost:4840`. - Galaxy + Wonderware drivers can't run in Linux containers (they need the Windows-only mxaccessgw + Historian SDK). On non-Windows, `DriverInstanceActor.ShouldStub(driverType, roles)` returns `true` for those types and the actor goes straight to a `Stubbed` state that returns deterministic success. - SQL persistence: ConfigDb state survives container restarts (named Docker volume). Drop the volume with `down -v` for a clean slate. diff --git a/docker-dev/docker-compose.yml b/docker-dev/docker-compose.yml index abfc17977..989362637 100644 --- a/docker-dev/docker-compose.yml +++ b/docker-dev/docker-compose.yml @@ -335,6 +335,12 @@ services: Cluster__Hostname: "0.0.0.0" Cluster__Port: "4053" Cluster__PublicHostname: "central-1" + # lmxopcua#801: each redundancy peer owns a DISTINCT ApplicationUri (docs/Redundancy.md) equal to its + # seeded ClusterNode.ApplicationUri, and advertises its pair partner in Server.ServerArray via + # PeerApplicationUris. Without these every node served the compiled default "urn:OtOpcUa" and a + # one-entry ServerArray, so a client could not tell the two halves of a pair apart. + OpcUa__ApplicationUri: "urn:OtOpcUa:central-1" + OpcUa__PeerApplicationUris__0: "urn:OtOpcUa:central-2" # Per-cluster mesh Phase 3 (config fetch-and-cache). Central SERVES the deployed-configuration # artifact to FetchAndCache site nodes over a dedicated h2c gRPC listener (:4055), gated by the # shared node key. Central itself is ALWAYS Direct — it IS the SQL source and must never fetch. @@ -502,6 +508,9 @@ services: Cluster__Hostname: "0.0.0.0" Cluster__Port: "4053" Cluster__PublicHostname: "central-2" + # lmxopcua#801: distinct ApplicationUri + pair partner in ServerArray — see central-1. + OpcUa__ApplicationUri: "urn:OtOpcUa:central-2" + OpcUa__PeerApplicationUris__0: "urn:OtOpcUa:central-1" # Per-cluster mesh Phase 3 — serves the artifact to FetchAndCache site nodes (see central-1). # Always Direct; ConfigServe:ApiKey must match the site nodes' ConfigSource:ApiKey. ConfigServe__GrpcListenPort: "4055" @@ -626,6 +635,9 @@ services: Cluster__Hostname: "0.0.0.0" Cluster__Port: "4053" Cluster__PublicHostname: "site-a-1" + # lmxopcua#801: distinct ApplicationUri + pair partner in ServerArray — see central-1. + OpcUa__ApplicationUri: "urn:OtOpcUa:site-a-1" + OpcUa__PeerApplicationUris__0: "urn:OtOpcUa:site-a-2" # FetchAndCache is MANDATORY for driver-only nodes (validator-enforced): they fetch the deployed # artifact from central over gRPC (endpoints below) and read ONLY the LocalDb cache — no central # SQL config read (they have no connection string). Central stays Direct + keeps ConfigServe. @@ -746,6 +758,9 @@ services: Cluster__Hostname: "0.0.0.0" Cluster__Port: "4053" Cluster__PublicHostname: "site-a-2" + # lmxopcua#801: distinct ApplicationUri + pair partner in ServerArray — see central-1. + OpcUa__ApplicationUri: "urn:OtOpcUa:site-a-2" + OpcUa__PeerApplicationUris__0: "urn:OtOpcUa:site-a-1" # Per-cluster mesh Phase 4: FetchAndCache mandatory for driver-only nodes (see site-a-1) — no # ConfigDb string; fetch the artifact from central over gRPC and read only the LocalDb cache. ConfigSource__Mode: "FetchAndCache" @@ -848,6 +863,9 @@ services: Cluster__Hostname: "0.0.0.0" Cluster__Port: "4053" Cluster__PublicHostname: "site-b-1" + # lmxopcua#801: distinct ApplicationUri + pair partner in ServerArray — see central-1. + OpcUa__ApplicationUri: "urn:OtOpcUa:site-b-1" + OpcUa__PeerApplicationUris__0: "urn:OtOpcUa:site-b-2" # Per-cluster mesh Phase 4: FetchAndCache mandatory for driver-only nodes (see site-a-1) — no # ConfigDb string; fetch the artifact from central over gRPC and read only the LocalDb cache. ConfigSource__Mode: "FetchAndCache" @@ -921,6 +939,9 @@ services: Cluster__Hostname: "0.0.0.0" Cluster__Port: "4053" Cluster__PublicHostname: "site-b-2" + # lmxopcua#801: distinct ApplicationUri + pair partner in ServerArray — see central-1. + OpcUa__ApplicationUri: "urn:OtOpcUa:site-b-2" + OpcUa__PeerApplicationUris__0: "urn:OtOpcUa:site-b-1" # Per-cluster mesh Phase 4: FetchAndCache mandatory for driver-only nodes (see site-a-1) — no # ConfigDb string; fetch the artifact from central over gRPC and read only the LocalDb cache. ConfigSource__Mode: "FetchAndCache"