feat: add subscription collector (info, status, next_due_timestamp)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5e61f224c4
commit
7708a64408
3 changed files with 170 additions and 0 deletions
1
collector/fixtures/node_subscription.json
Normal file
1
collector/fixtures/node_subscription.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"data":{"status":"active","level":"b","productname":"Proxmox VE Basic Subscription","nextduedate":"2027-02-03","regdate":"2025-02-03","key":"pve2b-test","sockets":2,"checktime":1773896474}}
|
||||
128
collector/subscription.go
Normal file
128
collector/subscription.go
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
package collector
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registerCollector("subscription", func(logger *slog.Logger) Collector {
|
||||
return newSubscriptionCollector(logger)
|
||||
})
|
||||
}
|
||||
|
||||
type subscriptionCollector struct {
|
||||
logger *slog.Logger
|
||||
mu sync.Mutex
|
||||
nodes []string
|
||||
}
|
||||
|
||||
func newSubscriptionCollector(logger *slog.Logger) *subscriptionCollector {
|
||||
return &subscriptionCollector{logger: logger}
|
||||
}
|
||||
|
||||
func (c *subscriptionCollector) SetNodes(nodes []string) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.nodes = nodes
|
||||
}
|
||||
|
||||
type subscriptionResponse struct {
|
||||
Data subscriptionData `json:"data"`
|
||||
}
|
||||
|
||||
type subscriptionData struct {
|
||||
Status string `json:"status"`
|
||||
Level string `json:"level"`
|
||||
NextDueDate string `json:"nextduedate"`
|
||||
}
|
||||
|
||||
var (
|
||||
subscriptionInfoDesc = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "subscription", "info"),
|
||||
"Subscription information for a node.",
|
||||
[]string{"id", "level"},
|
||||
nil,
|
||||
)
|
||||
subscriptionStatusDesc = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "subscription", "status"),
|
||||
"Subscription status for a node.",
|
||||
[]string{"id", "status"},
|
||||
nil,
|
||||
)
|
||||
subscriptionNextDueDesc = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "subscription", "next_due_timestamp_seconds"),
|
||||
"Next due date of the subscription as a Unix timestamp.",
|
||||
[]string{"id"},
|
||||
nil,
|
||||
)
|
||||
)
|
||||
|
||||
func (c *subscriptionCollector) Update(client *Client, ch chan<- prometheus.Metric) error {
|
||||
c.mu.Lock()
|
||||
nodes := make([]string, len(c.nodes))
|
||||
copy(nodes, c.nodes)
|
||||
c.mu.Unlock()
|
||||
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
errs []error
|
||||
emu sync.Mutex
|
||||
)
|
||||
|
||||
sem := make(chan struct{}, client.MaxConcurrent())
|
||||
|
||||
for _, node := range nodes {
|
||||
wg.Add(1)
|
||||
go func(node string) {
|
||||
defer wg.Done()
|
||||
sem <- struct{}{}
|
||||
defer func() { <-sem }()
|
||||
|
||||
if err := c.collectNode(client, ch, node); err != nil {
|
||||
emu.Lock()
|
||||
errs = append(errs, err)
|
||||
emu.Unlock()
|
||||
}
|
||||
}(node)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
if len(errs) > 0 {
|
||||
return fmt.Errorf("subscription collection errors: %v", errs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *subscriptionCollector) collectNode(client *Client, ch chan<- prometheus.Metric, node string) error {
|
||||
body, err := client.Get(fmt.Sprintf("/nodes/%s/subscription", node))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get subscription for node %s: %w", node, err)
|
||||
}
|
||||
|
||||
var resp subscriptionResponse
|
||||
if err := json.Unmarshal(body, &resp); err != nil {
|
||||
return fmt.Errorf("failed to parse subscription response for node %s: %w", node, err)
|
||||
}
|
||||
|
||||
id := "node/" + node
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(subscriptionInfoDesc, prometheus.GaugeValue, 1, id, resp.Data.Level)
|
||||
ch <- prometheus.MustNewConstMetric(subscriptionStatusDesc, prometheus.GaugeValue, 1, id, resp.Data.Status)
|
||||
|
||||
if resp.Data.NextDueDate != "" {
|
||||
t, err := time.Parse("2006-01-02", resp.Data.NextDueDate)
|
||||
if err != nil {
|
||||
c.logger.Warn("failed to parse nextduedate", "node", node, "err", err)
|
||||
} else {
|
||||
ch <- prometheus.MustNewConstMetric(subscriptionNextDueDesc, prometheus.GaugeValue, float64(t.Unix()), id)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
41
collector/subscription_test.go
Normal file
41
collector/subscription_test.go
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
package collector
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/testutil"
|
||||
)
|
||||
|
||||
func TestSubscriptionCollector(t *testing.T) {
|
||||
client := newTestClient(t, map[string]string{
|
||||
"/nodes/node01/subscription": "node_subscription.json",
|
||||
})
|
||||
|
||||
collector := newSubscriptionCollector(slog.Default())
|
||||
collector.SetNodes([]string{"node01"})
|
||||
adapter := &testCollectorAdapter{client: client, collector: collector}
|
||||
|
||||
reg := prometheus.NewRegistry()
|
||||
reg.MustRegister(adapter)
|
||||
|
||||
expected := `
|
||||
# HELP pve_subscription_info Subscription information for a node.
|
||||
# TYPE pve_subscription_info gauge
|
||||
pve_subscription_info{id="node/node01",level="b"} 1
|
||||
# HELP pve_subscription_next_due_timestamp_seconds Next due date of the subscription as a Unix timestamp.
|
||||
# TYPE pve_subscription_next_due_timestamp_seconds gauge
|
||||
pve_subscription_next_due_timestamp_seconds{id="node/node01"} 1.8016128e+09
|
||||
# HELP pve_subscription_status Subscription status for a node.
|
||||
# TYPE pve_subscription_status gauge
|
||||
pve_subscription_status{id="node/node01",status="active"} 1
|
||||
`
|
||||
|
||||
if err := testutil.GatherAndCompare(reg, strings.NewReader(expected),
|
||||
"pve_subscription_info", "pve_subscription_status", "pve_subscription_next_due_timestamp_seconds",
|
||||
); err != nil {
|
||||
t.Errorf("unexpected metrics: %s", err)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue