Hold-to-fix for the Lighting PC and the LiveStream (OBS) PC
- OBS: start OBS on the LiveStream PC if it is not running, then reconnect Companion. If the PC does not answer, send Wake-on-LAN and show WAKING PC. - ARENA: start Resolume Arena on the Lighting PC if it is not running, then reconnect Companion. VISTA SILENT: start Vista if it is not running. A running OBS, Arena or Vista is never closed. - MIDI (LIGHT PC): restart the rtpMIDI service; it reconnects to its saved peers (NA Ingest, NA ProP, NA Tracks). - Fixes may set "reconnect" (restart the check's Companion connection after the command succeeds); hosts may set "mac"/"wake_broadcast" for Wake-on-LAN. - windows-setup.ps1 finds OBS on any drive and Vista, and leaves Wake-on-LAN alone when it is already on (changing it can reset the adapter). - Tests stub the fix executors so a hold in a test never reaches a machine. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -47,6 +47,10 @@ Row 0 holds the group buttons. Each one shows the first problem in its group, pl
|
|||||||
| APPS: PROPRES OFFLINE / NOT RUNNING | restarts ProPresenter on its Mac (the newest version installed) |
|
| APPS: PROPRES OFFLINE / NOT RUNNING | restarts ProPresenter on its Mac (the newest version installed) |
|
||||||
| APPS: ABLETON / TRANSLATE NOT RUNNING | starts Ableton on the Tracks Mac / Church Translate on NA Translation |
|
| APPS: ABLETON / TRANSLATE NOT RUNNING | starts Ableton on the Tracks Mac / Church Translate on NA Translation |
|
||||||
| APPS: BIBLE APP DOWN | restarts the Bible app on the Sanctuary TV PC (SSH + its "Start BibleApp" task from `remote-access/windows-setup.ps1`) |
|
| APPS: BIBLE APP DOWN | restarts the Bible app on the Sanctuary TV PC (SSH + its "Start BibleApp" task from `remote-access/windows-setup.ps1`) |
|
||||||
|
| APPS: VISTA SILENT | starts Vista on the Lighting PC if it is not running. A running Vista is never closed. |
|
||||||
|
| ARENA | starts Resolume Arena on the Lighting PC if it is not running, then reconnects Companion |
|
||||||
|
| OBS | starts OBS on the LiveStream PC if it is not running, then reconnects Companion. If the PC is off, it is woken (Wake-on-LAN) and the button shows WAKING PC; hold again once someone has logged in. |
|
||||||
|
| MIDI: LIGHT PC | restarts rtpMIDI on the Lighting PC; it reconnects to NA Ingest, NA ProP and NA Tracks by itself. Lighting MIDI drops for a few seconds. |
|
||||||
| DANTE: PROP MAC / TRACKS / TRANSL / INGEST GONE | starts Dante Virtual Soundcard's engine on that Mac (`remote-access/mac-dvs-helper.sh`) |
|
| DANTE: PROP MAC / TRACKS / TRANSL / INGEST GONE | starts Dante Virtual Soundcard's engine on that Mac (`remote-access/mac-dvs-helper.sh`) |
|
||||||
| DANTE: any device powered by its switch port (today the four Studio B boxes) | power-cycles the port it is on **now**, found by its MAC on the Netgear Dante switches |
|
| DANTE: any device powered by its switch port (today the four Studio B boxes) | power-cycles the port it is on **now**, found by its MAC on the Netgear Dante switches |
|
||||||
| DANTE: any other device GONE | restarts Companion's Dante module (hardware: if still gone, it is off or unplugged) |
|
| DANTE: any other device GONE | restarts Companion's Dante module (hardware: if still gone, it is off or unplugged) |
|
||||||
|
|||||||
+16
-6
@@ -32,7 +32,9 @@
|
|||||||
"prop": { "ssh": "napropresenter@10.1.14.225" },
|
"prop": { "ssh": "napropresenter@10.1.14.225" },
|
||||||
"tracks": { "ssh": "waterschurchmedia@10.1.3.227" },
|
"tracks": { "ssh": "waterschurchmedia@10.1.3.227" },
|
||||||
"translation": { "ssh": "studio@10.1.7.39" },
|
"translation": { "ssh": "studio@10.1.7.39" },
|
||||||
"bibletv": { "ssh": "media@10.6.7.13" }
|
"bibletv": { "ssh": "media@10.6.7.13" },
|
||||||
|
"lighting": { "ssh": "media@10.1.10.206" },
|
||||||
|
"livestream": { "ssh": "media@10.1.9.126", "mac": "6c:24:08:d7:fb:57", "wake_broadcast": "10.1.255.255" }
|
||||||
},
|
},
|
||||||
|
|
||||||
"fixes": {
|
"fixes": {
|
||||||
@@ -52,6 +54,14 @@
|
|||||||
"run": "launchctl kickstart -k gui/$(id -u)/org.waterschurch.lobby-captions || launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/org.waterschurch.lobby-captions.plist" },
|
"run": "launchctl kickstart -k gui/$(id -u)/org.waterschurch.lobby-captions || launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/org.waterschurch.lobby-captions.plist" },
|
||||||
"bibleapp": { "host": "bibletv", "short": "BIBLE APP", "label": "restart the Bible app on the Sanctuary TV PC (Windows: cmd.exe)",
|
"bibleapp": { "host": "bibletv", "short": "BIBLE APP", "label": "restart the Bible app on the Sanctuary TV PC (Windows: cmd.exe)",
|
||||||
"run": "taskkill /F /T /IM \"Bible App.exe\" >nul 2>&1 & ping -n 3 127.0.0.1 >nul & schtasks /Run /TN \"\\StatusPanel\\Start BibleApp\"" },
|
"run": "taskkill /F /T /IM \"Bible App.exe\" >nul 2>&1 & ping -n 3 127.0.0.1 >nul & schtasks /Run /TN \"\\StatusPanel\\Start BibleApp\"" },
|
||||||
|
"obs": { "host": "livestream", "short": "OBS", "label": "start OBS on the LiveStream PC if it is not running (wakes the PC if it is off), then reconnect Companion", "reconnect": true,
|
||||||
|
"run": "tasklist /NH /FI \"IMAGENAME eq obs64.exe\" | find /I \"obs64.exe\" >nul || schtasks /Run /TN \"\\StatusPanel\\Start OBS\"" },
|
||||||
|
"arena": { "host": "lighting", "short": "ARENA", "label": "start Resolume Arena on the Lighting PC if it is not running, then reconnect Companion", "reconnect": true,
|
||||||
|
"run": "tasklist /NH /FI \"IMAGENAME eq Arena.exe\" | find /I \"Arena.exe\" >nul || schtasks /Run /TN \"\\StatusPanel\\Start Arena\"" },
|
||||||
|
"vista": { "host": "lighting", "short": "VISTA", "label": "start Vista on the Lighting PC if it is not running (a running Vista is never closed)",
|
||||||
|
"run": "tasklist /NH /FI \"IMAGENAME eq Vista.exe\" | find /I \"Vista.exe\" >nul || schtasks /Run /TN \"\\StatusPanel\\Start Vista\"" },
|
||||||
|
"rtpmidi": { "host": "lighting", "short": "RTPMIDI", "label": "restart rtpMIDI on the Lighting PC: it reconnects to NA Ingest, NA ProP and NA Tracks (MIDI drops for a few seconds)",
|
||||||
|
"run": "powershell -NoProfile -NonInteractive -Command \"Restart-Service rtpMIDIService -Force\"" },
|
||||||
"proddash": { "kind": "local", "short": "PRODDASH", "label": "start ProdDash", "run": "open /Applications/ProdDash.app" },
|
"proddash": { "kind": "local", "short": "PRODDASH", "label": "start ProdDash", "run": "open /Applications/ProdDash.app" },
|
||||||
"hyperdeck": { "kind": "hyperdeck", "short": "HYPERDECK", "label": "reconnect Companion to the HyperDeck, or reboot the deck only if it is hung" }
|
"hyperdeck": { "kind": "hyperdeck", "short": "HYPERDECK", "label": "reconnect Companion to the HyperDeck, or reboot the deck only if it is hung" }
|
||||||
},
|
},
|
||||||
@@ -88,9 +98,9 @@
|
|||||||
"NA-Tracks": "TRACKS", "FOH-SuperRack-LiveBox": "SUPERRACK", "NA-ProPresenter-Mac": "PROP MAC" } }
|
"NA-Tracks": "TRACKS", "FOH-SuperRack-LiveBox": "SUPERRACK", "NA-ProPresenter-Mac": "PROP MAC" } }
|
||||||
] },
|
] },
|
||||||
{ "id": "midi", "label": "MIDI", "pos": [2, 3], "checks": [
|
{ "id": "midi", "label": "MIDI", "pos": [2, 3], "checks": [
|
||||||
{ "type": "midi_peer", "session": "NA Ingest", "peer": "Lighting PC", "tag": "LIGHT PC" },
|
{ "type": "midi_peer", "session": "NA Ingest", "peer": "Lighting PC", "tag": "LIGHT PC", "fix": "rtpmidi" },
|
||||||
{ "type": "midi_peer", "session": "NA Ingest", "peer": "SuperRack-LiveBox", "tag": "SUPERRACK" },
|
{ "type": "midi_peer", "session": "NA Ingest", "peer": "SuperRack-LiveBox", "tag": "SUPERRACK" },
|
||||||
{ "type": "mdns", "instance": "Lighting PC", "name": "LIGHT PC MIDI" },
|
{ "type": "mdns", "instance": "Lighting PC", "name": "LIGHT PC MIDI", "fix": "rtpmidi" },
|
||||||
{ "type": "mdns", "instance": "NA Tracks", "name": "TRACKS MIDI" },
|
{ "type": "mdns", "instance": "NA Tracks", "name": "TRACKS MIDI" },
|
||||||
{ "type": "midi_rx", "source": "Network NA Ingest Listen", "name": "TRACKS IN" }
|
{ "type": "midi_rx", "source": "Network NA Ingest Listen", "name": "TRACKS IN" }
|
||||||
] },
|
] },
|
||||||
@@ -101,14 +111,14 @@
|
|||||||
{ "type": "remote", "host": "tracks", "cmd": "pgrep -xq Live", "msg": "ABLETON NOT RUNNING", "fix": "ableton" },
|
{ "type": "remote", "host": "tracks", "cmd": "pgrep -xq Live", "msg": "ABLETON NOT RUNNING", "fix": "ableton" },
|
||||||
{ "type": "remote", "host": "translation", "cmd": "pgrep -xq 'Church Translate'", "msg": "TRANSLATE NOT RUNNING", "fix": "translate" },
|
{ "type": "remote", "host": "translation", "cmd": "pgrep -xq 'Church Translate'", "msg": "TRANSLATE NOT RUNNING", "fix": "translate" },
|
||||||
{ "type": "ping", "host": "10.1.3.227", "name": "TRACKS MAC" },
|
{ "type": "ping", "host": "10.1.3.227", "name": "TRACKS MAC" },
|
||||||
{ "type": "artnet", "source": "10.1.10.206", "max_silence_seconds": 5, "msg": "VISTA SILENT" },
|
{ "type": "artnet", "source": "10.1.10.206", "max_silence_seconds": 5, "msg": "VISTA SILENT", "fix": "vista" },
|
||||||
{ "type": "http", "url": "http://10.6.7.13:8080/", "msg": "BIBLE APP DOWN", "interval_seconds": 5, "fix": "bibleapp" },
|
{ "type": "http", "url": "http://10.6.7.13:8080/", "msg": "BIBLE APP DOWN", "interval_seconds": 5, "fix": "bibleapp" },
|
||||||
{ "type": "conn", "conn": "BibleTV", "tag": "BIBLE OSC" }
|
{ "type": "conn", "conn": "BibleTV", "tag": "BIBLE OSC" }
|
||||||
] },
|
] },
|
||||||
|
|
||||||
{ "id": "service", "label": "ALARMS", "pos": [3, 0], "kind": "service" },
|
{ "id": "service", "label": "ALARMS", "pos": [3, 0], "kind": "service" },
|
||||||
{ "id": "atem", "label": "ATEM", "pos": [3, 1], "checks": [{ "type": "conn", "conn": "ATEM" }] },
|
{ "id": "atem", "label": "ATEM", "pos": [3, 1], "checks": [{ "type": "conn", "conn": "ATEM" }] },
|
||||||
{ "id": "obs", "label": "OBS", "pos": [3, 2], "checks": [{ "type": "conn", "conn": "OBS-Youtube", "service_only": true }] },
|
{ "id": "obs", "label": "OBS", "pos": [3, 2], "checks": [{ "type": "conn", "conn": "OBS-Youtube", "service_only": true, "fix": "obs" }] },
|
||||||
{ "id": "resi", "label": "RESI", "pos": [3, 3], "checks": [
|
{ "id": "resi", "label": "RESI", "pos": [3, 3], "checks": [
|
||||||
{ "type": "conn", "conn": "RESI_Decoder", "tag": "DECODER" },
|
{ "type": "conn", "conn": "RESI_Decoder", "tag": "DECODER" },
|
||||||
{ "type": "ping", "host": "10.1.10.203", "name": "DECODER" },
|
{ "type": "ping", "host": "10.1.10.203", "name": "DECODER" },
|
||||||
@@ -128,7 +138,7 @@
|
|||||||
{ "type": "ping", "host": "10.1.14.116", "name": "VX1000" },
|
{ "type": "ping", "host": "10.1.14.116", "name": "VX1000" },
|
||||||
{ "type": "ping", "host": "10.1.14.115", "name": "UHDJR" }
|
{ "type": "ping", "host": "10.1.14.115", "name": "UHDJR" }
|
||||||
] },
|
] },
|
||||||
{ "id": "arena", "label": "ARENA", "pos": [3, 7], "checks": [{ "type": "conn", "conn": "arena" }] },
|
{ "id": "arena", "label": "ARENA", "pos": [3, 7], "checks": [{ "type": "conn", "conn": "arena", "fix": "arena" }] },
|
||||||
|
|
||||||
{ "id": "axient1", "label": "AXIENT 1", "checks": [{ "type": "shure", "conn": "Axient_1", "channels": 4 }] },
|
{ "id": "axient1", "label": "AXIENT 1", "checks": [{ "type": "shure", "conn": "Axient_1", "channels": 4 }] },
|
||||||
{ "id": "axient2", "label": "AXIENT 2", "checks": [{ "type": "shure", "conn": "Axient_2", "channels": 4 }] },
|
{ "id": "axient2", "label": "AXIENT 2", "checks": [{ "type": "shure", "conn": "Axient_2", "channels": 4 }] },
|
||||||
|
|||||||
@@ -68,8 +68,13 @@ $roots = @($env:ProgramFiles, ${env:ProgramFiles(x86)}, (Join-Path $env:LOCALAPP
|
|||||||
$bible = Get-ChildItem -Path $roots -Filter '*.exe' -Recurse -Depth 3 -ErrorAction SilentlyContinue |
|
$bible = Get-ChildItem -Path $roots -Filter '*.exe' -Recurse -Depth 3 -ErrorAction SilentlyContinue |
|
||||||
Where-Object { $_.FullName -match 'bible' -and $_.Name -notmatch 'uninst|update|crash|helper' } |
|
Where-Object { $_.FullName -match 'bible' -and $_.Name -notmatch 'uninst|update|crash|helper' } |
|
||||||
Select-Object -First 1 -ExpandProperty FullName
|
Select-Object -First 1 -ExpandProperty FullName
|
||||||
|
# Apps can live on any local drive (the LiveStream PC has OBS on D:, the Lighting PC has Vista on G:).
|
||||||
|
$drives = Get-PSDrive -PSProvider FileSystem | Where-Object { $_.Free -ne $null } | ForEach-Object { $_.Root.TrimEnd('\') }
|
||||||
|
$vista = Find-First (@($drives | ForEach-Object { "$_\Vista3\Vista.exe" }) +
|
||||||
|
@(Get-ChildItem -Path $roots -Filter 'Vista.exe' -Recurse -Depth 3 -ErrorAction SilentlyContinue | ForEach-Object FullName))
|
||||||
$apps = @(
|
$apps = @(
|
||||||
@{ Name = 'OBS'; Exe = (Find-First @("$env:ProgramFiles\obs-studio\bin\64bit\obs64.exe")); Args = '--disable-shutdown-check' },
|
@{ Name = 'OBS'; Exe = (Find-First @($drives | ForEach-Object { "$_\Program Files\obs-studio\bin\64bit\obs64.exe" })); Args = '--disable-shutdown-check' },
|
||||||
|
@{ Name = 'Vista'; Exe = $vista; Args = '' },
|
||||||
@{ Name = 'Arena'; Exe = (Find-First (Get-ChildItem "$env:ProgramFiles\Resolume Arena*\Arena.exe" -ErrorAction SilentlyContinue | ForEach-Object FullName)); Args = '' },
|
@{ Name = 'Arena'; Exe = (Find-First (Get-ChildItem "$env:ProgramFiles\Resolume Arena*\Arena.exe" -ErrorAction SilentlyContinue | ForEach-Object FullName)); Args = '' },
|
||||||
@{ Name = 'rtpMIDI'; Exe = (Find-First @("${env:ProgramFiles(x86)}\Tobias Erichsen\rtpMIDI\rtpMIDI.exe", "$env:ProgramFiles\Tobias Erichsen\rtpMIDI\rtpMIDI.exe")); Args = '' },
|
@{ Name = 'rtpMIDI'; Exe = (Find-First @("${env:ProgramFiles(x86)}\Tobias Erichsen\rtpMIDI\rtpMIDI.exe", "$env:ProgramFiles\Tobias Erichsen\rtpMIDI\rtpMIDI.exe")); Args = '' },
|
||||||
@{ Name = 'BibleApp'; Exe = $bible; Args = ''; AtLogon = $true } # also opens it 15 s after login
|
@{ Name = 'BibleApp'; Exe = $bible; Args = ''; AtLogon = $true } # also opens it 15 s after login
|
||||||
@@ -96,6 +101,8 @@ foreach ($app in $apps) {
|
|||||||
# 4. Wake-on-LAN: let the wired adapter wake the PC (the BIOS setting must be on too)
|
# 4. Wake-on-LAN: let the wired adapter wake the PC (the BIOS setting must be on too)
|
||||||
$nics = Get-NetAdapter -Physical -ErrorAction SilentlyContinue | Where-Object Status -eq 'Up'
|
$nics = Get-NetAdapter -Physical -ErrorAction SilentlyContinue | Where-Object Status -eq 'Up'
|
||||||
foreach ($nic in $nics) {
|
foreach ($nic in $nics) {
|
||||||
|
# Only when it is off: changing it can briefly reset the adapter (and the PC's network).
|
||||||
|
if ((Get-NetAdapterPowerManagement -Name $nic.Name -ErrorAction SilentlyContinue).WakeOnMagicPacket -eq 'Enabled') { continue }
|
||||||
try { Set-NetAdapterPowerManagement -Name $nic.Name -WakeOnMagicPacket Enabled -ErrorAction Stop }
|
try { Set-NetAdapterPowerManagement -Name $nic.Name -WakeOnMagicPacket Enabled -ErrorAction Stop }
|
||||||
catch { $notes += "Wake-on-LAN not settable on $($nic.Name)" }
|
catch { $notes += "Wake-on-LAN not settable on $($nic.Name)" }
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-2
@@ -877,10 +877,12 @@ class Engine:
|
|||||||
|
|
||||||
def work(fn, args):
|
def work(fn, args):
|
||||||
ok = fn(*args)
|
ok = fn(*args)
|
||||||
results.append(ok is not False)
|
results.append(ok)
|
||||||
if len(results) == len(started): # the last one to finish reports for all
|
if len(results) == len(started): # the last one to finish reports for all
|
||||||
done = time.time()
|
done = time.time()
|
||||||
if all(results):
|
if WAKING in results:
|
||||||
|
self.note(tile_id, "WAKING PC\n" + summary, "fixing", done, 120)
|
||||||
|
elif all(r is not False for r in results):
|
||||||
self.note(tile_id, "CHECKING\n" + summary, "fixing", done, 20)
|
self.note(tile_id, "CHECKING\n" + summary, "fixing", done, 20)
|
||||||
else:
|
else:
|
||||||
self.note(tile_id, "FIX FAILED\n" + summary, "failed", done, 20)
|
self.note(tile_id, "FIX FAILED\n" + summary, "failed", done, 20)
|
||||||
@@ -1141,6 +1143,22 @@ def ssh_cmd(cfg, host):
|
|||||||
"-o", "ControlPath=/tmp/status-panel-ssh-%C", "-o", "ControlPersist=120", cfg["hosts"][host]["ssh"]]
|
"-o", "ControlPath=/tmp/status-panel-ssh-%C", "-o", "ControlPersist=120", cfg["hosts"][host]["ssh"]]
|
||||||
|
|
||||||
|
|
||||||
|
WAKING = "waking" # execute_fix result: the PC was off, a Wake-on-LAN packet went out
|
||||||
|
|
||||||
|
|
||||||
|
def wake_on_lan(mac, broadcast=None, port=9):
|
||||||
|
"""Send a Wake-on-LAN magic packet (to the PC's subnet broadcast as well, when given)."""
|
||||||
|
raw = bytes.fromhex(re.sub(r"[^0-9A-Fa-f]", "", mac))
|
||||||
|
if len(raw) != 6:
|
||||||
|
raise ValueError("not a MAC address: %r" % mac)
|
||||||
|
packet = b"\xff" * 6 + raw * 16
|
||||||
|
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
|
||||||
|
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||||
|
for addr in [a for a in (broadcast, "255.255.255.255") if a]:
|
||||||
|
s.sendto(packet, (addr, port))
|
||||||
|
return packet
|
||||||
|
|
||||||
|
|
||||||
def remote_script(cmds):
|
def remote_script(cmds):
|
||||||
"""One shell line that runs every check and prints SP<n>=<exit code>."""
|
"""One shell line that runs every check and prints SP<n>=<exit code>."""
|
||||||
return "; ".join("(%s) >/dev/null 2>&1; echo SP%d=$?" % (cmd, i) for i, cmd in enumerate(cmds))
|
return "; ".join("(%s) >/dev/null 2>&1; echo SP%d=$?" % (cmd, i) for i, cmd in enumerate(cmds))
|
||||||
@@ -1300,8 +1318,21 @@ def execute_fix(cfg, fix_id, chk):
|
|||||||
argv = ssh_cmd(cfg, fix["host"]) + [fix["run"]] if kind == "ssh" else ["/bin/zsh", "-c", fix["run"]]
|
argv = ssh_cmd(cfg, fix["host"]) + [fix["run"]] if kind == "ssh" else ["/bin/zsh", "-c", fix["run"]]
|
||||||
r = subprocess.run(argv, capture_output=True, text=True, timeout=fix.get("timeout", 60))
|
r = subprocess.run(argv, capture_output=True, text=True, timeout=fix.get("timeout", 60))
|
||||||
detail = (r.stdout + r.stderr).strip().replace("\n", " ")[-200:]
|
detail = (r.stdout + r.stderr).strip().replace("\n", " ")[-200:]
|
||||||
|
host = cfg.get("hosts", {}).get(fix.get("host"), {}) if kind == "ssh" else {}
|
||||||
|
if r.returncode == 255 and host.get("mac"):
|
||||||
|
# ssh could not reach the PC: it is probably off or asleep. Wake it; hold again
|
||||||
|
# once it is up (the app starts only after someone is logged in).
|
||||||
|
wake_on_lan(host["mac"], host.get("wake_broadcast"))
|
||||||
|
log.info("FIX %s -> %s does not answer: Wake-on-LAN sent to %s", label, fix["host"], host["mac"])
|
||||||
|
return WAKING
|
||||||
log.info("FIX %s -> %s%s", label, "ok" if r.returncode == 0 else "exit %d" % r.returncode,
|
log.info("FIX %s -> %s%s", label, "ok" if r.returncode == 0 else "exit %d" % r.returncode,
|
||||||
(": " + detail) if detail else "")
|
(": " + detail) if detail else "")
|
||||||
|
if r.returncode == 0 and fix.get("reconnect") and chk.get("conn"):
|
||||||
|
# The app is running now: make Companion reconnect to it instead of waiting for its retry.
|
||||||
|
comp = Companion(cfg["companion"].get("host", "127.0.0.1"), cfg["companion"].get("port", 8000))
|
||||||
|
ids = {c["label"]: c["id"] for c in comp.connections()}
|
||||||
|
ok = chk["conn"] in ids and comp.restart_connection(ids[chk["conn"]])
|
||||||
|
log.info("FIX %s -> Companion connection %s restarted %s", label, chk["conn"], "ok" if ok else "FAILED")
|
||||||
return r.returncode == 0
|
return r.returncode == 0
|
||||||
if kind == "netgear_poe":
|
if kind == "netgear_poe":
|
||||||
# Netgear AV switch on the Dante network: power-cycle one PoE port (netgear.py).
|
# Netgear AV switch on the Dante network: power-cycle one PoE port (netgear.py).
|
||||||
|
|||||||
@@ -465,6 +465,8 @@ class TestFixes(unittest.TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.snap = healthy_snapshot(SUNDAY)
|
self.snap = healthy_snapshot(SUNDAY)
|
||||||
self.engine = sp.Engine(CONFIG)
|
self.engine = sp.Engine(CONFIG)
|
||||||
|
# Safety net: a hold in these tests must never reach a real machine.
|
||||||
|
self.engine.executor = self.engine.poe_executor = lambda *a: True
|
||||||
|
|
||||||
def plan(self, tile):
|
def plan(self, tile):
|
||||||
run_steps(self.engine, self.snap, SUNDAY, 4)
|
run_steps(self.engine, self.snap, SUNDAY, 4)
|
||||||
@@ -492,6 +494,59 @@ class TestFixes(unittest.TestCase):
|
|||||||
self.assertEqual(CONFIG["hosts"][fix["host"]]["ssh"], "media@10.6.7.13")
|
self.assertEqual(CONFIG["hosts"][fix["host"]]["ssh"], "media@10.6.7.13")
|
||||||
self.assertIn('schtasks /Run /TN "\\StatusPanel\\Start BibleApp"', fix["run"]) # cmd.exe, not sh
|
self.assertIn('schtasks /Run /TN "\\StatusPanel\\Start BibleApp"', fix["run"]) # cmd.exe, not sh
|
||||||
|
|
||||||
|
def test_lighting_and_livestream_pcs(self):
|
||||||
|
self.snap.conns["OBS-Youtube"]["category"] = "error"
|
||||||
|
self.snap.conns["arena"]["category"] = "error"
|
||||||
|
self.snap.artnet_since, self.snap.artnet_last = SUNDAY - 100, {"10.1.10.206": SUNDAY - 100}
|
||||||
|
self.snap.midi["NA Ingest"]["peers"] = [p for p in self.snap.midi["NA Ingest"]["peers"]
|
||||||
|
if p["name"] != "Lighting PC"]
|
||||||
|
self.snap.mdns = self.snap.mdns - {"Lighting PC"}
|
||||||
|
self.assertEqual(self.plan("obs"), [("fix", "obs")])
|
||||||
|
self.assertEqual(self.plan("arena"), [("fix", "arena")])
|
||||||
|
self.assertEqual(self.plan("apps"), [("fix", "vista")])
|
||||||
|
self.assertEqual(self.plan("midi"), [("fix", "rtpmidi")]) # two checks, one restart
|
||||||
|
for fix_id in ("obs", "arena", "vista"): # never closes a running app
|
||||||
|
self.assertTrue(CONFIG["fixes"][fix_id]["run"].startswith("tasklist "), fix_id)
|
||||||
|
|
||||||
|
def test_an_off_pc_is_woken(self):
|
||||||
|
from unittest import mock
|
||||||
|
woke = []
|
||||||
|
done = mock.Mock(returncode=255, stdout="", stderr="ssh: connect to host 10.1.9.126 port 22: timed out")
|
||||||
|
with mock.patch.object(sp.subprocess, "run", return_value=done), \
|
||||||
|
mock.patch.object(sp, "wake_on_lan", side_effect=lambda mac, bc=None: woke.append((mac, bc))):
|
||||||
|
self.assertEqual(sp.execute_fix(CONFIG, "obs", {"conn": "OBS-Youtube"}), sp.WAKING)
|
||||||
|
self.assertEqual(woke, [("6c:24:08:d7:fb:57", "10.1.255.255")])
|
||||||
|
self.assertFalse(sp.execute_fix(CONFIG, "arena", {"conn": "arena"})) # no MAC: just fails
|
||||||
|
self.assertEqual(len(woke), 1)
|
||||||
|
# the button says so
|
||||||
|
self.snap.conns["OBS-Youtube"]["category"] = "error"
|
||||||
|
self.engine.executor = lambda cfg, fix_id, chk: sp.WAKING
|
||||||
|
_, t = run_steps(self.engine, self.snap, SUNDAY, 4)
|
||||||
|
self.engine.run_fixes("obs", self.snap, None, t)
|
||||||
|
time.sleep(0.2)
|
||||||
|
self.assertEqual(self.engine.step(self.snap, time.time())["obs"]["x"], "OBS\nWAKING PC\nOBS")
|
||||||
|
|
||||||
|
def test_magic_packet(self):
|
||||||
|
from unittest import mock
|
||||||
|
with mock.patch.object(sp.socket, "socket") as sock:
|
||||||
|
packet = sp.wake_on_lan("6c:24:08:d7:fb:57", "10.1.255.255")
|
||||||
|
self.assertEqual(packet, b"\xff" * 6 + bytes.fromhex("6c2408d7fb57") * 16)
|
||||||
|
sent = [c.args[1] for c in sock.return_value.__enter__.return_value.sendto.call_args_list]
|
||||||
|
self.assertEqual(sent, [("10.1.255.255", 9), ("255.255.255.255", 9)])
|
||||||
|
|
||||||
|
def test_companion_reconnects_after_the_app_starts(self):
|
||||||
|
from unittest import mock
|
||||||
|
comp = mock.Mock()
|
||||||
|
comp.connections.return_value = [{"label": "arena", "id": "abc"}]
|
||||||
|
ok = mock.Mock(returncode=0, stdout="", stderr="")
|
||||||
|
with mock.patch.object(sp.subprocess, "run", return_value=ok), \
|
||||||
|
mock.patch.object(sp, "Companion", return_value=comp):
|
||||||
|
self.assertTrue(sp.execute_fix(CONFIG, "arena", {"conn": "arena"}))
|
||||||
|
comp.restart_connection.assert_called_once_with("abc")
|
||||||
|
comp.reset_mock()
|
||||||
|
self.assertTrue(sp.execute_fix(CONFIG, "vista", {"type": "artnet"})) # no "reconnect"
|
||||||
|
comp.restart_connection.assert_not_called()
|
||||||
|
|
||||||
def test_unreachable_mac_is_not_a_verdict(self):
|
def test_unreachable_mac_is_not_a_verdict(self):
|
||||||
self.snap.remote["tracks\0pgrep -xq Live"] = None
|
self.snap.remote["tracks\0pgrep -xq Live"] = None
|
||||||
self.assertEqual(self.plan("apps"), [])
|
self.assertEqual(self.plan("apps"), [])
|
||||||
@@ -632,7 +687,8 @@ class TestFixes(unittest.TestCase):
|
|||||||
self.snap.vars[("audinate-dantecontroller", "devices")] = json.dumps(devices)
|
self.snap.vars[("audinate-dantecontroller", "devices")] = json.dumps(devices)
|
||||||
self.assertEqual(self.plan("dante"), [("fix", "dvs_ingest")])
|
self.assertEqual(self.plan("dante"), [("fix", "dvs_ingest")])
|
||||||
# A failure with no fix must not claim there is nothing wrong.
|
# A failure with no fix must not claim there is nothing wrong.
|
||||||
self.snap.midi["NA Ingest"]["peers"] = []
|
self.snap.midi["NA Ingest"]["peers"] = [p for p in self.snap.midi["NA Ingest"]["peers"]
|
||||||
|
if p["name"] != "SuperRack-LiveBox"]
|
||||||
_, t = run_steps(self.engine, self.snap, SUNDAY, 4)
|
_, t = run_steps(self.engine, self.snap, SUNDAY, 4)
|
||||||
self.engine.run_fixes("midi", self.snap, None, t)
|
self.engine.run_fixes("midi", self.snap, None, t)
|
||||||
self.assertEqual(self.engine.notes["midi"]["text"], "NO AUTO\nFIX")
|
self.assertEqual(self.engine.notes["midi"]["text"], "NO AUTO\nFIX")
|
||||||
|
|||||||
Reference in New Issue
Block a user