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_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
|
## LXC containers
|
||||||
|
|
||||||
Enabled with `metrics.lxc`. Templates are skipped. Labels are `cluster`,
|
Enabled with `metrics.lxc`. Templates are skipped. Labels are `cluster`,
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ func (c *PveSdnCollector) CollectMetrics() error {
|
|||||||
labels := prometheus.Labels{
|
labels := prometheus.Labels{
|
||||||
"cluster": cluster.GetClusterName(),
|
"cluster": cluster.GetClusterName(),
|
||||||
"node": node.Name,
|
"node": node.Name,
|
||||||
"sdn": sdn.SDN,
|
"sdn": sdn.GetName(),
|
||||||
"sdn_id": sdn.ID,
|
"sdn_id": sdn.ID,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+14
-2
@@ -39,7 +39,7 @@ type PveClusterStatus struct {
|
|||||||
|
|
||||||
// PveResource represents a generic PVE resource object.
|
// PveResource represents a generic PVE resource object.
|
||||||
type PveResource struct {
|
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.
|
Node string `mapstructure:"node"` // Node where the resource is located.
|
||||||
Status string `mapstructure:"status"` // Status of the resource (e.g., "running", "stopped", "online", "available").
|
Status string `mapstructure:"status"` // Status of the resource (e.g., "running", "stopped", "online", "available").
|
||||||
ID string `mapstructure:"id"` // Unique identifier for the resource.
|
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.
|
// PveSdnResource represents a PVE software-defined network (SDN) resource.
|
||||||
|
// PVE 8 reports these as "sdn" resources, PVE 9 as "network" resources.
|
||||||
type PveSdnResource struct {
|
type PveSdnResource struct {
|
||||||
PveResource
|
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.
|
// PVE resources.
|
||||||
@@ -455,6 +458,15 @@ func (r *PveResources) FindNodeSDN(nodeName string) *[]PveSdnResource {
|
|||||||
return &sdns
|
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.
|
// GetStatusNumeric returns the numeric status of an SDN resource.
|
||||||
// Returns 1 if the status is "ok", otherwise returns 0.
|
// Returns 1 if the status is "ok", otherwise returns 0.
|
||||||
func (r *PveSdnResource) GetStatusNumeric() float64 {
|
func (r *PveSdnResource) GetStatusNumeric() float64 {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package proxmox
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPveClusterStatusGetClusterModeNumeric(t *testing.T) {
|
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")
|
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"] {
|
switch obj["type"] {
|
||||||
case "cluster":
|
case "cluster":
|
||||||
if err := mapstructure.Decode(obj, &cluster); err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
case "node":
|
case "node":
|
||||||
var node PveNodeStatus
|
var node PveNodeStatus
|
||||||
if err := mapstructure.Decode(obj, &node); err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
cluster.NodeStatuses = append(cluster.NodeStatuses, node)
|
cluster.NodeStatuses = append(cluster.NodeStatuses, node)
|
||||||
default:
|
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":
|
case "lxc":
|
||||||
var resource PveLxcResource
|
var resource PveLxcResource
|
||||||
if err := mapstructure.Decode(obj, &resource); err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
if err := mapstructure.Decode(obj, &resource.PveResource); err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
resources.CTs = append(resources.CTs, resource)
|
resources.CTs = append(resources.CTs, resource)
|
||||||
case "qemu":
|
case "qemu":
|
||||||
var resource PveQemuResource
|
var resource PveQemuResource
|
||||||
if err := mapstructure.Decode(obj, &resource); err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
if err := mapstructure.Decode(obj, &resource.PveResource); err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
resources.VMs = append(resources.VMs, resource)
|
resources.VMs = append(resources.VMs, resource)
|
||||||
case "node":
|
case "node":
|
||||||
var resource PveNodeResource
|
var resource PveNodeResource
|
||||||
if err := mapstructure.Decode(obj, &resource); err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
if err := mapstructure.Decode(obj, &resource.PveResource); err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
resources.Nodes = append(resources.Nodes, resource)
|
resources.Nodes = append(resources.Nodes, resource)
|
||||||
case "storage":
|
case "storage":
|
||||||
var resource PveStorageResource
|
var resource PveStorageResource
|
||||||
if err := mapstructure.Decode(obj, &resource); err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
if err := mapstructure.Decode(obj, &resource.PveResource); err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
resources.Storages = append(resources.Storages, resource)
|
resources.Storages = append(resources.Storages, resource)
|
||||||
case "sdn":
|
case "sdn", "network":
|
||||||
var resource PveSdnResource
|
var resource PveSdnResource
|
||||||
if err := mapstructure.Decode(obj, &resource); err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
if err := mapstructure.Decode(obj, &resource.PveResource); err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
resources.SDNs = append(resources.SDNs, resource)
|
resources.SDNs = append(resources.SDNs, resource)
|
||||||
default:
|
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