Fix block parser splitting on drive- inside throttle JSON values

This commit is contained in:
illustris
2026-04-07 19:13:41 +05:30
parent 8c761d8c96
commit 84c312f19b
2 changed files with 74 additions and 4 deletions

View File

@@ -27,18 +27,33 @@ var blockHeaderRe = regexp.MustCompile(`^(\w+)(?:\s+\(#block(\d+)\))?: (.+) \(([
// lvmRe matches: /dev/{vg_name}/vm-{N}-disk-{N} // lvmRe matches: /dev/{vg_name}/vm-{N}-disk-{N}
var lvmRe = regexp.MustCompile(`^/dev/([^/]+)/(vm-\d+-disk-\d+)$`) var lvmRe = regexp.MustCompile(`^/dev/([^/]+)/(vm-\d+-disk-\d+)$`)
// driveStartRe matches "drive-" at the start of a line.
// This avoids splitting on "drive-" embedded inside JSON values
// (e.g., "throttle-group": "throttle-drive-virtio0").
var driveStartRe = regexp.MustCompile(`(?m)^drive-`)
// ParseBlockInfo parses "info block" output from qm monitor. // ParseBlockInfo parses "info block" output from qm monitor.
// Returns map of disk_name -> DiskInfo. Skips efidisk entries. // Returns map of disk_name -> DiskInfo. Skips efidisk entries.
func ParseBlockInfo(raw string) map[string]DiskInfo { func ParseBlockInfo(raw string) map[string]DiskInfo {
result := make(map[string]DiskInfo) result := make(map[string]DiskInfo)
// Split by "drive-" prefix to get individual disk blocks // Find all "drive-" at start of a line and extract each block section
parts := strings.Split(raw, "drive-") indices := driveStartRe.FindAllStringIndex(raw, -1)
if len(parts) < 2 { if len(indices) == 0 {
return result return result
} }
for _, part := range parts[1:] { parts := make([]string, len(indices))
for i, idx := range indices {
start := idx[0] + len("drive-")
end := len(raw)
if i+1 < len(indices) {
end = indices[i+1][0]
}
parts[i] = raw[start:end]
}
for _, part := range parts {
lines := strings.Split(strings.TrimSpace(part), "\n") lines := strings.Split(strings.TrimSpace(part), "\n")
if len(lines) == 0 { if len(lines) == 0 {
continue continue

View File

@@ -268,3 +268,58 @@ drive-scsi1 (#block101): /mnt/storage/images/100/vm-100-disk-1.qcow2 (qcow2, rea
t.Errorf("scsi1 type = %q", disks["scsi1"].DiskType) t.Errorf("scsi1 type = %q", disks["scsi1"].DiskType)
} }
} }
func TestParseBlockInfo_ThrottleJSONWithDriveInValue(t *testing.T) {
// PVE 9.x format: throttle JSON contains "throttle-drive-*" in values,
// which previously caused strings.Split("drive-") to break mid-JSON.
raw := `drive-ide2: json:{"throttle-group": "throttle-drive-ide2", "driver": "throttle", "file": {"driver": "raw", "file": {"driver": "host_device", "filename": "/dev/zvol/rpool/data/vm-100-cloudinit"}}} (throttle, read-only)
Attached to: ide2
Removable device: locked, tray closed
Cache mode: writeback
drive-virtio0: json:{"throttle-group": "throttle-drive-virtio0", "driver": "throttle", "file": {"driver": "raw", "file": {"driver": "host_device", "filename": "/dev/rbd-pve/00000000-0000-0000-0000-000000000001/pool1/vm-100-disk-0"}}} (throttle)
Attached to: /machine/peripheral/virtio0/virtio-backend
Cache mode: writeback
Detect zeroes: on`
disks := ParseBlockInfo(raw)
// ide2 is cloudinit (not efidisk), so it should be included
if len(disks) != 2 {
t.Fatalf("expected 2 disks, got %d", len(disks))
}
d := disks["virtio0"]
if d.DiskType != "rbd" {
t.Errorf("type = %q, want rbd", d.DiskType)
}
if d.DiskPath != "/dev/rbd-pve/00000000-0000-0000-0000-000000000001/pool1/vm-100-disk-0" {
t.Errorf("path = %q", d.DiskPath)
}
if d.Labels["attached_to"] != "virtio0" {
t.Errorf("attached_to = %q", d.Labels["attached_to"])
}
if d.Labels["detect_zeroes"] != "on" {
t.Errorf("detect_zeroes = %q", d.Labels["detect_zeroes"])
}
}
func TestParseBlockInfo_OldFormatNoMode(t *testing.T) {
// Old PVE format with (#blockN) but no read-write/read-only suffix
raw := `drive-virtio0 (#block109): /dev/zvol/data/vm-100-disk-0 (raw)
Attached to: /machine/peripheral/virtio0/virtio-backend
Cache mode: writeback, direct
Detect zeroes: on
`
disks := ParseBlockInfo(raw)
if len(disks) != 1 {
t.Fatalf("expected 1 disk, got %d", len(disks))
}
d := disks["virtio0"]
if d.BlockID != "109" {
t.Errorf("block_id = %q, want 109", d.BlockID)
}
if d.DiskType != "zvol" {
t.Errorf("type = %q, want zvol", d.DiskType)
}
if d.Labels["cache_mode"] != "writeback, direct" {
t.Errorf("cache_mode = %q", d.Labels["cache_mode"])
}
}