Fixed error
Build Docker image on push / docker (push) Successful in 23s

This commit is contained in:
2026-08-24 01:55:40 +02:00
parent e8e94c5da1
commit 9b076d4353
5 changed files with 98 additions and 18 deletions
+6
View File
@@ -141,6 +141,12 @@ Enabled with `metrics.sdn`. Labels are `cluster`, `node`, `sdn`, and `sdn_id`.
| --- | --- |
| `pve_sdn_state` | `1` when the SDN resource status is `ok`, otherwise `0`. |
PVE 8 reports SDN zones as `sdn` cluster resources, PVE 9 reports them as
`network` resources (zones and fabrics). Both are exported as `pve_sdn_state`;
the `sdn` label holds the zone or fabric name and `sdn_id` the resource ID,
which differs between the two formats (`sdn/<node>/<zone>` versus
`network/<node>/<network-type>/<name>`).
## LXC containers
Enabled with `metrics.lxc`. Templates are skipped. Labels are `cluster`,
+1 -1
View File
@@ -53,7 +53,7 @@ func (c *PveSdnCollector) CollectMetrics() error {
labels := prometheus.Labels{
"cluster": cluster.GetClusterName(),
"node": node.Name,
"sdn": sdn.SDN,
"sdn": sdn.GetName(),
"sdn_id": sdn.ID,
}
+14 -2
View File
@@ -39,7 +39,7 @@ type PveClusterStatus struct {
// PveResource represents a generic PVE resource object.
type PveResource struct {
Type string `mapstructure:"type"` // Type of resource (e.g., "lxc", "qemu", "node", "storage", "sdn").
Type string `mapstructure:"type"` // Type of resource (e.g., "lxc", "qemu", "node", "storage", "sdn", "network").
Node string `mapstructure:"node"` // Node where the resource is located.
Status string `mapstructure:"status"` // Status of the resource (e.g., "running", "stopped", "online", "available").
ID string `mapstructure:"id"` // Unique identifier for the resource.
@@ -106,9 +106,12 @@ type PveStorageResource struct {
}
// PveSdnResource represents a PVE software-defined network (SDN) resource.
// PVE 8 reports these as "sdn" resources, PVE 9 as "network" resources.
type PveSdnResource struct {
PveResource
SDN string `mapstructure:"sdn"` // Name of the SDN.
SDN string `mapstructure:"sdn"` // Name of the SDN (only "sdn" resources).
Network string `mapstructure:"network"` // Name of the network entity (only "network" resources).
NetworkType string `mapstructure:"network-type"` // Type of the network entity, e.g. "zone" or "fabric" (only "network" resources).
}
// PVE resources.
@@ -455,6 +458,15 @@ func (r *PveResources) FindNodeSDN(nodeName string) *[]PveSdnResource {
return &sdns
}
// GetName returns the name of an SDN resource, regardless of whether it was
// reported as an "sdn" (PVE 8) or a "network" (PVE 9) resource.
func (r *PveSdnResource) GetName() string {
if r.SDN != "" {
return r.SDN
}
return r.Network
}
// GetStatusNumeric returns the numeric status of an SDN resource.
// Returns 1 if the status is "ok", otherwise returns 0.
func (r *PveSdnResource) GetStatusNumeric() float64 {
+62
View File
@@ -3,6 +3,8 @@ package proxmox
import (
"encoding/json"
"testing"
"github.com/mitchellh/mapstructure"
)
func TestPveClusterStatusGetClusterModeNumeric(t *testing.T) {
@@ -112,3 +114,63 @@ func TestPveZfsPoolStatusUnmarshal(t *testing.T) {
t.Fatal("missing cache counters must stay absent instead of becoming zero metrics")
}
}
func TestPveSdnResourceGetName(t *testing.T) {
tests := []struct {
name string
resource PveSdnResource
want string
}{
{
name: "sdn resource",
resource: PveSdnResource{SDN: "localnetwork"},
want: "localnetwork",
},
{
name: "network resource",
resource: PveSdnResource{Network: "localnetwork", NetworkType: "zone"},
want: "localnetwork",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.resource.GetName(); got != tt.want {
t.Fatalf("GetName() = %v, want %v", got, tt.want)
}
})
}
}
func TestPveSdnResourceDecodeNetwork(t *testing.T) {
// Resource object as reported by PVE 9 for SDN entities.
obj := map[string]interface{}{
"id": "network/pve1/zone/localnetwork",
"type": "network",
"node": "pve1",
"status": "ok",
"network": "localnetwork",
"network-type": "zone",
}
var resource PveSdnResource
if err := mapstructure.Decode(obj, &resource); err != nil {
t.Fatalf("Decode() error = %v", err)
}
if err := mapstructure.Decode(obj, &resource.PveResource); err != nil {
t.Fatalf("Decode() error = %v", err)
}
if got, want := resource.GetName(), "localnetwork"; got != want {
t.Fatalf("GetName() = %v, want %v", got, want)
}
if got, want := resource.NetworkType, "zone"; got != want {
t.Fatalf("NetworkType = %v, want %v", got, want)
}
if got, want := resource.ID, "network/pve1/zone/localnetwork"; got != want {
t.Fatalf("ID = %v, want %v", got, want)
}
if got, want := resource.GetStatusNumeric(), float64(1); got != want {
t.Fatalf("GetStatusNumeric() = %v, want %v", got, want)
}
}
+15 -15
View File
@@ -64,18 +64,18 @@ func (instance *PveApiClient) GetClusterStatus() (*PveClusterStatus, error) {
switch obj["type"] {
case "cluster":
if err := mapstructure.Decode(obj, &cluster); err != nil {
log.Errorf("Unable to decode cluster status object. Error:", err)
log.Errorf("Unable to decode cluster status object. Error: %s", err)
continue
}
case "node":
var node PveNodeStatus
if err := mapstructure.Decode(obj, &node); err != nil {
log.Errorf("Unable to decode node status object. Error:", err)
log.Errorf("Unable to decode node status object. Error: %s", err)
continue
}
cluster.NodeStatuses = append(cluster.NodeStatuses, node)
default:
log.Errorf("Unable to decode cluster status object. Unknown type:", obj["type"])
log.Warnf("Skipping cluster status object of unknown type '%v'.", obj["type"])
}
}
@@ -106,60 +106,60 @@ func (instance *PveApiClient) GetClusterResources() (*PveResources, error) {
case "lxc":
var resource PveLxcResource
if err := mapstructure.Decode(obj, &resource); err != nil {
log.Errorf("Unable to decode cluster resource object. Error:", err)
log.Errorf("Unable to decode cluster resource object. Error: %s", err)
continue
}
if err := mapstructure.Decode(obj, &resource.PveResource); err != nil {
log.Errorf("Unable to decode cluster resource object. Error:", err)
log.Errorf("Unable to decode cluster resource object. Error: %s", err)
continue
}
resources.CTs = append(resources.CTs, resource)
case "qemu":
var resource PveQemuResource
if err := mapstructure.Decode(obj, &resource); err != nil {
log.Errorf("Unable to decode cluster resource object. Error:", err)
log.Errorf("Unable to decode cluster resource object. Error: %s", err)
continue
}
if err := mapstructure.Decode(obj, &resource.PveResource); err != nil {
log.Errorf("Unable to decode cluster resource object. Error:", err)
log.Errorf("Unable to decode cluster resource object. Error: %s", err)
continue
}
resources.VMs = append(resources.VMs, resource)
case "node":
var resource PveNodeResource
if err := mapstructure.Decode(obj, &resource); err != nil {
log.Errorf("Unable to decode cluster resource object. Error:", err)
log.Errorf("Unable to decode cluster resource object. Error: %s", err)
continue
}
if err := mapstructure.Decode(obj, &resource.PveResource); err != nil {
log.Errorf("Unable to decode cluster resource object. Error:", err)
log.Errorf("Unable to decode cluster resource object. Error: %s", err)
continue
}
resources.Nodes = append(resources.Nodes, resource)
case "storage":
var resource PveStorageResource
if err := mapstructure.Decode(obj, &resource); err != nil {
log.Errorf("Unable to decode cluster resource object. Error:", err)
log.Errorf("Unable to decode cluster resource object. Error: %s", err)
continue
}
if err := mapstructure.Decode(obj, &resource.PveResource); err != nil {
log.Errorf("Unable to decode cluster resource object. Error:", err)
log.Errorf("Unable to decode cluster resource object. Error: %s", err)
continue
}
resources.Storages = append(resources.Storages, resource)
case "sdn":
case "sdn", "network":
var resource PveSdnResource
if err := mapstructure.Decode(obj, &resource); err != nil {
log.Errorf("Unable to decode cluster resource object. Error:", err)
log.Errorf("Unable to decode cluster resource object. Error: %s", err)
continue
}
if err := mapstructure.Decode(obj, &resource.PveResource); err != nil {
log.Errorf("Unable to decode cluster resource object. Error:", err)
log.Errorf("Unable to decode cluster resource object. Error: %s", err)
continue
}
resources.SDNs = append(resources.SDNs, resource)
default:
log.Errorf("Unable to decode cluster resource object. Unknown type:", obj["type"])
log.Warnf("Skipping cluster resource object of unknown type '%v'.", obj["type"])
}
}