0af0672597
vd03 (DEV) upgraded2b9a7c3c->fe89363bvia the deployment-only winrm channel: COPY_ONLY backup, 55->57 delta script (AddAlarmMesReportable, AddSiteArtifactRevision), ~53 s window, snapshots identical to baseline, artifacts deployed. Register row 126 now covers the QA pair only. docker/seed-mes-test-machine.sh (idempotent) builds a standalone TestMesMachine base template - Left/Right TestMESReceiver + TestDelmiaReceiver (prod attribute names, STATIC), computed alarms with IsMesReportable, CvdReactor-compatible MesMoveIn/MesMoveOut/ProcessRecipeDownload/AlarmStatus scripts ported to SetAsync+WaitAsync, and Galaxy-side simulators with reject/no-response knobs - plus instances SBTEST01/02 and the SimpleAlarmStatusRequest (was a stub) / AlarmStatusRequest inbound methods in the legacy /mes/(simple)alarmstatus shapes, resolving machines through BTDB. BTDB rows SBTEST01=990001 / SBTEST02=990002 added on vd03. docker/verify-mes-test-machine.sh drives the real inbound methods via flow-debug: PASS 25/25.
126 lines
8.3 KiB
Bash
Executable File
126 lines
8.3 KiB
Bash
Executable File
#!/bin/bash
|
|
set -euo pipefail
|
|
|
|
# Live gate for docker/seed-mes-test-machine.sh — drives the REAL inbound methods (MesMoveIn, MesMoveOut,
|
|
# DelmiaRecipeDownload, SimpleAlarmStatusRequest, AlarmStatusRequest) through `flow-debug run-method`
|
|
# (the same inbound executor an HTTP call reaches, minus the API-key check — no key needed) against the
|
|
# SBTEST01 / SBTEST02 test machines, and trips/clears the computed alarms by writing their static source
|
|
# attributes. Needs the BTDB rows SBTEST01=990001 / SBTEST02=990002 (vd03 only).
|
|
#
|
|
# SCADABRIDGE_URL=http://wonder-app-vd03.zmr.zimmer.com:8085 bash docker/verify-mes-test-machine.sh
|
|
#
|
|
# Leaves both machines as it found them: alarm sources reset to their defaults, no batch moved in.
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
export SB_CLI="$PROJECT_ROOT/src/ZB.MOM.WW.ScadaBridge.CLI/bin/Debug/net10.0/scadabridge"
|
|
export SB_URL="${SCADABRIDGE_URL:-http://localhost:9000}"
|
|
export SB_USER="${SCADABRIDGE_USERNAME:-multi-role}" SB_PASS="${SCADABRIDGE_PASSWORD:-password}"
|
|
|
|
python3 - <<'PY'
|
|
import json, os, subprocess, sys
|
|
|
|
CLI, URL = os.environ["SB_CLI"], os.environ["SB_URL"]
|
|
AUTH = ["--url", URL, "--format=json", "--username", os.environ["SB_USER"], "--password", os.environ["SB_PASS"]]
|
|
|
|
def cli(*args, timeout=180):
|
|
r = subprocess.run([CLI, *args, *AUTH], capture_output=True, text=True, timeout=timeout)
|
|
if r.returncode != 0:
|
|
raise RuntimeError(f"{' '.join(args[:3])}: {r.stderr.strip() or r.stdout.strip()}")
|
|
return json.loads(r.stdout)
|
|
|
|
methods = {m["name"]: m["id"] for m in cli("api-method", "list")}
|
|
instances = {i["uniqueName"]: i["id"] for i in cli("instance", "list")}
|
|
|
|
def call(method, params):
|
|
"""Run an inbound method via the Flow Debugger and return its response body (dict)."""
|
|
d = cli("flow-debug", "run-method", "--method-id", str(methods[method]), "--params", json.dumps(params),
|
|
"--timeout-seconds", "90", timeout=150)
|
|
ev = next(e for e in d["events"] if e["action"] == "ApiInbound.InboundRequest")
|
|
det = json.loads(ev["detailsJson"])
|
|
if det.get("status") != "Delivered":
|
|
raise RuntimeError(f"{method} not delivered: {det.get('status')} {det.get('errorMessage')}")
|
|
return json.loads(det["responseSummary"])
|
|
|
|
def write(inst, attr, value):
|
|
subprocess.run([CLI, "flow-debug", "run-value-change", "--instance-id", str(instances[inst]), "--attribute", attr,
|
|
"--value", str(value), "--wait-seconds", "1", *AUTH], capture_output=True, text=True, timeout=90)
|
|
|
|
passed = failed = 0
|
|
def check(label, cond, detail=""):
|
|
global passed, failed
|
|
if cond: passed += 1; print(f" PASS {label}")
|
|
else: failed += 1; print(f" FAIL {label} {detail}")
|
|
|
|
WO = [{"WorkOrderNumber": "WO-1001", "PartNumber": "PN-2001"}]
|
|
def movein(sap, wo=WO): return call("MesMoveIn", {"SAPID": sap, "ContainerNumber": "CN-1", "JobSequenceNumber": "10", "NumberWorkOrders": len(wo), "WorkOrders": wo, "OperatorName": "sb-test"})
|
|
def moveout(sap, wo=WO): return call("MesMoveOut", {"SAPID": sap, "ContainerNumber": "CN-1", "NumberWorkOrders": len(wo), "WorkOrders": wo, "OperatorName": "sb-test"})
|
|
def names(r): return sorted(a["Name"] for a in r.get("Alarms") or [])
|
|
|
|
print("A. DelmiaRecipeDownload (routes by MachineCode)")
|
|
r = call("DelmiaRecipeDownload", {"MachineCode": "SBTEST01", "DownloadPath": r"\\share\recipes\r1.xml", "WorkOrderNumber": "WO-1001", "PartNumber": "PN-2001", "JobStepNumber": "20", "Username": "sb-test"})
|
|
check("A1 accepted", r.get("Result") is True and "accepted" in r.get("ResultText", ""), r)
|
|
r = call("DelmiaRecipeDownload", {"MachineCode": "SBTEST01", "DownloadPath": r"\\share\recipes\r1.bad", "WorkOrderNumber": "WO-1001"})
|
|
check("A2 .bad path rejected", r.get("Result") is False and "reject" in r.get("ResultText", "").lower(), r)
|
|
|
|
print("B. MesMoveIn / MesMoveOut (SAPID -> BTDB -> SBTEST01)")
|
|
r = movein("990001_A")
|
|
batch = r.get("BatchID", 0)
|
|
check("B1 MoveIn left succeeds with a batch id", r.get("WasSuccessful") is True and batch >= 5000, r)
|
|
r = moveout("990001_A")
|
|
check("B2 MoveOut left returns the same batch", r.get("WasSuccessful") is True and r.get("BatchID") == batch, r)
|
|
check("B3 MoveOut Run returns 16 cycle rows", len(r.get("Data") or []) == 16 and r["Data"][0].get("ValueName") == "TargetRuntime", r.get("Data"))
|
|
r = moveout("990001_A")
|
|
check("B4 second MoveOut rejected (nothing moved in)", r.get("WasSuccessful") is False and "No batch" in r.get("ErrorText", ""), r)
|
|
r = movein("990001_A", [{"WorkOrderNumber": "WO-FAIL-1", "PartNumber": "PN-2001"}])
|
|
check("B5 FAIL work order rejected", r.get("WasSuccessful") is False and "FAIL" in r.get("ErrorText", ""), r)
|
|
r = movein("990001")
|
|
check("B6 missing side suffix", r.get("WasSuccessful") is False and "suffix" in r.get("ErrorText", ""), r)
|
|
r = movein("990001_B_LT"); lt = r.get("BatchID", 0)
|
|
check("B7 leak-test MoveIn right", r.get("WasSuccessful") is True and lt > 0, r)
|
|
r = moveout("990001_B_LT")
|
|
check("B8 leak-test MoveOut returns 6 LT rows", r.get("WasSuccessful") is True and len(r.get("Data") or []) == 6 and r["Data"][0]["ValueName"] == "LTType", r)
|
|
r = movein("999999_A")
|
|
check("B9 unknown SAPID", r.get("WasSuccessful") is False and "No machine found" in r.get("ErrorText", ""), r)
|
|
|
|
print("C. Alarm APIs")
|
|
r = call("SimpleAlarmStatusRequest", {"SAPID": "990001"})
|
|
check("C1 no alarms active -> success, empty", r.get("WasSuccessful") is True and names(r) == [], r)
|
|
for inst in ("SBTEST01", "SBTEST02"):
|
|
write(inst, "VacuumPressure", 12); write(inst, "DoorOpen", "true"); write(inst, "SensorStatus", -1)
|
|
r = call("SimpleAlarmStatusRequest", {"SAPID": "990001"})
|
|
check("C2 SBTEST01 simple = flagged only (HighVacuumFault)", names(r) == ["HighVacuumFault"], names(r))
|
|
a = (r.get("Alarms") or [{}])[0]
|
|
check("C3 row shape", a.get("HierarchicalName") == "SBTEST01.HighVacuumFault" and a.get("IsFlaggedForMES") is True
|
|
and a.get("Severity") == 800 and a.get("StatusCode") == "Triggered.Acked" and a.get("TriggeredDT"), a)
|
|
r = call("SimpleAlarmStatusRequest", {"SAPID": "990002"})
|
|
check("C4 SBTEST02 simple includes the overridden SensorFault", names(r) == ["HighVacuumFault", "SensorFault"], names(r))
|
|
r = call("AlarmStatusRequest", {"MachineFilter": {"Code": "SBTEST01"}})
|
|
check("C5 full, no filter = all 3 active", names(r) == ["DoorOpenAlarm", "HighVacuumFault", "SensorFault"], names(r))
|
|
flags = {x["Name"]: x["IsFlaggedForMES"] for x in r.get("Alarms") or []}
|
|
check("C6 IsFlaggedForMES per alarm", flags == {"DoorOpenAlarm": False, "HighVacuumFault": True, "SensorFault": False}, flags)
|
|
r = call("AlarmStatusRequest", {"MachineFilter": {"SAPID": "990001_A"}, "AlarmFilter": {"MinSeverity": 500}})
|
|
check("C7 SAPID w/ suffix + MinSeverity 500", names(r) == ["HighVacuumFault", "SensorFault"], names(r))
|
|
r = call("AlarmStatusRequest", {"MachineFilter": {"ZTag": "SBTEST01"}, "AlarmFilter": {"NameFilter": "door"}})
|
|
check("C8 ZTag + NameFilter", names(r) == ["DoorOpenAlarm"], names(r))
|
|
r = call("AlarmStatusRequest", {"MachineFilter": {"MachineID": 204}, "AlarmFilter": {"FlaggedOnly": True, "MaxSeverity": 750}})
|
|
check("C9 MachineID + FlaggedOnly + MaxSeverity", names(r) == ["SensorFault"], names(r))
|
|
r = call("AlarmStatusRequest", {"MachineFilter": {"Code": "SBTEST01"}, "AlarmFilter": {"IncludeAcked": False}})
|
|
check("C10 IncludeAcked=false drops computed (always-acked) alarms", r.get("WasSuccessful") is True and names(r) == [], names(r))
|
|
r = call("AlarmStatusRequest", {"MachineFilter": {"SAPID": "123"}})
|
|
check("C11 unknown SAPID error text", r.get("WasSuccessful") is False and "Failed to find machine with SAPID '123'" in (r.get("ErrorText") or ""), r)
|
|
r = call("AlarmStatusRequest", {})
|
|
check("C12 empty filter error", r.get("WasSuccessful") is False and "machine filter" in (r.get("ErrorText") or ""), r)
|
|
r = call("SimpleAlarmStatusRequest", {"SAPID": "103559"})
|
|
check("C13 machine without AlarmStatus script -> not supported", r.get("WasSuccessful") is False and "not supported" in (r.get("ErrorText") or ""), r)
|
|
|
|
print("D. Reset alarm sources")
|
|
for inst in ("SBTEST01", "SBTEST02"):
|
|
write(inst, "VacuumPressure", 0.5); write(inst, "DoorOpen", "false"); write(inst, "SensorStatus", 0)
|
|
r = call("AlarmStatusRequest", {"MachineFilter": {"Code": "SBTEST02"}})
|
|
check("D1 cleared", r.get("WasSuccessful") is True and names(r) == [], names(r))
|
|
|
|
print(f"\n{'PASS' if failed == 0 else 'FAIL'} {passed}/{passed + failed}")
|
|
sys.exit(1 if failed else 0)
|
|
PY
|