feat: add cluster_status collector (pve_node_info, pve_cluster_info)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c8ae97d777
commit
63494d0fcb
3 changed files with 136 additions and 0 deletions
96
collector/cluster_status.go
Normal file
96
collector/cluster_status.go
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
package collector
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strconv"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registerCollector("cluster_status", func(logger *slog.Logger) Collector {
|
||||
return newClusterStatusCollector(logger)
|
||||
})
|
||||
}
|
||||
|
||||
type clusterStatusCollector struct {
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
func newClusterStatusCollector(logger *slog.Logger) *clusterStatusCollector {
|
||||
return &clusterStatusCollector{logger: logger}
|
||||
}
|
||||
|
||||
type clusterStatusResponse struct {
|
||||
Data []clusterStatusEntry `json:"data"`
|
||||
}
|
||||
|
||||
type clusterStatusEntry struct {
|
||||
Type string `json:"type"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
NodeID int `json:"nodeid"`
|
||||
Online int `json:"online"`
|
||||
Local int `json:"local"`
|
||||
IP string `json:"ip"`
|
||||
Level string `json:"level"`
|
||||
Version int `json:"version"`
|
||||
Nodes int `json:"nodes"`
|
||||
Quorate int `json:"quorate"`
|
||||
}
|
||||
|
||||
var (
|
||||
nodeInfoDesc = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "", "node_info"),
|
||||
"Information about a Proxmox VE node.",
|
||||
[]string{"id", "level", "name", "nodeid"},
|
||||
nil,
|
||||
)
|
||||
clusterInfoDesc = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "", "cluster_info"),
|
||||
"Information about the Proxmox VE cluster.",
|
||||
[]string{"id", "nodes", "quorate", "version"},
|
||||
nil,
|
||||
)
|
||||
)
|
||||
|
||||
func (c *clusterStatusCollector) Update(client *Client, ch chan<- prometheus.Metric) error {
|
||||
body, err := client.Get("/cluster/status")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get /cluster/status: %w", err)
|
||||
}
|
||||
|
||||
var resp clusterStatusResponse
|
||||
if err := json.Unmarshal(body, &resp); err != nil {
|
||||
return fmt.Errorf("failed to parse /cluster/status response: %w", err)
|
||||
}
|
||||
|
||||
for _, entry := range resp.Data {
|
||||
switch entry.Type {
|
||||
case "node":
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
nodeInfoDesc,
|
||||
prometheus.GaugeValue,
|
||||
1,
|
||||
entry.ID,
|
||||
entry.Level,
|
||||
entry.Name,
|
||||
strconv.Itoa(entry.NodeID),
|
||||
)
|
||||
case "cluster":
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
clusterInfoDesc,
|
||||
prometheus.GaugeValue,
|
||||
1,
|
||||
entry.ID,
|
||||
strconv.Itoa(entry.Nodes),
|
||||
strconv.Itoa(entry.Quorate),
|
||||
strconv.Itoa(entry.Version),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
39
collector/cluster_status_test.go
Normal file
39
collector/cluster_status_test.go
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package collector
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/testutil"
|
||||
)
|
||||
|
||||
func TestClusterStatusCollector(t *testing.T) {
|
||||
client := newTestClient(t, map[string]string{
|
||||
"/cluster/status": "cluster_status.json",
|
||||
})
|
||||
|
||||
collector := newClusterStatusCollector(slog.Default())
|
||||
adapter := &testCollectorAdapter{client: client, collector: collector}
|
||||
|
||||
reg := prometheus.NewRegistry()
|
||||
reg.MustRegister(adapter)
|
||||
|
||||
expected := `
|
||||
# HELP pve_cluster_info Information about the Proxmox VE cluster.
|
||||
# TYPE pve_cluster_info gauge
|
||||
pve_cluster_info{id="cluster",nodes="5",quorate="1",version="9"} 1
|
||||
# HELP pve_node_info Information about a Proxmox VE node.
|
||||
# TYPE pve_node_info gauge
|
||||
pve_node_info{id="node/node01",level="b",name="node01",nodeid="1"} 1
|
||||
pve_node_info{id="node/node02",level="b",name="node02",nodeid="2"} 1
|
||||
pve_node_info{id="node/node03",level="b",name="node03",nodeid="3"} 1
|
||||
pve_node_info{id="node/node04",level="b",name="node04",nodeid="4"} 1
|
||||
pve_node_info{id="node/node05",level="b",name="node05",nodeid="5"} 1
|
||||
`
|
||||
|
||||
if err := testutil.GatherAndCompare(reg, strings.NewReader(expected), "pve_node_info", "pve_cluster_info"); err != nil {
|
||||
t.Errorf("unexpected metrics: %s", err)
|
||||
}
|
||||
}
|
||||
1
collector/fixtures/cluster_status.json
Normal file
1
collector/fixtures/cluster_status.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"data":[{"type":"cluster","id":"cluster","name":"freyja","version":9,"nodes":5,"quorate":1},{"type":"node","id":"node/node01","name":"node01","nodeid":1,"online":1,"local":0,"ip":"10.99.0.1","level":"b"},{"type":"node","id":"node/node02","name":"node02","nodeid":2,"online":1,"local":1,"ip":"10.99.0.2","level":"b"},{"type":"node","id":"node/node03","name":"node03","nodeid":3,"online":1,"local":0,"ip":"10.99.0.3","level":"b"},{"type":"node","id":"node/node04","name":"node04","nodeid":4,"online":1,"local":0,"ip":"10.99.0.4","level":"b"},{"type":"node","id":"node/node05","name":"node05","nodeid":5,"online":1,"local":0,"ip":"10.99.0.5","level":"b"}]}
|
||||
Loading…
Add table
Add a link
Reference in a new issue