feat: initial QNAP QTS driver scaffold

QTS is a Linux distribution, so the driver inherits the OS surface from
LinuxDriver and adds QNAP's storage, QPKG and virtualisation layers.

What is here: the class with its discovery fingerprints, per-session
detection of the QTS major version and of the QPKG-local docker and virsh
binaries, and explicit resolution of the MRO collisions that StorageDriver
creates over LinuxDriver.

What is not: the storage, QPKG and VM parsers. Those need real command
output from QTS 4 and QTS 5 hardware to be written against, which is what
tools/harvest.sh collects and tools/sanitize.py anonymises. Two tests are
marked xfail(strict) as the specification for that work.
This commit is contained in:
2026-08-21 09:53:13 +07:00
commit 26d723f417
11 changed files with 893 additions and 0 deletions
+105
View File
@@ -0,0 +1,105 @@
#!/usr/bin/env bash
# Collect the command outputs this driver parses, from a real QNAP NAS.
#
# Run once against a QTS 4 box and once against a QTS 5 box; the resulting
# files become the test fixtures. Writing parsers against guessed output is how
# a driver ends up passing its own tests and failing on hardware, so this runs
# before any parser code.
#
# ./tools/harvest.sh admin@nas1.example.lan qts4
# ./tools/harvest.sh admin@nas2.example.lan qts5
#
# Output lands in tools/harvest-out/<label>/ (gitignored). Sanitise with
# ./tools/sanitize.py before pasting anything into a test file.
set -uo pipefail
TARGET="${1:?usage: harvest.sh <user@host> <label> [ssh-port]}"
LABEL="${2:?usage: harvest.sh <user@host> <label> [ssh-port]}"
PORT="${3:-22}"
OUT="$(dirname "$0")/harvest-out/$LABEL"
mkdir -p "$OUT"
# One SSH session for the whole run: QTS is slow to authenticate, and opening a
# connection per command turns a 30-second harvest into several minutes.
CTL="$(mktemp -u /tmp/qnap-harvest-XXXXXX)"
cleanup() { ssh -O exit -o ControlPath="$CTL" "$TARGET" 2>/dev/null || true; }
trap cleanup EXIT
ssh -M -o ControlPath="$CTL" -o ControlPersist=120 -p "$PORT" -N "$TARGET" &
sleep 3
run() {
local name="$1"; shift
local cmd="$*"
printf ' %-28s %s\n' "$name" "$cmd"
{
echo "### command: $cmd"
ssh -o ControlPath="$CTL" -p "$PORT" "$TARGET" "$cmd" 2>&1
echo "### exit: $?"
} > "$OUT/$name.txt"
}
echo "Harvesting $TARGET -> $OUT"
# ── Identity / facts ──────────────────────────────────────────────────────────
run version 'getcfg System Version'
run build 'getcfg System "Build Number"'
run model 'getcfg System Model'
run servername 'getcfg System "Server Name"'
run getsysinfo_model 'getsysinfo model'
run getsysinfo_serial 'getsysinfo serial'
run uptime 'cat /proc/uptime'
run uname 'uname -a'
run ulinux_system 'sed -n "/^\[System\]/,/^\[/p" /etc/config/uLinux.conf'
# ── Interfaces ────────────────────────────────────────────────────────────────
run ip_link 'ip -o link show'
run ip_addr 'ip -o addr show'
run ifconfig 'ifconfig -a'
run ulinux_network 'sed -n "/^\[Network\]/,/^\[/p" /etc/config/uLinux.conf'
# ── Storage ───────────────────────────────────────────────────────────────────
run qcli_storage 'qcli_storage'
run qcli_storage_d 'qcli_storage -d'
run qcli_storage_v 'qcli_storage -v'
run qcli_storage_p 'qcli_storage -p'
run qcli_storage_t 'qcli_storage -t'
run mdstat 'cat /proc/mdstat'
run volume_conf 'cat /etc/volume.conf'
run enclosure_conf 'cat /etc/enclosure_0.conf'
run df 'df -B1 -P'
run hdnum 'getsysinfo hdnum'
run hdtmp_all 'for i in $(seq 1 8); do echo "== slot $i"; getsysinfo hdtmp $i; done'
run hdstatus_all 'for i in $(seq 1 8); do echo "== slot $i"; getsysinfo hdstatus $i; done'
run smartinfo_1 'get_hd_smartinfo -d 1'
run smartinfo_2 'get_hd_smartinfo -d 2'
run lsblk 'lsblk -b -P -o NAME,MODEL,SERIAL,SIZE,ROTA,TYPE'
# ── Shares & services ─────────────────────────────────────────────────────────
run smb_conf 'cat /etc/config/smb.conf'
run exports 'cat /etc/exports'
run ss_listen 'ss -lntup'
run svc_samba 'getcfg Samba Enable'
run svc_nfs 'getcfg NFS Enable'
run svc_ftp 'getcfg FTP Enable'
run svc_afp 'getcfg AFP Enable'
run svc_iscsi 'getcfg iSCSI Enable'
run init_d 'ls /etc/init.d/'
# ── Packages (QPKG) ───────────────────────────────────────────────────────────
run qpkg_conf 'cat /etc/config/qpkg.conf'
run qpkg_cli 'qpkg_cli -L'
run qpkg_dir 'ls -d /share/*/.qpkg/*'
# ── Virtualisation & containers ───────────────────────────────────────────────
run virsh_which 'ls /share/*/.qpkg/QKVM/usr/bin/virsh /share/*/.qpkg/*/bin/virsh 2>&1'
run virsh_list 'virsh list --all'
run docker_which 'ls /share/*/.qpkg/container-station/bin/docker /share/*/.qpkg/container-station/usr/bin/docker 2>&1'
run docker_ps 'docker ps -a --no-trunc --format "{{json .}}"'
# ── Firmware / updates ────────────────────────────────────────────────────────
run fw_update_cfg 'sed -n "/^\[FIRMWARE UPDATE\]/,/^\[/p" /etc/config/uLinux.conf'
echo
echo "Done. $(ls "$OUT" | wc -l) files in $OUT"
echo "Next: ./tools/sanitize.py $OUT"