diff --git a/.drone.sh b/.drone.sh new file mode 100644 index 0000000..85cc6f0 --- /dev/null +++ b/.drone.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +export now=$(TZ=Australia/Sydney date '+%Y%m%d-%H%M%S') +echo $now +echo "build commences" +go build -ldflags "-X main.sha1ver=`git rev-parse HEAD` -X main.buildTime=$now" -o ucs-exporter +echo "build complete" +sha256sum ucs-exporter > smt_checksum.txt +ls -lah \ No newline at end of file diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..e300a8e --- /dev/null +++ b/.drone.yml @@ -0,0 +1,28 @@ +kind: pipeline +type: docker +name: default + +# Docs at https://docs.drone.io/pipeline/exec/overview/ +# Also see https://github.com/harness/drone-cli/blob/master/.drone.yml + +steps: +- name: build + image: golang + commands: + - sh ./.drone.sh + +- name: dell-deploy +# # https://github.com/cschlosser/drone-ftps/blob/master/README.md + image: cschlosser/drone-ftps + environment: + FTP_USERNAME: + from_secret: FTP_USERNAME + FTP_PASSWORD: + from_secret: FTP_PASSWORD + PLUGIN_HOSTNAME: ftp.emc.com:21 + PLUGIN_SECURE: false + PLUGIN_VERIFY: false + PLUGIN_CHMOD: false + #PLUGIN_DEBUG: false + PLUGIN_INCLUDE: ^ucs-exporter$,^ucs-exporter_checksum.txt$ + PLUGIN_EXCLUDE: ^\.git/$,^\cmd/$,^\internal/$ \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d3969f --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +ucs-exporter +ucs-exporter.* +.env +*.pem +.DS_Store \ No newline at end of file diff --git a/main.go b/cmd/server/main.go similarity index 55% rename from main.go rename to cmd/server/main.go index 6ca62e4..4869be9 100644 --- a/main.go +++ b/cmd/server/main.go @@ -3,13 +3,13 @@ package main import ( "context" "crypto/tls" - "encoding/xml" "flag" "log" "net/http" + "ucs-exporter/internal/exporters" "github.com/dnaeon/go-ucs/api" - "github.com/dnaeon/go-ucs/mo" + "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" ) @@ -20,6 +20,7 @@ func main() { vURL := flag.String("url", "", "The URL of a UCS Manager, eg https://server.domain.example/nuova") vUser := flag.String("user", "ucspe", "The username to use when connecting to UCS Manager") vPass := flag.String("password", "ucspe", "The password to use when connecting to UCS Manager") + listenPort := flag.String("listen-port", "9101", "The port to listen on for telemetry") flag.Parse() // The following example shows how to retrieve all compute blades. @@ -38,7 +39,7 @@ func main() { HttpClient: httpClient, } - client, err := api.NewClient(config) + ucsClient, err := api.NewClient(config) if err != nil { log.Fatalf("Unable to create API client: %s", err) } @@ -46,44 +47,20 @@ func main() { ctx := context.Background() log.Printf("Logging in to %s\n", config.Endpoint) - if _, err := client.AaaLogin(ctx); err != nil { + if _, err := ucsClient.AaaLogin(ctx); err != nil { log.Fatalf("Unable to login: %s\n", err) } - defer client.AaaLogout(ctx) + defer ucsClient.AaaLogout(ctx) - log.Printf("Got authentication cookie: %s\n", client.Cookie) + log.Printf("Got authentication cookie: %s\n", ucsClient.Cookie) - // The type into which we unmarshal the result data - type blades struct { - XMLName xml.Name - Blades []mo.ComputeBlade `xml:"computeBlade"` - } - - req := api.ConfigResolveClassRequest{ - Cookie: client.Cookie, - ClassId: "computeBlade", - InHierarchical: "false", - } - - var out blades - - log.Println("Retrieving managed objects with class `computeBlade`") - if err := client.ConfigResolveClass(ctx, req, &out); err != nil { - log.Fatalf("Unable to retrieve `computeBlade` managed object: %s", err) - } - - log.Printf("Retrieved %d compute blades\n", len(out.Blades)) - for _, blade := range out.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) - } + // Register prometheus exporters + exporter := exporters.NewUcsmTemperatureCollector(ucsClient, ctx) + prometheus.MustRegister(exporter) // Start prometheus exporter http.Handle("/metrics", promhttp.Handler()) // TODO - run this in a go routine to avoid blocking, as per cbs code - log.Fatal(http.ListenAndServe(":9101", nil)) + log.Fatal(http.ListenAndServe(*listenPort, nil)) } diff --git a/go.mod b/go.mod index e12135f..c011e07 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module ucs-exporter/v2 +module ucs-exporter go 1.21.0 diff --git a/internal/exporters/UcsmTemperatures.go b/internal/exporters/UcsmTemperatures.go new file mode 100644 index 0000000..88b3950 --- /dev/null +++ b/internal/exporters/UcsmTemperatures.go @@ -0,0 +1,106 @@ +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 UcsmTemperaturesCollector struct { + ucsClient *api.Client + ctx context.Context +} + +func NewUcsmTemperatureCollector(client *api.Client, ctx context.Context) *UcsmTemperaturesCollector { + return &UcsmTemperaturesCollector{ + ucsClient: client, + ctx: ctx, + } +} + +// 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, + ) +} + +// 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"` + } + + req := api.ConfigResolveClassRequest{ + Cookie: u.ucsClient.Cookie, + ClassId: "computeBlade", + InHierarchical: "false", + } + + var out blades + + log.Println("Retrieving managed objects with class `computeBlade`") + if err := u.ucsClient.ConfigResolveClass(u.ctx, req, &out); err != nil { + log.Fatalf("Unable to retrieve `computeBlade` managed object: %s", err) + } + + log.Printf("Retrieved %d compute blades\n", len(out.Blades)) + for _, blade := range out.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) + log.Printf("\tThermal: %s\n", blade.ComputeBoard.Thermal) + } + +} + +/* +logger = logging.getLogger("UcsmTemperatureSensorCollector") + +class UcsmTemperatureSensorCollector(BaseCollector): + def get_metrics(self): + return { + "sensor": GaugeMetricFamily("ucsm_temperature_sensor", + "UCSM temperature sensor for motherboard/cpu/psu", + labels=['server', 'component', + 'description']) + } + + def collect_metrics(self, server, handle): + logger.debug("Collecting Metrics ") + g = self.get_metrics()['sensor'] + + mb_temp = handle.query_classid(NamingId.COMPUTE_MB_TEMP_STATS) + for temp in mb_temp: + value_rear = math.nan if temp.fm_temp_sen_rear == 'not-applicable' else temp.fm_temp_sen_rear + g.add_metric(labels=[server, temp.dn, "motherboard_rear_temperature"], + value=value_rear) + value_io = math.nan if temp.fm_temp_sen_io == 'not-applicable' else temp.fm_temp_sen_io + g.add_metric(labels=[server, temp.dn, "motherboard_front_temperature"], + value=value_io) + + cpu_temp = handle.query_classid(NamingId.PROCESSOR_ENV_STATS) + for temp in cpu_temp: + g.add_metric(labels=[server, temp.dn, "cpu_temperature"], + value=temp.temperature) + + psu_temp = handle.query_classid(NamingId.EQUIPMENT_PSU_STATS) + for temp in psu_temp: + g.add_metric(labels=[server, temp.dn, "psu_temperature"], + value=temp.ambient_temp) + yield g +*/