hub v0.25.0: per-storage worst-fill alerting (StorageFillChecker)
Generalizes host_disk to any reported storage target (dump/backup volume, data drive, thin pool, PBS). Per-(host,target) state, born/persistent, natural critical severity, distinct storage_fill_* events; excludes the root-backed builtin (host_disk owns root). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HxLA1mZurFq9kt8hneFeCs
This commit is contained in:
@@ -1731,6 +1731,61 @@ func (s *Store) GetHostDiskUsage() ([]HostDiskRow, error) {
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// HostStorageTargetRow is one (host, storage target) fill observation, parsed from the latest report's
|
||||
// storage_targets[] (no denorm column — modeled on GetHostDiskUsage's report_json parse). Percent is
|
||||
// used_fraction×100 (the agent reports a 0..1 fraction). A target with no usable fraction yields 0 (ok).
|
||||
type HostStorageTargetRow struct {
|
||||
HostID string
|
||||
CustomerID string
|
||||
Name string // Proxmox storage id
|
||||
Type string // local | local-dir | lvmthin | usb | nfs | cifs | pbs
|
||||
MountPath string // host mountpoint ("" for network/lvm/root-backed dir)
|
||||
Percent float64 // 0..100 (used_fraction × 100)
|
||||
TotalBytes int64
|
||||
UsedBytes int64
|
||||
}
|
||||
|
||||
// GetHostStorageTargets returns the per-storage fill of every host's LATEST report (MAX(id) per host),
|
||||
// parsed from report_json.storage_targets[] (mirrors GetHostDiskUsage). The StorageFillChecker keys on
|
||||
// (host, name) and excludes the root-backed builtin — see monitor/storage_fill.go.
|
||||
func (s *Store) GetHostStorageTargets() ([]HostStorageTargetRow, error) {
|
||||
rows, err := s.db.Query(`
|
||||
SELECT hr.host_id, hr.customer_id, hr.report_json
|
||||
FROM host_reports hr
|
||||
JOIN (SELECT host_id, MAX(id) AS mx FROM host_reports GROUP BY host_id) latest
|
||||
ON hr.id = latest.mx`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []HostStorageTargetRow
|
||||
for rows.Next() {
|
||||
var hostID, customerID, reportJSON string
|
||||
if err := rows.Scan(&hostID, &customerID, &reportJSON); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var body struct {
|
||||
StorageTargets []struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
MountPath string `json:"mount_path"`
|
||||
UsedFraction float64 `json:"used_fraction"`
|
||||
TotalBytes int64 `json:"total_bytes"`
|
||||
UsedBytes int64 `json:"used_bytes"`
|
||||
} `json:"storage_targets"`
|
||||
}
|
||||
_ = json.Unmarshal([]byte(reportJSON), &body) // malformed/old body → no targets (never a false alert)
|
||||
for _, t := range body.StorageTargets {
|
||||
out = append(out, HostStorageTargetRow{
|
||||
HostID: hostID, CustomerID: customerID,
|
||||
Name: t.Name, Type: t.Type, MountPath: t.MountPath,
|
||||
Percent: t.UsedFraction * 100, TotalBytes: t.TotalBytes, UsedBytes: t.UsedBytes,
|
||||
})
|
||||
}
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// GetHostLeafFingerprints returns the latest reported local-API leaf fp per host (mirrors
|
||||
// GetHostCapabilities — MAX(id) per host, parsed from report_json so there is no schema migration).
|
||||
func (s *Store) GetHostLeafFingerprints() ([]HostLeafRow, error) {
|
||||
|
||||
Reference in New Issue
Block a user