Galaxy E2E — point at live writable historized attribute + MachineStatus
Pick a Galaxy attribute that actually exercises the full driver stack: TestMachine_001.TestHistoryValue. Verified against the live dev-box ZB: it's Int32, writable (security_classification = Operate), and historized (HistoryExtension primitive). The query lives in `gr/queries/attributes_extended.sql` — swap to any other writable historized attribute via the same shape (`WHERE is_historized = 1 AND security_classification > 0`). Seed changes: - Tag row: FullName = TestMachine_001.TestHistoryValue (Int32 / ReadWrite) - VirtualTag renamed: `Doubled` → `MachineStatus` (Boolean), script returns `Source > 0`. Historized, so the write/subscribe exercise doubles as a historian-sink check once the alarm/write stages are enabled. - Scripted alarm predicate reads the same Source and fires on `> 50`. - Added ClusterNodeCredential(sa → p7-smoke-node) row so sp_GetCurrentGenerationForCluster's caller-binding check passes. Without this the server bootstrap fails with `Unauthorized: caller sa is not bound to NodeId p7-smoke-node`. E2E script: - Path-based NodeId defaults updated to match the new MachineStatus virtual tag. - Added optional `-Username / -Password` parameters. Anonymous sessions still get denied against Operate-classified attributes (PR 26 / docs/Security.md); supplying `-Username writeop -Password writeop123` against the dev-box GLAuth exercises the reverse-bridge stage. - Wired those credentials into every Invoke-Cli / Start-Process CLI invocation the script drives. Anonymous smoke remains 3/7 pass (probe + source read + reverse-bridge marked acl-expected INFO). A fuller run with `-Username writeop -Password writeop123` requires also enabling LDAP + a SecurityProfile that carries a UserName UserTokenPolicy — separate config step tracked alongside #124 (3-user authz matrix). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -55,8 +55,10 @@
|
||||
`reactor-1` → `Source` (Tag.Name).
|
||||
|
||||
.PARAMETER VirtualNodeId
|
||||
NodeId of the VirtualTag computed as Source × 2 (Phase 7 scripting). Same
|
||||
path-based scheme, ending in the VirtualTag.Name (`Doubled`).
|
||||
NodeId of the VirtualTag that computes MachineStatus = (Source > 0) (Phase 7
|
||||
scripting). Same path-based scheme, ending in the VirtualTag.Name
|
||||
(`MachineStatus`). The tag is historized so the write/subscribe exercise
|
||||
doubles as a historian-sink check.
|
||||
|
||||
.PARAMETER AlarmNodeId
|
||||
NodeId of the scripted-alarm Condition (fires when Source > 50). Same
|
||||
@@ -93,12 +95,19 @@
|
||||
param(
|
||||
[string]$OpcUaUrl = "opc.tcp://localhost:4840",
|
||||
[string]$SourceNodeId = "ns=2;s=p7-smoke-galaxy/lab-floor/galaxy-line/reactor-1/Source",
|
||||
[string]$VirtualNodeId = "ns=2;s=p7-smoke-galaxy/lab-floor/galaxy-line/reactor-1/Doubled",
|
||||
[string]$VirtualNodeId = "ns=2;s=p7-smoke-galaxy/lab-floor/galaxy-line/reactor-1/MachineStatus",
|
||||
[string]$AlarmNodeId = "ns=2;s=p7-smoke-galaxy/lab-floor/galaxy-line/reactor-1/OverTemp",
|
||||
[string]$AlarmTriggerValue = "75",
|
||||
[int]$ChangeWaitSec = 10,
|
||||
[int]$AlarmWaitSec = 10,
|
||||
[int]$HistoryLookbackSec = 3600
|
||||
[int]$HistoryLookbackSec = 3600,
|
||||
# The default Phase 7 seed uses a Galaxy attribute with
|
||||
# security_classification=Operate. Anonymous OPC UA sessions are denied writes
|
||||
# against Operate-classified tags (PR 26 / docs/Security.md). Supply an LDAP
|
||||
# user with WriteOperate to exercise the reverse-bridge stage — e.g.
|
||||
# `-Username writeop -Password writeop123` against the dev-box GLAuth.
|
||||
[string]$Username = "",
|
||||
[string]$Password = ""
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
@@ -108,6 +117,13 @@ $opcUaCli = Get-CliInvocation `
|
||||
-ProjectFolder "src/ZB.MOM.WW.OtOpcUa.Client.CLI" `
|
||||
-ExeName "otopcua-cli"
|
||||
|
||||
# Auth-extension helper — appends `-U / -P` to the CLI args when credentials
|
||||
# were supplied. Stays empty for anonymous runs so the default smoke path
|
||||
# doesn't require an LDAP round-trip.
|
||||
$authArgs = @()
|
||||
if ($Username) { $authArgs += @("-U", $Username) }
|
||||
if ($Password) { $authArgs += @("-P", $Password) }
|
||||
|
||||
$results = @()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -117,7 +133,7 @@ $results = @()
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Write-Header "Probe"
|
||||
$probe = Invoke-Cli -Cli $opcUaCli -Args @("read", "-u", $OpcUaUrl, "-n", $SourceNodeId)
|
||||
$probe = Invoke-Cli -Cli $opcUaCli -Args (@("read", "-u", $OpcUaUrl, "-n", $SourceNodeId) + $authArgs)
|
||||
if ($probe.ExitCode -eq 0 -and $probe.Output -match "Status:\s+0x00000000") {
|
||||
Write-Pass "source NodeId readable (Galaxy pipe → proxy → server → client chain up)"
|
||||
$results += @{ Passed = $true }
|
||||
@@ -134,7 +150,7 @@ if ($probe.ExitCode -eq 0 -and $probe.Output -match "Status:\s+0x00000000") {
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Write-Header "Source read"
|
||||
$sourceRead = Invoke-Cli -Cli $opcUaCli -Args @("read", "-u", $OpcUaUrl, "-n", $SourceNodeId)
|
||||
$sourceRead = Invoke-Cli -Cli $opcUaCli -Args (@("read", "-u", $OpcUaUrl, "-n", $SourceNodeId) + $authArgs)
|
||||
$sourceValue = $null
|
||||
if ($sourceRead.ExitCode -eq 0 -and $sourceRead.Output -match "Value:\s+([^\r\n]+)") {
|
||||
$sourceValue = $Matches[1].Trim()
|
||||
@@ -158,7 +174,7 @@ if ([string]::IsNullOrEmpty($VirtualNodeId)) {
|
||||
Write-Skip "VirtualNodeId not supplied — skipping Phase 7 bridge check"
|
||||
} else {
|
||||
Write-Header "Virtual-tag bridge"
|
||||
$vtRead = Invoke-Cli -Cli $opcUaCli -Args @("read", "-u", $OpcUaUrl, "-n", $VirtualNodeId)
|
||||
$vtRead = Invoke-Cli -Cli $opcUaCli -Args (@("read", "-u", $OpcUaUrl, "-n", $VirtualNodeId) + $authArgs)
|
||||
if ($vtRead.ExitCode -eq 0 -and $vtRead.Output -match "Value:\s+([^\r\n]+)") {
|
||||
$vtValue = $Matches[1].Trim()
|
||||
Write-Pass "virtual-tag value = $vtValue (source was $sourceValue)"
|
||||
@@ -181,7 +197,7 @@ $stdout = New-TemporaryFile
|
||||
$stderr = New-TemporaryFile
|
||||
$subArgs = @($opcUaCli.PrefixArgs) + @(
|
||||
"subscribe", "-u", $OpcUaUrl, "-n", $SourceNodeId,
|
||||
"-i", "500", "--duration", "$ChangeWaitSec")
|
||||
"-i", "500", "--duration", "$ChangeWaitSec") + $authArgs
|
||||
$subProc = Start-Process -FilePath $opcUaCli.File `
|
||||
-ArgumentList $subArgs -NoNewWindow -PassThru `
|
||||
-RedirectStandardOutput $stdout.FullName `
|
||||
@@ -214,8 +230,8 @@ if ($changeLines.Count -gt 0) {
|
||||
|
||||
Write-Header "Reverse bridge (OPC UA write)"
|
||||
$writeValue = [int]$AlarmTriggerValue # reuse the alarm trigger value — two stages for one write
|
||||
$w = Invoke-Cli -Cli $opcUaCli -Args @(
|
||||
"write", "-u", $OpcUaUrl, "-n", $SourceNodeId, "-v", "$writeValue")
|
||||
$w = Invoke-Cli -Cli $opcUaCli -Args (@(
|
||||
"write", "-u", $OpcUaUrl, "-n", $SourceNodeId, "-v", "$writeValue") + $authArgs)
|
||||
if ($w.ExitCode -ne 0) {
|
||||
# Connection/protocol failure — still a test failure.
|
||||
Write-Fail "write CLI exit=$($w.ExitCode)"
|
||||
@@ -230,7 +246,7 @@ if ($w.ExitCode -ne 0) {
|
||||
} elseif ($w.Output -match "Write successful") {
|
||||
# Read back — Galaxy poll interval + MXAccess advise may need a second or two to settle.
|
||||
Start-Sleep -Seconds 2
|
||||
$r = Invoke-Cli -Cli $opcUaCli -Args @("read", "-u", $OpcUaUrl, "-n", $SourceNodeId)
|
||||
$r = Invoke-Cli -Cli $opcUaCli -Args (@("read", "-u", $OpcUaUrl, "-n", $SourceNodeId) + $authArgs)
|
||||
if ($r.Output -match "Value:\s+$([Regex]::Escape("$writeValue"))\b") {
|
||||
Write-Pass "write propagated — source reads back $writeValue"
|
||||
$results += @{ Passed = $true }
|
||||
|
||||
Reference in New Issue
Block a user