Added ZFS state metrics
This commit is contained in:
@@ -223,6 +223,40 @@ type PveDisk struct {
|
||||
Used string `json:"used,omitempty"` // How the drive is used (optional field).
|
||||
}
|
||||
|
||||
// PveZfsPool represents summary information about a ZFS pool.
|
||||
type PveZfsPool struct {
|
||||
Name string `json:"name"` // Pool name.
|
||||
Size uint64 `json:"size"` // Total pool size in bytes.
|
||||
Alloc uint64 `json:"alloc"` // Allocated pool space in bytes.
|
||||
Free uint64 `json:"free"` // Free pool space in bytes.
|
||||
Frag uint64 `json:"frag"` // Pool fragmentation percentage.
|
||||
Dedup float64 `json:"dedup"` // Pool deduplication ratio.
|
||||
Health string `json:"health"` // Pool health state.
|
||||
}
|
||||
|
||||
// PveZfsComponent represents a component in the recursive ZFS pool topology.
|
||||
// Error counters are pointers because section nodes such as "cache" do not
|
||||
// always contain read, write, or checksum values in the PVE API response.
|
||||
type PveZfsComponent struct {
|
||||
Name string `json:"name"` // Component, vdev, or device name.
|
||||
State string `json:"state"` // Component state, for example ONLINE.
|
||||
Leaf uint8 `json:"leaf"` // Whether this component is a leaf device.
|
||||
Read *uint64 `json:"read"` // Read error count when available.
|
||||
Write *uint64 `json:"write"` // Write error count when available.
|
||||
Checksum *uint64 `json:"cksum"` // Checksum error count when available.
|
||||
Message string `json:"msg"` // Optional component status message.
|
||||
Children []PveZfsComponent `json:"children"` // Nested ZFS components.
|
||||
}
|
||||
|
||||
// PveZfsPoolStatus represents the detailed status and topology of a ZFS pool.
|
||||
type PveZfsPoolStatus struct {
|
||||
PveZfsComponent
|
||||
Status string `json:"status"` // Human-readable pool status.
|
||||
Action string `json:"action"` // Recommended recovery action.
|
||||
Scan string `json:"scan"` // Last or current scrub information.
|
||||
Errors string `json:"errors"` // Human-readable pool errors.
|
||||
}
|
||||
|
||||
// PVE node time.
|
||||
type PveNodeTime struct {
|
||||
Time uint64 `json:"time"` // Unix timestamp in UTC.
|
||||
|
||||
+61
-1
@@ -1,6 +1,9 @@
|
||||
package proxmox
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPveClusterStatusGetClusterModeNumeric(t *testing.T) {
|
||||
tests := []struct {
|
||||
@@ -52,3 +55,60 @@ func TestPveSubscriptionGetStatusNumeric(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPveZfsPoolStatusUnmarshal(t *testing.T) {
|
||||
payload := []byte(`{
|
||||
"errors": "No known data errors",
|
||||
"state": "ONLINE",
|
||||
"leaf": 0,
|
||||
"name": "hdd",
|
||||
"children": [
|
||||
{
|
||||
"name": "hdd",
|
||||
"state": "ONLINE",
|
||||
"read": 0,
|
||||
"write": 0,
|
||||
"cksum": 0,
|
||||
"children": [
|
||||
{
|
||||
"name": "/dev/disk/by-id/disk-1-part1",
|
||||
"state": "ONLINE",
|
||||
"leaf": 1,
|
||||
"read": 0,
|
||||
"write": 0,
|
||||
"cksum": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "cache",
|
||||
"leaf": 0
|
||||
}
|
||||
]
|
||||
}`)
|
||||
|
||||
var status PveZfsPoolStatus
|
||||
if err := json.Unmarshal(payload, &status); err != nil {
|
||||
t.Fatalf("unable to unmarshal ZFS pool status: %v", err)
|
||||
}
|
||||
|
||||
if status.Name != "hdd" || status.State != "ONLINE" || status.Errors != "No known data errors" {
|
||||
t.Fatalf("unexpected pool status: %+v", status)
|
||||
}
|
||||
if len(status.Children) != 2 || len(status.Children[0].Children) != 1 {
|
||||
t.Fatalf("unexpected ZFS topology: %+v", status.Children)
|
||||
}
|
||||
|
||||
leaf := status.Children[0].Children[0]
|
||||
if leaf.Leaf != 1 || leaf.Read == nil || leaf.Write == nil || leaf.Checksum == nil {
|
||||
t.Fatal("leaf component is missing its type or error counters")
|
||||
}
|
||||
if *leaf.Read != 0 || *leaf.Write != 0 || *leaf.Checksum != 0 {
|
||||
t.Fatalf("unexpected leaf error counters: read=%d write=%d checksum=%d", *leaf.Read, *leaf.Write, *leaf.Checksum)
|
||||
}
|
||||
|
||||
cache := status.Children[1]
|
||||
if cache.Read != nil || cache.Write != nil || cache.Checksum != nil {
|
||||
t.Fatal("missing cache counters must stay absent instead of becoming zero metrics")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
@@ -212,6 +213,37 @@ func (instance *PveApiClient) GetNodeDisksList(node string) (*[]PveDisk, error)
|
||||
return &disks, nil
|
||||
}
|
||||
|
||||
// Get ZFS pools configured on a PVE node.
|
||||
func (instance *PveApiClient) GetNodeZfsPools(node string) (*[]PveZfsPool, error) {
|
||||
res, err := instance.apiClient.PerformRequest("GET", "/nodes/"+node+"/disks/zfs", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var pools []PveZfsPool
|
||||
if err := json.Unmarshal(res.Data, &pools); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pools, nil
|
||||
}
|
||||
|
||||
// Get detailed status and topology of a ZFS pool on a PVE node.
|
||||
func (instance *PveApiClient) GetNodeZfsPoolStatus(node string, pool string) (*PveZfsPoolStatus, error) {
|
||||
path := "/nodes/" + node + "/disks/zfs/" + url.PathEscape(pool)
|
||||
res, err := instance.apiClient.PerformRequest("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var status PveZfsPoolStatus
|
||||
if err := json.Unmarshal(res.Data, &status); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &status, nil
|
||||
}
|
||||
|
||||
// Get PVE node time info.
|
||||
func (instance *PveApiClient) GetNodeTime(node string) (*PveNodeTime, error) {
|
||||
res, err := instance.apiClient.PerformRequest("GET", "/nodes/"+node+"/time", nil)
|
||||
|
||||
Reference in New Issue
Block a user