Attach ZFS pools for dataset-backed storages

This commit is contained in:
rcourtman 2026-03-26 22:29:32 +00:00
parent d9b7c99f02
commit ae6b663e95
2 changed files with 78 additions and 31 deletions

View file

@ -1531,36 +1531,26 @@ func (m *Monitor) pollStorageWithNodes(ctx context.Context, instanceName string,
enableZFSMonitoring := os.Getenv("PULSE_DISABLE_ZFS_MONITORING") != "true" // Enabled by default
if enableZFSMonitoring {
hasZFSStorage := false
for _, storage := range nodeStorage {
if storage.Type == "zfspool" || storage.Type == "zfs" || storage.Type == "local-zfs" {
hasZFSStorage = true
break
}
}
if poolInfos, err := client.GetZFSPoolsWithDetails(ctx, n.Node); err == nil {
log.Debug().
Str("node", n.Node).
Int("pools", len(poolInfos)).
Msg("Successfully fetched ZFS pool details")
if hasZFSStorage {
if poolInfos, err := client.GetZFSPoolsWithDetails(ctx, n.Node); err == nil {
log.Debug().
Str("node", n.Node).
Int("pools", len(poolInfos)).
Msg("Successfully fetched ZFS pool details")
// Convert to our model format
for _, poolInfo := range poolInfos {
modelPool := convertPoolInfoToModel(&poolInfo)
if modelPool != nil {
zfsPoolMap[poolInfo.Name] = modelPool
}
// Convert to our model format
for _, poolInfo := range poolInfos {
modelPool := convertPoolInfoToModel(&poolInfo)
if modelPool != nil {
zfsPoolMap[poolInfo.Name] = modelPool
}
} else {
// Log but don't fail - ZFS monitoring is optional
log.Debug().
Err(err).
Str("node", n.Node).
Str("instance", instanceName).
Msg("Could not get ZFS pool status (may require additional permissions)")
}
} else {
// Log but don't fail - ZFS monitoring is optional
log.Debug().
Err(err).
Str("node", n.Node).
Str("instance", instanceName).
Msg("Could not get ZFS pool status (may require additional permissions)")
}
}
@ -1627,10 +1617,10 @@ func (m *Monitor) pollStorageWithNodes(ctx context.Context, instanceName string,
}
}
// If this is ZFS storage, attach pool status information.
if storage.Type == "zfspool" || storage.Type == "zfs" || storage.Type == "local-zfs" {
modelStorage.ZFSPool = matchZFSPoolForStorage(modelStorage, zfsPoolMap)
}
// Attach ZFS pool status information whenever the storage name or
// dataset path resolves to a known pool, even for dir storages
// backed by ZFS datasets.
modelStorage.ZFSPool = matchZFSPoolForStorage(modelStorage, zfsPoolMap)
// Override with cluster config if available, but only when the
// cluster metadata explicitly carries those flags. Some storage

View file

@ -258,6 +258,12 @@ func TestMatchZFSPoolForStorage(t *testing.T) {
pools: map[string]*models.ZFSPool{"rpool": rpool},
want: "rpool",
},
{
name: "matches dir storage from dataset path",
storage: models.Storage{Name: "local", Type: "dir", Path: "/rpool/data"},
pools: map[string]*models.ZFSPool{"rpool": rpool},
want: "rpool",
},
{
name: "single pool fallback for local zfs",
storage: models.Storage{Name: "local-zfs", Type: "local-zfs"},
@ -342,3 +348,54 @@ func TestPollStorageWithNodesOptimizedAttachesZFSPoolFromDatasetPath(t *testing.
t.Fatalf("ZFS pool name = %q, want rpool", monitor.state.Storage[0].ZFSPool.Name)
}
}
func TestPollStorageWithNodesOptimizedAttachesZFSPoolForDirStorageOnDatasetPath(t *testing.T) {
t.Setenv("PULSE_DATA_DIR", t.TempDir())
monitor := &Monitor{
state: &models.State{},
metricsHistory: NewMetricsHistory(16, time.Hour),
alertManager: alerts.NewManager(),
}
t.Cleanup(func() {
monitor.alertManager.Stop()
})
storage := proxmox.Storage{
Storage: "local",
Type: "dir",
Path: "/rpool/data",
Content: "images",
Active: 1,
Enabled: 1,
Shared: 0,
Total: 1000,
Used: 250,
Available: 750,
}
client := &fakeStorageClient{
allStorage: []proxmox.Storage{storage},
storageByNode: map[string][]proxmox.Storage{
"node1": {storage},
},
zfsPoolsByNode: map[string][]proxmox.ZFSPoolInfo{
"node1": {
{Name: "rpool", Size: 1000, Alloc: 250, Free: 750, Frag: 1, Dedup: 1.0, Health: "ONLINE"},
},
},
}
nodes := []proxmox.Node{{Node: "node1", Status: "online"}}
monitor.pollStorageWithNodes(context.Background(), "inst1", client, nodes)
if len(monitor.state.Storage) != 1 {
t.Fatalf("expected 1 storage entry, got %d", len(monitor.state.Storage))
}
if monitor.state.Storage[0].ZFSPool == nil {
t.Fatal("expected dir storage on ZFS dataset path to have ZFS pool attached")
}
if monitor.state.Storage[0].ZFSPool.Name != "rpool" {
t.Fatalf("ZFS pool name = %q, want rpool", monitor.state.Storage[0].ZFSPool.Name)
}
}