Handle FreeBSD guest agent disk counters

Refs #580
This commit is contained in:
rcourtman 2025-10-22 14:06:45 +00:00
parent cdf80dbab5
commit 13e2577c57
2 changed files with 124 additions and 0 deletions

View file

@ -1050,6 +1050,95 @@ type VMFileSystem struct {
DiskRaw []interface{} `json:"disk"` // Raw disk device info from API
}
func (fs *VMFileSystem) UnmarshalJSON(data []byte) error {
type rawVMFileSystem struct {
Name string `json:"name"`
Type string `json:"type"`
Mountpoint string `json:"mountpoint"`
TotalBytes interface{} `json:"total-bytes"`
UsedBytes interface{} `json:"used-bytes"`
DiskRaw []interface{} `json:"disk"`
}
var raw rawVMFileSystem
if err := json.Unmarshal(data, &raw); err != nil {
return err
}
total, err := parseUint64Flexible(raw.TotalBytes)
if err != nil {
return err
}
used, err := parseUint64Flexible(raw.UsedBytes)
if err != nil {
return err
}
fs.Name = raw.Name
fs.Type = raw.Type
fs.Mountpoint = raw.Mountpoint
fs.TotalBytes = total
fs.UsedBytes = used
fs.DiskRaw = raw.DiskRaw
fs.Disk = ""
return nil
}
func parseUint64Flexible(value interface{}) (uint64, error) {
switch v := value.(type) {
case nil:
return 0, nil
case uint64:
return v, nil
case int:
if v < 0 {
return 0, nil
}
return uint64(v), nil
case int64:
if v < 0 {
return 0, nil
}
return uint64(v), nil
case float64:
if v < 0 {
return 0, nil
}
return uint64(v), nil
case json.Number:
return parseUint64Flexible(v.String())
case string:
s := strings.TrimSpace(v)
if s == "" {
return 0, nil
}
if strings.HasPrefix(s, "0x") || strings.HasPrefix(s, "0X") {
u, err := strconv.ParseUint(s[2:], 16, 64)
if err != nil {
return 0, err
}
return u, nil
}
if strings.ContainsAny(s, ".eE") {
f, err := strconv.ParseFloat(s, 64)
if err != nil {
return 0, err
}
if f < 0 {
return 0, nil
}
return uint64(f), nil
}
u, err := strconv.ParseUint(s, 10, 64)
if err != nil {
return 0, err
}
return u, nil
default:
return 0, fmt.Errorf("unsupported type %T for uint64 conversion", value)
}
}
type VMIpAddress struct {
Address string `json:"ip-address"`
Prefix int `json:"prefix"`

View file

@ -73,6 +73,41 @@ func TestDiskUnmarshalWearout(t *testing.T) {
}
}
func TestVMFileSystemUnmarshalFlexibleNumbers(t *testing.T) {
t.Run("accepts numeric values", func(t *testing.T) {
payload := `{"name":"rootfs","type":"zfs","mountpoint":"/","total-bytes":8589934592,"used-bytes":3221225472,"disk":[{"dev":"/dev/vtbd0p2"}]}`
var fs VMFileSystem
if err := json.Unmarshal([]byte(payload), &fs); err != nil {
t.Fatalf("unexpected error unmarshalling numeric fields: %v", err)
}
if fs.TotalBytes != 8589934592 || fs.UsedBytes != 3221225472 {
t.Fatalf("unexpected numeric values: got total=%d used=%d", fs.TotalBytes, fs.UsedBytes)
}
})
t.Run("accepts numeric strings", func(t *testing.T) {
payload := `{"name":"rootfs","type":"ufs","mountpoint":"/","total-bytes":"5368709120","used-bytes":"2147483648","disk":[{"dev":"/dev/vtbd0p3"}]}`
var fs VMFileSystem
if err := json.Unmarshal([]byte(payload), &fs); err != nil {
t.Fatalf("unexpected error unmarshalling string fields: %v", err)
}
if fs.TotalBytes != 5368709120 || fs.UsedBytes != 2147483648 {
t.Fatalf("unexpected string values: got total=%d used=%d", fs.TotalBytes, fs.UsedBytes)
}
})
t.Run("accepts float-like strings", func(t *testing.T) {
payload := `{"name":"rootfs","type":"ufs","mountpoint":"/","total-bytes":"1073741824.0","used-bytes":"536870912.0","disk":[{"dev":"/dev/vtbd0p4"}]}`
var fs VMFileSystem
if err := json.Unmarshal([]byte(payload), &fs); err != nil {
t.Fatalf("unexpected error unmarshalling float-like strings: %v", err)
}
if fs.TotalBytes != 1073741824 || fs.UsedBytes != 536870912 {
t.Fatalf("unexpected float string values: got total=%d used=%d", fs.TotalBytes, fs.UsedBytes)
}
})
}
func TestMemoryStatusEffectiveAvailable(t *testing.T) {
t.Run("nil receiver returns zero", func(t *testing.T) {
var status *MemoryStatus