diff --git a/cmd/server/main.go b/cmd/server/main.go index cff2762..a6459d8 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -46,6 +46,7 @@ func main() { 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) if _, err := ucsClient.AaaLogin(ctx); err != nil { log.Fatalf("Unable to login: %s\n", err) diff --git a/internal/exporters/UcsmBlade.go b/internal/exporters/UcsmBlade.go new file mode 100644 index 0000000..9a0c73d --- /dev/null +++ b/internal/exporters/UcsmBlade.go @@ -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) + } + +} diff --git a/internal/exporters/UcsmTemperatures.go b/internal/exporters/UcsmTemperatures.go index ac04c31..05dcfeb 100644 --- a/internal/exporters/UcsmTemperatures.go +++ b/internal/exporters/UcsmTemperatures.go @@ -4,6 +4,7 @@ import ( "context" "encoding/xml" "log" + "strings" "github.com/dnaeon/go-ucs/api" "github.com/dnaeon/go-ucs/mo" @@ -13,8 +14,14 @@ import ( type UcsmTemperaturesCollector struct { ucsClient *api.Client ctx context.Context + metrics []*prometheus.Desc } +var metrics []prometheus.Desc + +//var ucsClient *api.Client +//var ctx context.Context + /* type ComputeMbTempStats struct { XMLName xml.Name `xml:"computeMbTempStats"` @@ -24,91 +31,106 @@ type UcsmTemperaturesCollector struct { } */ 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{ ucsClient: client, 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 func (u *UcsmTemperaturesCollector) 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, - ) + + // Describe the metrics and send them on the channel + for _, desc := range u.metrics { + ch <- desc + } + + /* + 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 func (u *UcsmTemperaturesCollector) 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"` - } - type temps struct { XMLName xml.Name Temperatures []mo.ComputeMbTempStats `xml:"computeMbTempStats"` } - bladeRequest := api.ConfigResolveClassRequest{ + tempRequest := api.ConfigResolveClassRequest{ Cookie: u.ucsClient.Cookie, - ClassId: "computeBlade", + ClassId: "computeMbTempStats", 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 { - log.Fatalf("Unable to retrieve `computeBlade` managed object: %s", err) + if err := u.ucsClient.ConfigResolveClass(u.ctx, tempRequest, &boardTempList); err != nil { + log.Fatalf("Unable to retrieve `computeMbTempStats` 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("Retrieved temps for %d compute blades\n", len(boardTempList.Temperatures)) + for _, temps := range boardTempList.Temperatures { + // 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/" - //boardDn := blade.Dn + "/board/" + log.Printf("Temps for blade %s:\n", bladeDn) + log.Printf("Front Temperature: %v\n", temps.FmTempSenIo) + log.Printf("Rear Temperature: %v\n", temps.FmTempSenRear) - log.Printf("%s:\n", boardDn) - 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) + // TODO - work out a better way of running this twice, once for each temperature sensor - filter := api.FilterWildcard{ - FilterProperty: api.FilterProperty{ - Class: "computeMbTempStats", - Property: "dn", - Value: boardDn, - }, - } - log.Printf("Filter: %v\n", filter) + // Collect the actual metric values and send them on the channel + for _, desc := range u.metrics { - tempReq := api.ConfigResolveClassRequest{ - Cookie: u.ucsClient.Cookie, - ClassId: "computeMbTempStats", - InHierarchical: "false", - InFilter: filter, + // populate the labels array + //labelValues := []string{bladeDn, temps.Dn, "motherboard_rear_temperature"} + + // generate the metric + 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 - 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) + //ch <- prometheus.MustNewConstMetric() } } diff --git a/local/go-ucs/api/client.go b/local/go-ucs/api/client.go index 1a415ef..bb969f9 100755 --- a/local/go-ucs/api/client.go +++ b/local/go-ucs/api/client.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "encoding/xml" - "fmt" "io" "net/http" "net/url" @@ -186,8 +185,8 @@ func (c *Client) doRequest(ctx context.Context, in, out interface{}) error { return err } - fmt.Println("doRequest sending following XML request:") - fmt.Println(string(data[:])) + //fmt.Println("doRequest sending following XML request:") + //fmt.Println(string(data[:])) r, err := http.NewRequest("POST", c.apiUrl.String(), bytes.NewBuffer(data)) if err != nil { @@ -209,8 +208,8 @@ func (c *Client) doRequest(ctx context.Context, in, out interface{}) error { return err } - fmt.Println("doRequest received following response:") - fmt.Println(string(body[:])) + //fmt.Println("doRequest received following response:") + //fmt.Println(string(body[:])) return xml.Unmarshal(body, &out) } diff --git a/local/go-ucs/api/xml.go b/local/go-ucs/api/xml.go index 08ce52c..6909383 100755 --- a/local/go-ucs/api/xml.go +++ b/local/go-ucs/api/xml.go @@ -23,7 +23,7 @@ import ( // As of now XML marshaling in Go always uses start and end tags, // which results in XML elements like the one below. // -// +// // // 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. @@ -41,13 +41,15 @@ import ( // hopefully one day make into the language as a feature. // // 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) { data, err := xml.Marshal(in) if err != nil { return nil, err } - re := regexp.MustCompile(`<([^/][\w\s\"\=\-\/]*)>\s*<(\/\w*)>`) + re := regexp.MustCompile(`<([^/][\w\s\"\=\-\/\^]*)>\s*<(\/\w*)>`) newData := re.ReplaceAllString(string(data), "<$1/>") return []byte(newData), nil diff --git a/local/go-ucs/mo/mo.go b/local/go-ucs/mo/mo.go index b5b945d..dcebecb 100755 --- a/local/go-ucs/mo/mo.go +++ b/local/go-ucs/mo/mo.go @@ -263,8 +263,8 @@ type ComputeBoard struct { 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"` + FmTempSenIo float64 `xml:"fmTempSenIo,attr,omitempty"` + FmTempSenRear float64 `xml:"fmTempSenRear,attr,omitempty"` } // MemoryArray represents an array of memory units.