fix(reliability): encode str stdin in device_run; smoke drill path fixes + regression tests

This commit is contained in:
2026-08-22 16:23:50 -06:00
parent d954b6e90d
commit eae47d99bf
3 changed files with 36 additions and 7 deletions
@@ -107,6 +107,8 @@ SELF_PAYLOAD_KEY = os.environ.get('PAGER_SELF_PAYLOAD_KEY', 'user~remote_access~
def device_run(args, timeout=20, input_data=None): def device_run(args, timeout=20, input_data=None):
try: try:
if isinstance(input_data, str):
input_data = input_data.encode('utf-8')
p = subprocess.run(args, input=input_data, capture_output=True, timeout=timeout) p = subprocess.run(args, input=input_data, capture_output=True, timeout=timeout)
return p.returncode, p.stdout.decode('utf-8', 'replace'), p.stderr.decode('utf-8', 'replace') return p.returncode, p.stdout.decode('utf-8', 'replace'), p.stderr.decode('utf-8', 'replace')
except FileNotFoundError: except FileNotFoundError:
+22 -7
View File
@@ -39,6 +39,9 @@ set -u
BASE=/mmc/mk8 BASE=/mmc/mk8
REL="$BASE/releases/current" REL="$BASE/releases/current"
# Release layout: <release>/user/<category>/<key>/... — resolve the payload dir.
REL_PAY="$(find "$REL/user" -maxdepth 3 -name server.py 2>/dev/null | head -n 1)"
REL_PAY="${REL_PAY%/server.py}"
LEGACY=/root/payloads/user/remote_access/pager-webui LEGACY=/root/payloads/user/remote_access/pager-webui
URL=http://127.0.0.1:8080 URL=http://127.0.0.1:8080
GUARD_INIT=/etc/init.d/mk8-guard GUARD_INIT=/etc/init.d/mk8-guard
@@ -221,9 +224,9 @@ check_monitors() {
} }
check_versions() { check_versions() {
sv="$(grep '^SERVER_VERSION' "$REL/server.py" 2>/dev/null | head -n 1 \ sv="$(grep '^SERVER_VERSION' "$REL_PAY/server.py" 2>/dev/null | head -n 1 \
| sed -e 's/^SERVER_VERSION = //' -e "s/'//g" | tr -d '\r')" | sed -e 's/^SERVER_VERSION = //' -e "s/'//g" | tr -d '\r')"
pv="$(grep '^#[ ]*[Vv]ersion:' "$REL/payload.sh" 2>/dev/null | head -n 1 \ pv="$(grep '^#[ ]*[Vv]ersion:' "$REL_PAY/payload.sh" 2>/dev/null | head -n 1 \
| sed 's/^#[ ]*[Vv]ersion:[ ]*//' | tr -d '\r')" | sed 's/^#[ ]*[Vv]ersion:[ ]*//' | tr -d '\r')"
if [ -n "$sv" ] && [ "$sv" = "$pv" ]; then if [ -n "$sv" ] && [ "$sv" = "$pv" ]; then
pass "versions: release server.py and payload.sh agree ($sv)" pass "versions: release server.py and payload.sh agree ($sv)"
@@ -241,7 +244,7 @@ check_authed_api() {
fail 'authed API: python3 not found for response check' fail 'authed API: python3 not found for response check'
return return
fi fi
body='{"password":"'"$PASS"'"}' body='{"username":"root","password":"'"$PASS"'"}'
code="$(curl -fsS -m 10 -o /dev/null -w '%{http_code}' \ code="$(curl -fsS -m 10 -o /dev/null -w '%{http_code}' \
-c "$JAR" -H 'Content-Type: application/json' \ -c "$JAR" -H 'Content-Type: application/json' \
-d "$body" "$URL/api/login" 2>/dev/null)" -d "$body" "$URL/api/login" 2>/dev/null)"
@@ -272,7 +275,7 @@ drill_bad_values() {
uci set pineapd.wlan1mon.bands='2,5' uci set pineapd.wlan1mon.bands='2,5'
uci commit pineapd uci commit pineapd
"$PY" "$REL/server.py" --reconcile >/dev/null 2>&1 "$PY" "$REL_PAY/server.py" --reconcile >/dev/null 2>&1
got="$(uci_get pineapd.wlan1mon.bands)" got="$(uci_get pineapd.wlan1mon.bands)"
if [ "$got" = "5" ]; then if [ "$got" = "5" ]; then
pass 'drill: reconcile repaired bands 2,5 -> 5' pass 'drill: reconcile repaired bands 2,5 -> 5'
@@ -289,7 +292,7 @@ drill_bad_values() {
done done
uci set "pineapd.@ssidpool[0].ssid=${list# }" uci set "pineapd.@ssidpool[0].ssid=${list# }"
uci commit pineapd uci commit pineapd
"$PY" "$REL/server.py" --reconcile >/dev/null 2>&1 "$PY" "$REL_PAY/server.py" --reconcile >/dev/null 2>&1
got="$(uci_get 'pineapd.@ssidpool[0].ssid')" got="$(uci_get 'pineapd.@ssidpool[0].ssid')"
if [ -z "$got" ]; then if [ -z "$got" ]; then
pass 'drill: reconcile cleared oversized SSID pool' pass 'drill: reconcile cleared oversized SSID pool'
@@ -303,7 +306,7 @@ drill_bad_values() {
uci -q delete 'pineapd.@ssidpool[0].ssid' uci -q delete 'pineapd.@ssidpool[0].ssid'
fi fi
uci commit pineapd uci commit pineapd
"$PY" "$REL/server.py" --reconcile >/dev/null 2>&1 "$PY" "$REL_PAY/server.py" --reconcile >/dev/null 2>&1
} }
drill_role() { drill_role() {
@@ -368,12 +371,24 @@ PYEOF
} }
drill_watchdog() { drill_watchdog() {
WDOG="$REL/mk8-watchdog.sh" WDOG="$REL_PAY/mk8-watchdog.sh"
[ -f "$WDOG" ] || WDOG="$LEGACY/mk8-watchdog.sh" [ -f "$WDOG" ] || WDOG="$LEGACY/mk8-watchdog.sh"
if [ ! -f "$WDOG" ]; then if [ ! -f "$WDOG" ]; then
fail 'drill: mk8-watchdog.sh not found in release or legacy dir' fail 'drill: mk8-watchdog.sh not found in release or legacy dir'
return return
fi fi
# Ensure a profile exists: snapshot the live config as the drill target.
if [ ! -d "$BASE/profiles" ] || [ -z "$(ls "$BASE/profiles" 2>/dev/null)" ]; then
"$PY" - "$REL_PAY" <<'PYEOF' >/dev/null 2>&1 || {
import sys
sys.path.insert(0, sys.argv[1])
import mk8_profiles
mk8_profiles.snapshot('smoke-drill')
PYEOF
fail 'drill: could not snapshot a profile for watchdog drill'
return
}
fi
prof=lastknown-good prof=lastknown-good
[ -d "$BASE/profiles/$prof" ] \ [ -d "$BASE/profiles/$prof" ] \
|| prof="$(ls "$BASE/profiles" 2>/dev/null | head -n 1)" || prof="$(ls "$BASE/profiles" 2>/dev/null | head -n 1)"
+12
View File
@@ -69,3 +69,15 @@ class DeviceRunTest(unittest.TestCase):
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
class DeviceRunInputTest(unittest.TestCase):
def test_str_input_data_is_encoded(self):
rc, out, err = server.device_run(['cat'], input_data='uci import text')
self.assertEqual(rc, 0)
self.assertEqual(out, 'uci import text')
def test_bytes_input_data_passes_through(self):
rc, out, err = server.device_run(['cat'], input_data=b'raw')
self.assertEqual(rc, 0)
self.assertEqual(out, 'raw')