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:
Davíð Steinn Geirsson 2026-03-20 11:31:31 +00:00
parent c8ae97d777
commit 63494d0fcb
3 changed files with 136 additions and 0 deletions

View 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
}

View 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)
}
}

View 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"}]}