gates / gates (push) Successful in 6m43s
Spike (read-only probe, tools/physdisk_probe.py): the agent-side walk (partition -> disk, dm/LVM slaves, loop -> backing file) names the right physical disk for one-disk boxes, two disks, two partitions of one disk and a pass-through USB bridge; inside a VM (Tester 1) every disk is "unknown". Today's agent lists whole disks only and calls an empty partition table "data-bearing"; the controller's Tier-2 "different disk" is st_dev. sys_drive is a folder of mp0 (backup=1), so the design gives one-disk boxes a separate internal data place outside the whole-guest copy instead. Draft only; waits on the operator's rulings D1-D6 (16 section 12). 07 section 6.1's stale mp1 cell corrected. Register 163 -> 169. Scratch loop disk and Tester 1 scratch disk removed. Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb
61 lines
6.2 KiB
Markdown
61 lines
6.2 KiB
Markdown
# Spike — can the agent name the physical disk under every storage place? (2026-10-15)
|
|
|
|
For `architecture/16-storage-physical-identity.md` (draft). Method: `tools/physdisk_probe.py` — read-only, run as root
|
|
on each Proxmox host. It follows the block chain down to whole disks — partition → parent (`/sys/class/block/<p>/partition`,
|
|
the same as lsblk `PKNAME`), device-mapper / LVM thin → `/sys/class/block/<dm>/slaves/*` recursively, loop →
|
|
`loop/backing_file` → the filesystem that holds the file — and reads each disk's `ID_WWN`, `ID_SERIAL_SHORT`, `ID_BUS`,
|
|
vendor and model from udev, plus `systemd-detect-virt`. `tools/summarize.py` prints the tables. The agent already
|
|
has most of this chain (`role.go` `walkSlaves`/`physicalDisksOf`, `hostread.go` `parentDisk`); it lacks the loop step
|
|
and a per-place report.
|
|
|
|
## Results
|
|
|
|
| # | Case | Where | The probe's answer | Truth | Right? | Evidence |
|
|
|---|---|---|---|---|---|---|
|
|
| 1 | One disk: host root, `local`, the thin pool, guest rootfs and `mp0` (`sys_drive` is a folder of `mp0`) | N100 | all → `wwn:0x5000000123456f1f` (AirDisk 512 GB SSD), proven | one SATA SSD | **yes** | `B-n100.txt` |
|
|
| 1b | The same on demo-hp | demo-hp | all system places → `wwn:0x5001b448b60a9d6e` (SanDisk 128 GB) | one SSD | **yes** | `B-demo-hp.txt` |
|
|
| 2 | Two disks: system + enrolled data drive | demo-hp, N100 | system → SanDisk / AirDisk; `/mnt/felhom-drives/hdd_1` → Toshiba NVMe (`eui…46c07b`) / Toshiba MQ04 on USB (`0x5000039ddb108568`) | two disks each | **yes** | `B-demo-hp.txt`, `B-n100.txt` |
|
|
| 3 | One disk, two partitions, two places | demo-hp, a 4 GB scratch file as a loop disk, `p1` and `p2` ext4, mounted | both → the NVMe that holds the file, via `loop2pN → loop2 → file on nvme0n1` | the same disk | **yes** (after a probe fix: the partition step must run before the loop step) | `B-case3-loop.*` |
|
|
| 4a | A USB enclosure that passes the disk's identity | N100's backup drive | `ID_BUS=ata`, real model, serial `65NOP3HDT`, WWN; `ID_USB_DRIVER=usb-storage` | the real Toshiba disk | **yes** | `B-n100.txt` |
|
|
| 4b | A USB enclosure that hides or fakes the serial | — | **not measured** — no such device at hand, none in earlier evidence (searched `documentation/`, agent, controller) | — | — | R-1077 |
|
|
| 4c | A cheap SSD with a generic WWN | N100 system SSD | `ID_WWN=0x5000000123456f1f`: NAA-5 with **OUI 000000** (no registered vendor) — the serial `QDF922W009654S30EX` looks real | unknown whether two units share the WWN | **probably weak** | R-1076 |
|
|
| 5 | Inside a VM: virtual disks on the **same** host disk | Tester 1 (VM 341): `scsi0`, `scsi1`, `scsi2` | inside: `QEMU HARDDISK`, serials `drive-scsi0/1/2`, `systemd-detect-virt=kvm` → **unknown** | host: all three are files on demo-hp's NVMe (`B-case56-host-truth.txt`) | **yes — „unknown"** | `B-tester1.txt`, `B-case56-*` |
|
|
| 6 | Inside a VM: a virtual disk on a **different** host disk | Tester 1, scratch `scsi6` (4 GB on `local-lvm` = demo-hp's SanDisk), hot-added and removed | inside: `QEMU HARDDISK`, `ID_SERIAL_SHORT=drive-scsi6` (the set serial `SCRATCHSID6` shows only as `ID_SCSI_SERIAL`) → **unknown** | host: the SanDisk | **yes — „unknown"** | `B-case56-*` |
|
|
| 7 | A network share | — (no box has one mounted now; the OMV run mounted one at `/mnt/felhom-drives/omv-filmek`) | the probe classes `cifs`/`nfs` sources as network → unknown, no disk | a disk on another machine | **yes by rule** (not run on a live share) | `../omv-vm-2026-10-15/B6-omv-share.txt` |
|
|
|
|
**The VM cases in one sentence:** from inside a VM, two virtual disks on the same host disk and two on different host
|
|
disks look exactly alike (same vendor, same model, serial = the hypervisor's slot name), so the agent can only say
|
|
„unknown"; only the hypervisor's host knows.
|
|
|
|
## What today's agent does with a partitioned disk (Tester 1, scratch `sdg`, read-only `GET /disks/candidates`)
|
|
|
|
| Disk state | Agent answer | Evidence |
|
|
|---|---|---|
|
|
| blank | `initialize`: `/dev/sdg`, `data_bearing:false`, `mountable:false` | `B-case56-agent-blank.txt` |
|
|
| GPT, `p1` empty (no fs), `p2` empty ext4 | `attach` and `initialize`: one row `/dev/sdg`, `fstype ext4`, `mount_source /dev/sdg2`, `data_bearing:true`; **`p1` is not offered at all** | `B-case4b-partitions-empty.txt`, `B-case4b-partitions.txt` |
|
|
| GPT, both partitions empty | `initialize` only: `/dev/sdg`, `data_bearing:true`, `mountable:false` — the only way to use it is to wipe the whole disk | `B-case4b-partitions.txt` |
|
|
|
|
So today a place is always a whole disk (or the one partition the agent picks), an empty filesystem counts as data, and
|
|
a partition table alone makes the whole disk „data-bearing".
|
|
|
|
## Also found while reading (code, not run)
|
|
|
|
- The controller's „same disk" test is `stat().st_dev` (`felhom-controller/controller/internal/system/mounts_linux.go:225-245`;
|
|
its own comment: „two partitions on one physical disk look "different" here"). Tier-2 target choice
|
|
(`internal/backup/tier2.go:83-179`) and the one-copy warning (`internal/backup/backup.go:1387-1410`) use it.
|
|
- Tester 1 today makes Tier-2 copies between `media_a` / `adatlemez` / … — all files on one host NVMe — and counts them as
|
|
off-drive copies (case 5). R-1074.
|
|
- `hasOffDriveTarget` (`backup.go:1390-1402`) does not skip network paths, and a NAS is registered `Schedulable:true`
|
|
(`internal/web/netstorage_job.go:267-273`), so adding a NAS would remove the „Csak egy másolat…" warning while Tier-2
|
|
refuses a NAS (`tier2.go:141-143`). Read in code, not run. R-1075.
|
|
- `sys_drive` is a folder of `mp0` (`/var/lib/felhom`, `backup=1`), next to Docker — not a separate `mp1` (all three boxes,
|
|
`pct config 9201`; `07-backup-architecture.md` §7.5.1). Anything stored there rides in every whole-guest copy.
|
|
|
|
## Put back
|
|
|
|
- demo-hp: the 4 GB scratch file and its loop device removed (checked: `losetup -a` 0 lines, file absent). A first try left
|
|
them for ~1 minute (a missing `partprobe`); removed at once, nothing else touched.
|
|
- Tester 1: scratch `scsi6` partitioned, wiped (`wipefs -a`, `sgdisk -Z` — the scratch disk only, matched by its serial
|
|
before every write), hot-removed, its LV `vm-341-disk-0` destroyed; `sdg` gone; VM 341 still running.
|
|
- Probe files deleted from all hosts. No disk that holds anything was written. DooPlex and ep0 not touched.
|