seems to work
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2023-09-28 14:27:03 +10:00
parent 95a48a89a6
commit 6c7d447c56
6 changed files with 162 additions and 61 deletions

View File

@@ -46,6 +46,7 @@ func main() {
ctx := context.Background() ctx := context.Background()
log.Printf("Starting execution. Built on %s from sha1 %s\n", buildTime, sha1ver)
log.Printf("Logging in to %s\n", config.Endpoint) log.Printf("Logging in to %s\n", config.Endpoint)
if _, err := ucsClient.AaaLogin(ctx); err != nil { if _, err := ucsClient.AaaLogin(ctx); err != nil {
log.Fatalf("Unable to login: %s\n", err) log.Fatalf("Unable to login: %s\n", err)

View File

@@ -0,0 +1,77 @@
package exporters
import (
"context"
"encoding/xml"
"log"
"github.com/dnaeon/go-ucs/api"
"github.com/dnaeon/go-ucs/mo"
"github.com/prometheus/client_golang/prometheus"
)
type UcsmBladeCollector struct {
ucsClient *api.Client
ctx context.Context
}
/*
type ComputeMbTempStats struct {
XMLName xml.Name `xml:"computeMbTempStats"`
Dn string `xml:"dn,attr,omitempty"`
FmTempSenIo string `xml:"fmTempSenIo,attr,omitempty"`
FmTempSenRear string `xml:"fmTempSenRear,attr,omitempty"`
}
*/
func NewUcsmBladeCollector(client *api.Client, ctx context.Context) *UcsmTemperaturesCollector {
return &UcsmTemperaturesCollector{
ucsClient: client,
ctx: ctx,
}
}
// Describe prometheus describe
func (u *UcsmBladeCollector) Describe(ch chan<- *prometheus.Desc) {
log.Printf("Running Describe for UcsmTemperaturesCollector\n")
ch <- prometheus.NewDesc("ucsm_temperature_sensor",
"UCSM temperature sensor for motherboard/cpu/psu",
[]string{"server", "component", "description"}, nil,
)
ch <- prometheus.NewDesc("ucsm_temperature_sensor",
"UCSM temperature sensor for motherboard/cpu/psu",
[]string{"server", "component", "description"}, nil,
)
}
// Collect prometheus collect
func (u *UcsmBladeCollector) Collect(ch chan<- prometheus.Metric) {
// The type into which we unmarshal the result data
type blades struct {
XMLName xml.Name
Blades []mo.ComputeBlade `xml:"computeBlade"`
}
bladeRequest := api.ConfigResolveClassRequest{
Cookie: u.ucsClient.Cookie,
ClassId: "computeBlade",
InHierarchical: "false",
}
var bladeList blades
log.Println("Retrieving managed objects with class `computeBlade`")
if err := u.ucsClient.ConfigResolveClass(u.ctx, bladeRequest, &bladeList); err != nil {
log.Fatalf("Unable to retrieve `computeBlade` managed object: %s", err)
}
log.Printf("Retrieved %d compute blades\n", len(bladeList.Blades))
for _, blade := range bladeList.Blades {
log.Printf("%s:\n", blade.Dn)
log.Printf("\tNumber of CPUs: %d\n", blade.NumOfCpus)
log.Printf("\tTotal Memory: %d\n", blade.TotalMemory)
log.Printf("\tModel: %s\n", blade.Model)
log.Printf("\tVendor: %s\n", blade.Vendor)
}
}

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"encoding/xml" "encoding/xml"
"log" "log"
"strings"
"github.com/dnaeon/go-ucs/api" "github.com/dnaeon/go-ucs/api"
"github.com/dnaeon/go-ucs/mo" "github.com/dnaeon/go-ucs/mo"
@@ -13,8 +14,14 @@ import (
type UcsmTemperaturesCollector struct { type UcsmTemperaturesCollector struct {
ucsClient *api.Client ucsClient *api.Client
ctx context.Context ctx context.Context
metrics []*prometheus.Desc
} }
var metrics []prometheus.Desc
//var ucsClient *api.Client
//var ctx context.Context
/* /*
type ComputeMbTempStats struct { type ComputeMbTempStats struct {
XMLName xml.Name `xml:"computeMbTempStats"` XMLName xml.Name `xml:"computeMbTempStats"`
@@ -24,91 +31,106 @@ type UcsmTemperaturesCollector struct {
} }
*/ */
func NewUcsmTemperatureCollector(client *api.Client, ctx context.Context) *UcsmTemperaturesCollector { func NewUcsmTemperatureCollector(client *api.Client, ctx context.Context) *UcsmTemperaturesCollector {
/*
ucsClient = client
ctx = ctx
*/
/*
return &main.Collector{
UcsmTemperature: prometheus.NewDesc("ucsm_temperature_sensor",
"UCSM temperature sensor for motherboard/cpu/psu",
[]string{"server", "component", "description"}, nil,
),
}
*/
return &UcsmTemperaturesCollector{ return &UcsmTemperaturesCollector{
ucsClient: client, ucsClient: client,
ctx: ctx, ctx: ctx,
metrics: []*prometheus.Desc{
prometheus.NewDesc(
"ucsm_temperature_sensor",
"UCSM temperature sensor for motherboard/cpu/psu",
[]string{"server", "component", "description"}, nil,
),
},
} }
} }
// Describe prometheus describe // Describe prometheus describe
func (u *UcsmTemperaturesCollector) Describe(ch chan<- *prometheus.Desc) { func (u *UcsmTemperaturesCollector) Describe(ch chan<- *prometheus.Desc) {
log.Printf("Running Describe for UcsmTemperaturesCollector\n") log.Printf("Running Describe for UcsmTemperaturesCollector\n")
ch <- prometheus.NewDesc("ucsm_temperature_sensor",
"UCSM temperature sensor for motherboard/cpu/psu", // Describe the metrics and send them on the channel
[]string{"server", "component", "description"}, nil, for _, desc := range u.metrics {
) ch <- desc
ch <- prometheus.NewDesc("ucsm_temperature_sensor", }
"UCSM temperature sensor for motherboard/cpu/psu",
[]string{"server", "component", "description"}, nil, /*
) metricDesc := prometheus.NewDesc("ucsm_temperature_sensor",
"UCSM temperature sensor for motherboard/cpu/psu",
[]string{"server", "component", "description"}, nil)
fmt.Printf("metricDesc: %v\n", *metricDesc)
metrics = append(metrics, *metricDesc)
ch <- metricDesc
*/
} }
// Collect prometheus collect // Collect prometheus collect
func (u *UcsmTemperaturesCollector) Collect(ch chan<- prometheus.Metric) { func (u *UcsmTemperaturesCollector) Collect(ch chan<- prometheus.Metric) {
// The type into which we unmarshal the result data // The type into which we unmarshal the result data
type blades struct {
XMLName xml.Name
Blades []mo.ComputeBlade `xml:"computeBlade"`
}
type temps struct { type temps struct {
XMLName xml.Name XMLName xml.Name
Temperatures []mo.ComputeMbTempStats `xml:"computeMbTempStats"` Temperatures []mo.ComputeMbTempStats `xml:"computeMbTempStats"`
} }
bladeRequest := api.ConfigResolveClassRequest{ tempRequest := api.ConfigResolveClassRequest{
Cookie: u.ucsClient.Cookie, Cookie: u.ucsClient.Cookie,
ClassId: "computeBlade", ClassId: "computeMbTempStats",
InHierarchical: "false", InHierarchical: "false",
} }
var bladeList blades var boardTempList temps
log.Println("Retrieving managed objects with class `computeBlade`") log.Println("Retrieving managed objects with class `computeMbTempStats`")
if err := u.ucsClient.ConfigResolveClass(u.ctx, bladeRequest, &bladeList); err != nil { if err := u.ucsClient.ConfigResolveClass(u.ctx, tempRequest, &boardTempList); err != nil {
log.Fatalf("Unable to retrieve `computeBlade` managed object: %s", err) log.Fatalf("Unable to retrieve `computeMbTempStats` managed object: %s", err)
} }
log.Printf("Retrieved %d compute blades\n", len(bladeList.Blades)) log.Printf("Retrieved temps for %d compute blades\n", len(boardTempList.Temperatures))
for _, blade := range bladeList.Blades { for _, temps := range boardTempList.Temperatures {
log.Printf("%s:\n", blade.Dn) // Substring the Dn for the temperatures to get the Dn for the parent blade itself
bladeDn := strings.Replace(temps.Dn, "/board/temp-stats", "", -1)
boardDn := "^" + blade.Dn + "/board/" log.Printf("Temps for blade %s:\n", bladeDn)
//boardDn := blade.Dn + "/board/" log.Printf("Front Temperature: %v\n", temps.FmTempSenIo)
log.Printf("Rear Temperature: %v\n", temps.FmTempSenRear)
log.Printf("%s:\n", boardDn) // TODO - work out a better way of running this twice, once for each temperature sensor
log.Printf("\tNumber of CPUs: %d\n", blade.NumOfCpus)
log.Printf("\tTotal Memory: %d\n", blade.TotalMemory)
log.Printf("\tModel: %s\n", blade.Model)
log.Printf("\tVendor: %s\n", blade.Vendor)
//log.Printf("\tThermal: %v\n", blade.ComputeBoard.Thermal)
filter := api.FilterWildcard{ // Collect the actual metric values and send them on the channel
FilterProperty: api.FilterProperty{ for _, desc := range u.metrics {
Class: "computeMbTempStats",
Property: "dn",
Value: boardDn,
},
}
log.Printf("Filter: %v\n", filter)
tempReq := api.ConfigResolveClassRequest{ // populate the labels array
Cookie: u.ucsClient.Cookie, //labelValues := []string{bladeDn, temps.Dn, "motherboard_rear_temperature"}
ClassId: "computeMbTempStats",
InHierarchical: "false", // generate the metric
InFilter: filter, metric1 := prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, temps.FmTempSenIo, bladeDn, temps.Dn, "motherboard_rear_temperature")
// send the data
ch <- metric1
// generate the metric
metric2 := prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, temps.FmTempSenRear, bladeDn, temps.Dn, "motherboard_front_temperature")
// send the data
ch <- metric2
} }
var tempList temps //ch <- prometheus.MustNewConstMetric()
log.Printf("Retrieving temperatures for this blade\n")
/*
if err := u.ucsClient.ConfigResolveClass(u.ctx, tempReq, &tempList); err != nil {
log.Fatalf("Unable to retrieve `computeMbTempStats` managed object: %s", err)
}
*/
u.ucsClient.ConfigResolveClass(u.ctx, tempReq, &tempList)
log.Printf("Front Temperature: %v\n", tempList)
} }
} }

View File

@@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"context" "context"
"encoding/xml" "encoding/xml"
"fmt"
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
@@ -186,8 +185,8 @@ func (c *Client) doRequest(ctx context.Context, in, out interface{}) error {
return err return err
} }
fmt.Println("doRequest sending following XML request:") //fmt.Println("doRequest sending following XML request:")
fmt.Println(string(data[:])) //fmt.Println(string(data[:]))
r, err := http.NewRequest("POST", c.apiUrl.String(), bytes.NewBuffer(data)) r, err := http.NewRequest("POST", c.apiUrl.String(), bytes.NewBuffer(data))
if err != nil { if err != nil {
@@ -209,8 +208,8 @@ func (c *Client) doRequest(ctx context.Context, in, out interface{}) error {
return err return err
} }
fmt.Println("doRequest received following response:") //fmt.Println("doRequest received following response:")
fmt.Println(string(body[:])) //fmt.Println(string(body[:]))
return xml.Unmarshal(body, &out) return xml.Unmarshal(body, &out)
} }

View File

@@ -23,7 +23,7 @@ import (
// As of now XML marshaling in Go always uses start and end tags, // As of now XML marshaling in Go always uses start and end tags,
// which results in XML elements like the one below. // which results in XML elements like the one below.
// //
// <Person name="me"></Person> // <Person name="me"></Person>
// //
// Above XML elements cannot be parsed by the remote Cisco UCS API endpoint, // Above XML elements cannot be parsed by the remote Cisco UCS API endpoint,
// and such API calls result in parse error returned to the client. // and such API calls result in parse error returned to the client.
@@ -41,13 +41,15 @@ import (
// hopefully one day make into the language as a feature. // hopefully one day make into the language as a feature.
// //
// https://groups.google.com/forum/#!topic/golang-nuts/guG6iOCRu08 // https://groups.google.com/forum/#!topic/golang-nuts/guG6iOCRu08
//
// Update by Nathan: update regex to also match caret when searching for tags to update
func xmlMarshalWithSelfClosingTags(in interface{}) ([]byte, error) { func xmlMarshalWithSelfClosingTags(in interface{}) ([]byte, error) {
data, err := xml.Marshal(in) data, err := xml.Marshal(in)
if err != nil { if err != nil {
return nil, err return nil, err
} }
re := regexp.MustCompile(`<([^/][\w\s\"\=\-\/]*)>\s*<(\/\w*)>`) re := regexp.MustCompile(`<([^/][\w\s\"\=\-\/\^]*)>\s*<(\/\w*)>`)
newData := re.ReplaceAllString(string(data), "<$1/>") newData := re.ReplaceAllString(string(data), "<$1/>")
return []byte(newData), nil return []byte(newData), nil

View File

@@ -263,8 +263,8 @@ type ComputeBoard struct {
type ComputeMbTempStats struct { type ComputeMbTempStats struct {
XMLName xml.Name `xml:"computeMbTempStats"` XMLName xml.Name `xml:"computeMbTempStats"`
Dn string `xml:"dn,attr,omitempty"` Dn string `xml:"dn,attr,omitempty"`
FmTempSenIo string `xml:"fmTempSenIo,attr,omitempty"` FmTempSenIo float64 `xml:"fmTempSenIo,attr,omitempty"`
FmTempSenRear string `xml:"fmTempSenRear,attr,omitempty"` FmTempSenRear float64 `xml:"fmTempSenRear,attr,omitempty"`
} }
// MemoryArray represents an array of memory units. // MemoryArray represents an array of memory units.