diff --git a/.github/workflows/pm_exporter.yml b/.github/workflows/docker.yml similarity index 50% rename from .github/workflows/pm_exporter.yml rename to .github/workflows/docker.yml index 1af9d94..59a16c8 100644 --- a/.github/workflows/pm_exporter.yml +++ b/.github/workflows/docker.yml @@ -7,21 +7,6 @@ on: branches: - main jobs: - binary: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Go - uses: actions/setup-go@v2 - with: - go-version: 1.16 - - name: Build - run: go build -o pm_exporter cmd/main.go - - name: Upload pm_exporter - uses: actions/upload-artifact@v2 - with: - name: pm_exporter - path: pm_exporter build-docker-image: runs-on: ubuntu-latest needs: binary @@ -33,15 +18,11 @@ jobs: registry: 'https://index.docker.io/v1/' username: '${{ secrets.DOCKER_USERNAME }}' password: '${{ secrets.DOCKER_PASSWORD }}' - - name: Download a Build Artifact - uses: actions/download-artifact@v2.0.9 - with: - name: pm_exporter - name: Build and push container image uses: docker/build-push-action@v2 with: context: . push: true tags: | - gportal/pm_exporter:${{ github.sha }} - gportal/pm_exporter:latest + gportal/redfish_exporter:${{ github.sha }} + gportal/redfish_exporter:latest diff --git a/Dockerfile b/Dockerfile index 127555d..8f9bb43 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,21 @@ -FROM fedora:34 +FROM gportal/golang:latest -COPY pm_exporter /usr/local/bin/pm_exporter -RUN chmod +x /usr/local/bin/pm_exporter +# Import application source +COPY ./ /opt/app-root/src -ENTRYPOINT [ "/usr/local/bin/pm_exporter" ] +# Change working directory +WORKDIR /opt/app-root/src + +# Build binary for Latency Service +RUN go build -v -o "${APP_ROOT}/redfish_exporter" cmd/main.go && \ + setcap cap_net_bind_service+ep "${APP_ROOT}/redfish_exporter" + +# Finally delete application source +RUN rm -rf /opt/app-root/src/* + +EXPOSE 9096 + +RUN /usr/bin/fix-permissions ${APP_ROOT} && \ + /usr/bin/fix-permissions /data/ + +CMD ["/opt/app-root/redfish_exporter"] \ No newline at end of file diff --git a/LICENSE b/LICENSE index 8c0fa28..b6c6e64 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 Ociris GmbH +Copyright (c) 2024 Ociris GmbH Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 3592547..fc19e57 100644 --- a/README.md +++ b/README.md @@ -1,71 +1,13 @@ -# pm_exporter +# Redfish Prometheus Exporter -[![Build](https://github.com/GPORTALcloud/pm_exporter/actions/workflows/pm_exporter.yml/badge.svg?branch=main)](https://github.com/GPORTALcloud/pm_exporter/actions/workflows/pm_exporter.yml) +### This is WIP. -!!! STILL UNDER DEVELOPMENT !!! +Redfish Exporter is providing generic metrics fetched from Redfish endpoints. -pm_exporter is providing metrics fetched from platform management endpoints (such as iDRAC or ILO) and exposing those through HTTP for making those readable for prometheus. - -For fetching those details the platform management specific API's are used (such as Redfish for iDRAC). -Those metrics are getting persisted within the runtime for the duration specified through command line arguments. - -The list of platform managements to fetch details from is stored within a yaml file, each platform management needs to be defined there, -including their credentials, address and types. In addition, this config file allows adding -custom labels to the metrics which can be used for example for passing the id of the server within your -own inventory system or the switch the platform management is connected to. - -Metrics which are getting collected: - -| metric | Type | description | -| ------------------------------ |:-------:| :-----------------------------------------------| -| pm_platform_management_up | gauge | Defines if the platform management is reachable | -| pm_powersupply_health | gauge | Indicates the health status of the Power Supply | -| pm_battery_health | gauge | Indicates the health status of the Battery | -| pm_cpu_health | gauge | Indicates the health status of the CPU | -| pm_fan_health | gauge | Indicates the health status of the Fan | -| pm_storage_health | gauge | Indicates the health status of the Storage | -| pm_temperature_health | gauge | Indicates the health status of the Temperature | -| pm_intrusion_health | gauge | Indicates if the chassis is closed or not | -| pm_license_health | gauge | Indicates if the license is still valid | -| pm_memory_health | gauge | Indicates the health status of the Memory | -| pm_overall_health | gauge | Indicates if any of the health metrics are bad | - -Platform Managements are getting implemented once there is a host needs to get monitored, this list will grow with time. - -Currently supported: -* iDRAC (redfish) - - -## Install -There are different ways of running the pm_exporter. - -### Precompiled binaries -Precompiled binaries are getting created with each workflow run. Just download the artifact below "Actions" and -place them wherever you want. - -### Docker images -pm_exporter is getting build and published to the Docker and Github registry. - -You can just launch your own pm_exporter instance using the following command (arguments you're fine with the default you can just omit) - -``` -docker run -d --name pm_exporter --rm \ - -v /local/config.yml:/etc/pm_exporter.yml \ - msniveau/pm_exporter \ - --config.file=/etc/pm_exporter.yml \ - --web.listen-address=0.0.0.0:9096 \ - --web.enable-lifecycle \ - --metrics.persist_duration=5m \ - --metrics.refresh_interval=1m \ - --worker.count=5 -``` - -## Command line arguments -| argument | default | description | -| ------------------------- |:----------------------:| :------------------------------------------------------| -| --config.file | /etc/pm_exporter.yml | Config file location | -| -metric.persist_duration | 1m30s | Duration metrics are getting persisted | -| -metric.refresh_interval | 1m | Interval metrics are getting fetched | -| -web.enable-lifecycle | false | Adds the /-/reload endpoint for config reload | -| -web.listen-address | 0.0.0.0:9096 | Listen address the HTTP server runs on | -| -worker.count | 10 | The amount of workers used for fetching the metrics | \ No newline at end of file +Tested with: +* HPE ILO4 +* HPE ILO5 +* Dell iDRAC 8 +* Dell iDRAC 9 +* AsRockRack +* SuperMicro SuperServer diff --git a/cmd/main.go b/cmd/main.go index 0556713..5335336 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -2,48 +2,31 @@ package main import ( "flag" + "github.com/g-portal/redfish_exporter/pkg/config" + "github.com/g-portal/redfish_exporter/pkg/metric" + "github.com/gin-gonic/gin" "log" - "net/http" - "time" - - "github.com/GPORTALcloud/pm_exporter/pkg/config" - "github.com/GPORTALcloud/pm_exporter/pkg/metric" - "github.com/GPORTALcloud/pm_exporter/pkg/updater" ) var ( - workerCount int - refreshInterval time.Duration - persistDuration time.Duration - listenAddr string - enableLifecycle bool - configPath string + listenAddr string + configPath string ) func init() { - flag.StringVar(&configPath, "config.file", "/etc/pm_exporter.yml", "Defines the path to the platform management config") + flag.StringVar(&configPath, "config.file", "/etc/redfish_exporter/config.yml", "Defines the path to the platform management config") flag.StringVar(&listenAddr, "web.listen-address", "0.0.0.0:9096", "Address the exporter listens on") - flag.BoolVar(&enableLifecycle, "web.enable-lifecycle", false, "With this parameter set calls to /-/reload are reloading the config") - flag.IntVar(&workerCount, "worker.count", 10, "Worker processes for calling platform management API's") - flag.DurationVar(&refreshInterval, "metric.refresh_interval", time.Second*60, "Interval the exporter refresh the metrics") - flag.DurationVar(&persistDuration, "metric.persist_duration", time.Second*90, "Duration collected metrics persist before being invalidated") flag.Parse() - config.Prepare(configPath) + + config.SetPath(configPath) } func main() { + r := gin.Default() - metric.SetPersistDuration(persistDuration) - - go updater.Run(workerCount, refreshInterval) - http.HandleFunc("/metrics", metric.MetricHttpHandler) - - if enableLifecycle { - http.HandleFunc("/-/reload", config.ReloadConfigHandler) - } - + r.GET("/metrics", metric.Handle) log.Println("Starting listening on: " + listenAddr) - err := http.ListenAndServe(listenAddr, nil) + err := r.Run(listenAddr) if err != nil { log.Printf("Error starting http server: %v", err) } diff --git a/config.example.yml b/config.example.yml index 8bb53a8..8056866 100644 --- a/config.example.yml +++ b/config.example.yml @@ -1,21 +1,6 @@ --- -platform_managements: - - host: https://192.168.0.1 - username: root - password: toor - type: IDRAC - labels: - funny: labels - - host: https://192.168.0.2 - username: root - password: toor - type: IDRAC - labels: - customer: 1337 - - host: https://192.168.0.3 - username: root - password: toor - type: IDRAC - labels: - internal_name: host00123 +# Redfish Default Credentials +redfish: + username: root + password: toor diff --git a/go.mod b/go.mod index 299a893..861c548 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,44 @@ -module github.com/GPORTALcloud/pm_exporter +module github.com/g-portal/redfish_exporter -go 1.16 +go 1.22 require ( - github.com/fsnotify/fsnotify v1.4.9 + github.com/gin-gonic/gin v1.9.1 + github.com/prometheus/client_golang v1.19.0 + github.com/stmcginnis/gofish v0.15.0 gopkg.in/yaml.v2 v2.4.0 ) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/bytedance/sonic v1.11.3 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect + github.com/chenzhuoyu/iasm v0.9.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.19.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.0 // indirect + github.com/prometheus/client_model v0.6.0 // indirect + github.com/prometheus/common v0.51.1 // indirect + github.com/prometheus/procfs v0.13.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + golang.org/x/arch v0.7.0 // indirect + golang.org/x/crypto v0.21.0 // indirect + golang.org/x/net v0.22.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum index d49cec7..c8066cc 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,114 @@ -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9 h1:L2auWcuQIvxz9xSEqzESnV/QN/gNRXNApHi3fYwl2w0= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= +github.com/bytedance/sonic v1.11.3 h1:jRN+yEjakWh8aK5FzrciUHG8OFXK+4/KrAX/ysEtHAA= +github.com/bytedance/sonic v1.11.3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= +github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= +github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= +github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= +github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4= +github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= +github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= +github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= +github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= +github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= +github.com/prometheus/common v0.51.1 h1:eIjN50Bwglz6a/c3hAgSMcofL3nD+nFQkV6Dd4DsQCw= +github.com/prometheus/common v0.51.1/go.mod h1:lrWtQx+iDfn2mbH5GUzlH9TSHyfZpHkSiG1W7y3sF2Q= +github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o= +github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/stmcginnis/gofish v0.15.0 h1:8TG41+lvJk/0Nf8CIIYErxbMlQUy80W0JFRZP3Ld82A= +github.com/stmcginnis/gofish v0.15.0/go.mod h1:BLDSFTp8pDlf/xDbLZa+F7f7eW0E/CHCboggsu8CznI= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= +golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/pkg/api/api.go b/pkg/api/api.go index 04d4215..93f5cad 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -1,13 +1,22 @@ package api import ( - "net/http" - "time" + "github.com/g-portal/redfish_exporter/pkg/drivers/redfish" + "github.com/prometheus/client_golang/prometheus" ) -var client = &http.Client{Timeout: 15 * time.Second} - type Client interface { - GetHost() string - FetchInventoryMetrics() error + Connect(host, username, password string, tlsVerify bool) error + GetMetrics() (*prometheus.Registry, error) + Disconnect() error +} + +func NewClient(host, username, password string, tlsVerify bool) (Client, error) { + client := &redfish.Redfish{} + err := client.Connect(host, username, password, tlsVerify) + if err != nil { + return nil, err + } + + return client, nil } diff --git a/pkg/api/hpe.go b/pkg/api/hpe.go deleted file mode 100644 index d624b14..0000000 --- a/pkg/api/hpe.go +++ /dev/null @@ -1,36 +0,0 @@ -package api - -import ( - "encoding/base64" - "fmt" -) - -type HpeApi struct { - auth string - host string -} - -func NewHpeApi(host string) *HpeApi { - r := HpeApi{host: host} - return &r -} - -func (r *HpeApi) SetUser(user string, pass string) *HpeApi { - r.auth = base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", user, pass))) - return r -} - -func (r *HpeApi) Request(path string) (*HpeSystemMetric, error) { - // TODO implement - return nil, nil -} -func (r *HpeApi) GetHost() string { - return r.host -} - -func (r *HpeApi) FetchInventoryMetrics() error { - return nil -} - -type HpeSystemMetric struct { -} diff --git a/pkg/api/redfish.go b/pkg/api/redfish.go deleted file mode 100644 index 75ecd7f..0000000 --- a/pkg/api/redfish.go +++ /dev/null @@ -1,160 +0,0 @@ -package api - -import ( - "crypto/tls" - "encoding/base64" - "encoding/json" - "fmt" - "io/ioutil" - "log" - "net/http" - "time" - - "github.com/GPORTALcloud/pm_exporter/pkg/metric" -) - -type RedfishApi struct { - auth string - host string -} - -func NewRedfishAPI(host string) *RedfishApi { - r := RedfishApi{host: host} - return &r -} - -// Set redfish login credentials -func (r *RedfishApi) SetUser(user string, pass string) *RedfishApi { - r.auth = base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", user, pass))) - return r -} - -// HTTP request wrapper adds authentication headers -// and parses the DellSystemMetric result -// TODO: rename -func (r *RedfishApi) Request(path string) (*DellSystemMetric, error) { - http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} - url := fmt.Sprintf("%s%s", r.host, path) - req, err := http.NewRequest("GET", url, nil) - if err != nil { - log.Println("Error building request") - } - req.Header.Add("Authorization", fmt.Sprintf("Basic %v", r.auth)) - req.Header.Add("Content-Type", "application/json") - req.Header.Add("Accept", "application/json") - res, err := client.Do(req) - if err != nil { - log.Printf("request failed: %v", err) - return nil, err - } - defer res.Body.Close() - body, err := ioutil.ReadAll(res.Body) - obj := DellSystemMetric{} - json.Unmarshal(body, &obj) - return &obj, nil -} - -func (r *RedfishApi) GetHost() string { - return r.host -} - -// utility function, converts "OK" and "" to 1 -// note: if there is no power supply health check will result in 1 -// only if state is critical / != null and != OK it will result in 0 -func checkOK(ok string) int { - if ok == "OK" || ok == "" || ok == "Unknown" { - return 1 - } - return 0 -} - -// FetchInventoryMetrics is used for populating metrics to the collector -// metrics.UpdateMetric stores those within the metric module -// no need to return anything except the error here -// also, meta metric "pm_platform_management_up" is used for indicating if -// the platform management is reachable or not -func (r *RedfishApi) FetchInventoryMetrics() error { - response, err := r.Request("/redfish/v1/Dell/Systems/System.Embedded.1/DellSystem/System.Embedded.1") - if err != nil { - metric.UpdateMetric(metric.PMPlatformManagementUp, r.host, 0) - return err - } - metric.UpdateMetric(metric.PMPlatformManagementUp, r.host, 1) - metric.UpdateMetric(metric.PMPowerSupplyHealth, r.host, checkOK(response.Psrollupstatus)) - metric.UpdateMetric(metric.PMBatteryHealth, r.host, checkOK(response.Batteryrollupstatus)) - metric.UpdateMetric(metric.PMCpuHealth, r.host, checkOK(response.Cpurollupstatus)) - metric.UpdateMetric(metric.PMFanHealth, r.host, checkOK(response.Fanrollupstatus)) - metric.UpdateMetric(metric.PMStorageHealth, r.host, checkOK(response.Storagerollupstatus)) - metric.UpdateMetric(metric.PMTemperatureHealth, r.host, checkOK(response.Temprollupstatus)) - metric.UpdateMetric(metric.PMIntrusionHealth, r.host, checkOK(response.Intrusionrollupstatus)) - metric.UpdateMetric(metric.PMLicenceHealth, r.host, checkOK(response.Licensingrollupstatus)) - metric.UpdateMetric(metric.PMMemoryHealth, r.host, checkOK(response.Sysmemprimarystatus)) - - // summarized metric across all health variables - overallHealth := checkOK(response.Psrollupstatus) * - checkOK(response.Batteryrollupstatus) * - checkOK(response.Cpurollupstatus) * - checkOK(response.Fanrollupstatus) * - checkOK(response.Storagerollupstatus) * - checkOK(response.Temprollupstatus) * - checkOK(response.Intrusionrollupstatus) * - checkOK(response.Licensingrollupstatus) - metric.UpdateMetric(metric.PMOverallHealth, r.host, overallHealth) - return nil -} - -// Struct for the system metric endpoint -type DellSystemMetric struct { - OdataContext string `json:"@odata.context"` - OdataID string `json:"@odata.id"` - OdataType string `json:"@odata.type"` - Biosreleasedate string `json:"BIOSReleaseDate"` - Baseboardchassisslot string `json:"BaseBoardChassisSlot"` - Batteryrollupstatus string `json:"BatteryRollupStatus"` - Bladegeometry string `json:"BladeGeometry"` - Cmcip interface{} `json:"CMCIP"` - Cpurollupstatus string `json:"CPURollupStatus"` - Chassismodel string `json:"ChassisModel"` - Chassisname string `json:"ChassisName"` - Chassisservicetag string `json:"ChassisServiceTag"` - Chassissystemheightunit int `json:"ChassisSystemHeightUnit"` - Currentrollupstatus string `json:"CurrentRollupStatus"` - Description string `json:"Description"` - Estimatedexhausttemperaturecelsius int `json:"EstimatedExhaustTemperatureCelsius"` - Estimatedsystemairflowcfm int `json:"EstimatedSystemAirflowCFM"` - Expressservicecode string `json:"ExpressServiceCode"` - Fanrollupstatus string `json:"FanRollupStatus"` - Idsdmrollupstatus interface{} `json:"IDSDMRollupStatus"` - ID string `json:"Id"` - Intrusionrollupstatus string `json:"IntrusionRollupStatus"` - Isoembranded string `json:"IsOEMBranded"` - Lastsysteminventorytime time.Time `json:"LastSystemInventoryTime"` - Lastupdatetime time.Time `json:"LastUpdateTime"` - Licensingrollupstatus string `json:"LicensingRollupStatus"` - Maxcpusockets int `json:"MaxCPUSockets"` - Maxdimmslots int `json:"MaxDIMMSlots"` - Maxpcieslots int `json:"MaxPCIeSlots"` - Memoryoperationmode string `json:"MemoryOperationMode"` - Name string `json:"Name"` - Nodeid string `json:"NodeID"` - Psrollupstatus string `json:"PSRollupStatus"` - Populateddimmslots int `json:"PopulatedDIMMSlots"` - Populatedpcieslots int `json:"PopulatedPCIeSlots"` - Powercapenabledstate string `json:"PowerCapEnabledState"` - Sdcardrollupstatus interface{} `json:"SDCardRollupStatus"` - Selrollupstatus string `json:"SELRollupStatus"` - Serverallocationwatts interface{} `json:"ServerAllocationWatts"` - Storagerollupstatus string `json:"StorageRollupStatus"` - Sysmemerrormethodology string `json:"SysMemErrorMethodology"` - Sysmemfailoverstate string `json:"SysMemFailOverState"` - Sysmemlocation string `json:"SysMemLocation"` - Sysmemprimarystatus string `json:"SysMemPrimaryStatus"` - Systemgeneration string `json:"SystemGeneration"` - Systemid int `json:"SystemID"` - Systemrevision string `json:"SystemRevision"` - Temprollupstatus string `json:"TempRollupStatus"` - Tempstatisticsrollupstatus string `json:"TempStatisticsRollupStatus"` - UUID string `json:"UUID"` - Voltrollupstatus string `json:"VoltRollupStatus"` - Smbiosguid string `json:"smbiosGUID"` -} diff --git a/pkg/config/config.go b/pkg/config/config.go index 66ff926..01e19e0 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -1,73 +1,41 @@ package config import ( - "io/ioutil" "log" - "net/http" "os" "gopkg.in/yaml.v2" ) var ( - pmConfig *PlatformManagementConfig - pmConfigPath string + configPath string + exporterConfig *Config ) -// Prepare function used for setting the config path (after flags are parsed) -func Prepare(config_path string) { - pmConfig = &PlatformManagementConfig{} - - pmConfigPath = os.Getenv("CONFIG_PATH") - if pmConfigPath == "" { - pmConfigPath = config_path - } - - if _, err := os.Stat(pmConfigPath); os.IsNotExist(err) { +func SetPath(opt string) { + configPath = opt + if _, err := os.Stat(configPath); os.IsNotExist(err) { log.Fatalf("Error loading config file: %v", err) - os.Exit(1) } - - pmConfig.reload() + exporterConfig = &Config{} + exporterConfig.reload() } // reload Utility function for reloading the config file -func (c *PlatformManagementConfig) reload() { - configContent, err := ioutil.ReadFile(pmConfigPath) +func (c *Config) reload() { + configContent, err := os.ReadFile(configPath) if err != nil { log.Fatalf("Unable to read file: %v", err) - os.Exit(1) } err = yaml.Unmarshal(configContent, c) if err != nil { log.Fatalf("Unable to parse config file: %v", err) - os.Exit(1) } log.Println("Config file reloaded") } // GetConfig Returns the current config instance -func GetConfig() *PlatformManagementConfig { - return pmConfig -} - -// GetManagementConfig Shortcut for getting node specific configs (if present) -func GetManagementConfig(host string) *NodeConfig { - for i, _ := range pmConfig.PlatformManagements { - if pmConfig.PlatformManagements[i].Host == host { - return &pmConfig.PlatformManagements[i] - } - } - return nil -} - -// ReloadConfigHandler HTTP handler for /-/reload endpoint (if enabled) -func ReloadConfigHandler(w http.ResponseWriter, req *http.Request) { - if req.Method != "PUT" && req.Method != "POST" { - _, _ = w.Write([]byte("Only POST or PUT requests allowed")) - w.WriteHeader(http.StatusMethodNotAllowed) - } - pmConfig.reload() - _, _ = w.Write([]byte("OK")) +func GetConfig() *Config { + return exporterConfig } diff --git a/pkg/config/format.go b/pkg/config/format.go new file mode 100644 index 0000000..27b50a2 --- /dev/null +++ b/pkg/config/format.go @@ -0,0 +1,11 @@ +package config + +type Config struct { + Redfish struct { + // Username Default username for collecting metrics + Username string `yaml:"username"` + + // Password Default password for collecting metrics + Password string `yaml:"password"` + } `yaml:"redfish"` +} diff --git a/pkg/config/types.go b/pkg/config/types.go deleted file mode 100644 index ecd0bda..0000000 --- a/pkg/config/types.go +++ /dev/null @@ -1,13 +0,0 @@ -package config - -type NodeConfig struct { - Host string `yaml:"host"` - Username string `yaml:"username"` - Password string `yaml:"password"` - Type string `yaml:"type"` - Labels map[string]string `yaml:"labels"` -} - -type PlatformManagementConfig struct { - PlatformManagements []NodeConfig `yaml:"platform_managements"` -} diff --git a/pkg/drivers/redfish/client.go b/pkg/drivers/redfish/client.go new file mode 100644 index 0000000..cadbebe --- /dev/null +++ b/pkg/drivers/redfish/client.go @@ -0,0 +1,51 @@ +package redfish + +import ( + "fmt" + "github.com/g-portal/redfish_exporter/pkg/drivers/redfish/metrics" + "github.com/prometheus/client_golang/prometheus" + "github.com/stmcginnis/gofish" +) + +type Redfish struct { + client *gofish.APIClient +} + +func (rf *Redfish) Connect(host, username, password string, verifyTLS bool) error { + var err error + + cfg := gofish.ClientConfig{ + Endpoint: host, + Username: username, + Password: password, + Insecure: !verifyTLS, + TLSHandshakeTimeout: 30, + } + // Debug + //cfg.DumpWriter = os.Stdout + + rf.client, err = gofish.Connect(cfg) + + if err != nil { + + return fmt.Errorf("error connecting to redfish: %v", err) + } + + return err +} + +func (rf *Redfish) GetMetrics() (*prometheus.Registry, error) { + m := metrics.NewMetrics(rf.client) + err := m.Collect() + if err != nil { + return nil, fmt.Errorf("error collecting metrics: %v", err) + } + + return m.Registry(), nil +} + +func (rf *Redfish) Disconnect() error { + rf.client.Logout() + + return nil +} diff --git a/pkg/drivers/redfish/metrics/metrics.go b/pkg/drivers/redfish/metrics/metrics.go new file mode 100644 index 0000000..7e7ccc7 --- /dev/null +++ b/pkg/drivers/redfish/metrics/metrics.go @@ -0,0 +1,64 @@ +package metrics + +import ( + "fmt" + "github.com/g-portal/redfish_exporter/pkg/metric/base" + "github.com/stmcginnis/gofish" + "github.com/stmcginnis/gofish/common" + "github.com/stmcginnis/gofish/redfish" +) + +type Metrics struct { + base.Metrics + + api *gofish.APIClient +} + +func NewMetrics(gofish *gofish.APIClient) *Metrics { + return &Metrics{ + api: gofish, + } +} + +func (m *Metrics) Collect() error { + service := m.api.GetService() + + systems, err := service.Systems() + if err != nil { + return fmt.Errorf("error getting systems: %v", err) + } + + for _, system := range systems { + m.With(base.WithRedfishHealthMetric(convertHealthStatus(system.Status.Health), map[string]string{ + "system_id": system.ODataID, + }), base.WithRedfishPowerStateMetric(convertPowerState(system.PowerState), map[string]string{ + "system_id": system.ODataID, + })) + } + + return nil +} + +func convertHealthStatus(status common.Health) base.RedfishHealthStatus { + switch status { + case common.OKHealth: + return base.RedfishHealthOK + case common.WarningHealth: + return base.RedfishHealthWarning + case common.CriticalHealth: + return base.RedfishHealthCritical + default: + return base.RedfishHealthWarning + } +} + +func convertPowerState(state redfish.PowerState) base.RedfishPowerStateType { + switch state { + case redfish.OnPowerState, redfish.PoweringOnPowerState: + return base.RedfishPowerStateON + case redfish.OffPowerState, redfish.PoweringOffPowerState: + return base.RedfishPowerStateOFF + default: + return base.RedfishPowerStateON + } +} diff --git a/pkg/metric/base/metric.go b/pkg/metric/base/metric.go new file mode 100644 index 0000000..ac6bc4e --- /dev/null +++ b/pkg/metric/base/metric.go @@ -0,0 +1,13 @@ +package base + +import ( + "github.com/prometheus/client_golang/prometheus" +) + +type MetricCollection map[ID]Metric + +type Metric interface { + GetID() ID + prometheus.Collector + prometheus.Metric +} diff --git a/pkg/metric/base/metric_gauges.go b/pkg/metric/base/metric_gauges.go new file mode 100644 index 0000000..baa7e53 --- /dev/null +++ b/pkg/metric/base/metric_gauges.go @@ -0,0 +1,13 @@ +package base + +import "github.com/prometheus/client_golang/prometheus" + +type GaugeMetric struct { + ID ID + + prometheus.Gauge +} + +func (g *GaugeMetric) GetID() ID { + return g.ID +} diff --git a/pkg/metric/base/metric_power_state.go b/pkg/metric/base/metric_power_state.go new file mode 100644 index 0000000..0474142 --- /dev/null +++ b/pkg/metric/base/metric_power_state.go @@ -0,0 +1,32 @@ +package base + +import ( + "github.com/prometheus/client_golang/prometheus" +) + +const ( + RedfishPowerState ID = "redfish_power_state" +) + +var RedfishPowerStateLabels = []string{"system_id"} + +type RedfishPowerStateType float64 + +const ( + RedfishPowerStateON RedfishPowerStateType = 1 + RedfishPowerStateOFF RedfishPowerStateType = 2 +) + +func WithRedfishPowerStateMetric(state RedfishPowerStateType, labels prometheus.Labels) Metric { + m := &GaugeMetric{ + ID: RedfishPowerState, + Gauge: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Name: string(RedfishPowerState), + Help: "Indicates the power state of the system. 1 = ON, 2 = OFF.", + }, RedfishPowerStateLabels).With(labels), + } + + m.Set(float64(state)) + + return m +} diff --git a/pkg/metric/base/metric_up.go b/pkg/metric/base/metric_up.go new file mode 100644 index 0000000..08dcfbf --- /dev/null +++ b/pkg/metric/base/metric_up.go @@ -0,0 +1,33 @@ +package base + +import ( + "github.com/prometheus/client_golang/prometheus" +) + +const ( + RedfishHealth ID = "redfish_health" +) + +var RedfishHealthLabels = []string{"system_id"} + +type RedfishHealthStatus float64 + +const ( + RedfishHealthOK RedfishHealthStatus = 1 + RedfishHealthWarning RedfishHealthStatus = 2 + RedfishHealthCritical RedfishHealthStatus = 3 +) + +func WithRedfishHealthMetric(health RedfishHealthStatus, labels prometheus.Labels) Metric { + m := &GaugeMetric{ + ID: RedfishHealth, + Gauge: prometheus.NewGaugeVec(prometheus.GaugeOpts{ + Name: string(RedfishHealth), + Help: "Indicates the health status of the system. 1 = OK, 2 = Warning, 3 = Critical.", + }, RedfishHealthLabels).With(labels), + } + + m.Set(float64(health)) + + return m +} diff --git a/pkg/metric/base/metrics.go b/pkg/metric/base/metrics.go new file mode 100644 index 0000000..bfe6ba4 --- /dev/null +++ b/pkg/metric/base/metrics.go @@ -0,0 +1,35 @@ +package base + +import ( + "fmt" + "github.com/prometheus/client_golang/prometheus" +) + +type ID string + +type Metrics struct { + metrics MetricCollection +} + +func (b *Metrics) With(metrics ...Metric) { + if b.metrics == nil { + b.metrics = make(MetricCollection) + } + + for _, metric := range metrics { + b.metrics[metric.GetID()] = metric + } +} + +func (b *Metrics) Registry() *prometheus.Registry { + registry := prometheus.NewRegistry() + for id, collector := range b.metrics { + + fmt.Printf("Registering metric %s\n", id) + + // debug log? + registry.MustRegister(collector) + } + + return registry +} diff --git a/pkg/metric/handler.go b/pkg/metric/handler.go new file mode 100644 index 0000000..7235bbb --- /dev/null +++ b/pkg/metric/handler.go @@ -0,0 +1,64 @@ +package metric + +import ( + "github.com/g-portal/redfish_exporter/pkg/api" + "github.com/g-portal/redfish_exporter/pkg/config" + "github.com/gin-gonic/gin" + "github.com/prometheus/client_golang/prometheus/promhttp" + "log" + "net/http" +) + +func Handle(c *gin.Context) { + params := extractCollectorParams(c.Request) + + client, err := api.NewClient(params.Host, params.Username, params.Password, false) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + log.Println("finished with handling") + defer func() { + err := client.Disconnect() + if err != nil { + log.Printf("error disconnecting: %v", err) + } + }() + + // Get metrics from the client. + registry, err := client.GetMetrics() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + // Delegate http serving to Prometheus client library, which will call collector.Collect. + promhttp.HandlerFor(registry, promhttp.HandlerOpts{}).ServeHTTP(c.Writer, c.Request) +} + +type collectorParams struct { + Username string + Password string + Host string +} + +func extractCollectorParams(r *http.Request) collectorParams { + cfg := config.GetConfig() + params := collectorParams{} + if host := r.URL.Query().Get("host"); host != "" { + params.Host = host + } + if username := r.URL.Query().Get("username"); username != "" { + params.Username = username + } else { + params.Username = cfg.Redfish.Username + } + if password := r.URL.Query().Get("password"); password != "" { + params.Password = password + } else { + params.Password = cfg.Redfish.Password + } + + return params +} diff --git a/pkg/metric/metric.go b/pkg/metric/metric.go deleted file mode 100644 index 4c45213..0000000 --- a/pkg/metric/metric.go +++ /dev/null @@ -1,117 +0,0 @@ -package metric - -import ( - "fmt" - "log" - "net/http" - "strings" - "sync" - "time" - - "github.com/GPORTALcloud/pm_exporter/pkg/config" -) - -const ( - PMPlatformManagementUp = "pm_platform_management_up" - PMPowerSupplyHealth = "pm_power_supply_health" - PMBatteryHealth = "pm_battery_health" - PMCpuHealth = "pm_cpu_health" - PMFanHealth = "pm_fan_health" - PMStorageHealth = "pm_storage_health" - PMTemperatureHealth = "pm_temperature_health" - PMIntrusionHealth = "pm_intrusion_health" - PMLicenceHealth = "pm_license_health" - PMMemoryHealth = "pm_memory_health" - PMOverallHealth = "pm_overall_health" -) - -var metricStore = Storage{ - Mutex: &sync.Mutex{}, - persistDuration: 60, - Metrics: map[string]Metric{}, -} - -func init() { - RegisterMetric(PMPlatformManagementUp, "gauge", "Platform Management reachable") - RegisterMetric(PMPowerSupplyHealth, "gauge", "Power Supply Health status") - RegisterMetric(PMBatteryHealth, "gauge", "Battery Health status") - RegisterMetric(PMCpuHealth, "gauge", "CPU Health status") - RegisterMetric(PMFanHealth, "gauge", "Fan Health status") - RegisterMetric(PMStorageHealth, "gauge", "Storage Health status") - RegisterMetric(PMTemperatureHealth, "gauge", "Temperature Health status") - RegisterMetric(PMIntrusionHealth, "gauge", "Intrusion Health status") - RegisterMetric(PMLicenceHealth, "gauge", "License Health status") - RegisterMetric(PMMemoryHealth, "gauge", "Memory Health status") - RegisterMetric(PMOverallHealth, "gauge", "Overall Health status") -} - -// SetPersistDuration overwrite duration the metrics are getting persisted within the metricStore -func SetPersistDuration(duration time.Duration) { - metricStore.persistDuration = duration -} - -// RegisterMetric used for adding new metric types to the metricStore -func RegisterMetric(name string, t string, help string) { - metricStore.Mutex.Lock() - defer metricStore.Mutex.Unlock() - if _, ok := metricStore.Metrics[name]; ok { - return - } - metricStore.Metrics[name] = Metric{Name: name, Type: t, Help: help, Vars: map[string]MetricVar{}} -} - -// UpdateMetric adds new metric values to the metricStore -func UpdateMetric(name string, host string, val interface{}) { - metricStore.Mutex.Lock() - defer metricStore.Mutex.Unlock() - metricStore.Metrics[name].Vars[host] = MetricVar{ - Value: val, - ExpiresAt: time.Now().Add(metricStore.persistDuration).Unix(), - } -} - -// UnsetMetric removes specific host metrics from the metricStore -func UnsetMetric(name string, host string) { - delete(metricStore.Metrics[name].Vars, host) -} - -// MetricHttpHandler HTTP handler for returning the metrics formatted for prometheus -func MetricHttpHandler(w http.ResponseWriter, req *http.Request) { - _, err := w.Write([]byte(dump())) - if err != nil { - log.Printf("Error writing to ResponseWriter: %v\n", err) - } -} - -// dump Utility function used by the HTTP handler for returning a proper prometheus format -func dump() string { - metricStore.Mutex.Lock() - defer metricStore.Mutex.Unlock() - responseString := "" - for i, m := range metricStore.Metrics { - responseString += fmt.Sprintf("# HELP %v %v\n", m.Name, m.Help) - responseString += fmt.Sprintf("# TYPE %v %v\n", m.Name, m.Type) - for h, d := range metricStore.Metrics[i].Vars { - nodeConfig := config.GetManagementConfig(h) - if d.ExpiresAt <= time.Now().Unix() { - UnsetMetric(m.Name, h) - continue - } - labels := map[string]string{} - if nodeConfig == nil { - UnsetMetric(m.Name, h) - continue - } else { - labels = nodeConfig.Labels - } - labels["host"] = h - labelGroups := []string{} - for key, value := range labels { - labelGroups = append(labelGroups, fmt.Sprintf("%v=\"%v\"", key, value)) - } - labelString := fmt.Sprintf("{%v}", strings.Join(labelGroups, ",")) - responseString += fmt.Sprintf("%v%v %v\n", m.Name, string(labelString), d.Value) - } - } - return responseString -} diff --git a/pkg/metric/types.go b/pkg/metric/types.go deleted file mode 100644 index a511d20..0000000 --- a/pkg/metric/types.go +++ /dev/null @@ -1,24 +0,0 @@ -package metric - -import ( - "sync" - "time" -) - -type Storage struct { - Mutex *sync.Mutex - persistDuration time.Duration - Metrics map[string]Metric -} - -type Metric struct { - Name string - Type string - Help string - Vars map[string]MetricVar -} - -type MetricVar struct { - Value interface{} - ExpiresAt int64 -} diff --git a/pkg/updater/updater.go b/pkg/updater/updater.go deleted file mode 100644 index e4cba00..0000000 --- a/pkg/updater/updater.go +++ /dev/null @@ -1,52 +0,0 @@ -package updater - -import ( - "log" - "time" - - "github.com/GPORTALcloud/pm_exporter/pkg/api" - "github.com/GPORTALcloud/pm_exporter/pkg/config" -) - -var clients = make(chan api.Client) - -// worker Function used for starting a worker pool -func worker(clients <-chan api.Client) { - for c := range clients { - log.Printf("Fetching metrics from %s", c.GetHost()) - err := c.FetchInventoryMetrics() - if err != nil { - log.Printf("Error updating %s: %v", c.GetHost(), err) - } - } -} - -// Run Start the worker pool and add api clients to the chan every $refreshInterval -func Run(workerCount int, refreshInterval time.Duration) { - for w := 0; w < workerCount; w++ { - go worker(clients) - } - for { - cfg := config.GetConfig() - for i, _ := range cfg.PlatformManagements { - switch pm := cfg.PlatformManagements[i].Type; pm { - case "IDRAC": - fallthrough - case "IDRAC9": - fallthrough - case "IDRAC8": - client := api.NewRedfishAPI(cfg.PlatformManagements[i].Host) - client.SetUser(cfg.PlatformManagements[i].Username, cfg.PlatformManagements[i].Password) - clients <- client - case "ILO5": - client := api.NewHpeApi(cfg.PlatformManagements[i].Host) - client.SetUser(cfg.PlatformManagements[i].Username, cfg.PlatformManagements[i].Password) - clients <- client - continue - default: - log.Printf("Error fetching inventory for %v - type unknown (%v)\n", cfg.PlatformManagements[i].Host, cfg.PlatformManagements[i].Type) - } - } - time.Sleep(refreshInterval) - } -}