This commit is contained in:
@@ -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`,
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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"])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user