Select Git revision
main.go 5.96 KiB
package main
import (
"flag"
"net/http"
"github.com/prometheus/client_golang/prometheus/promhttp"
"fmt"
"os"
"github.com/prometheus/common/log"
"github.com/prometheus/common/version"
"github.com/prometheus/client_golang/prometheus"
"sync"
"crypto/tls"
"io/ioutil"
"encoding/json"
"bytes"
)
const (
namespace = "nexenta" // For Prometheus metrics.
apiPath = "/rest/nms"
)
var (
listenAddr = flag.String("listen-address", ":9457", "The address to listen on for HTTP requests.")
metricsEndpoint = flag.String("metrics-endpoint", "/metrics", "Path under which to expose metrics.")
apiHost = flag.String("host", "nexenta", "Nexenta API host.")
apiPort = flag.String("port", "8457", "Nexenta API port.")
apiUser = flag.String("user", "admin", "Nexenta API username.")
apiPass = flag.String("password", "password", "Nexenta API password.")
apiSsl = flag.Bool("ssl", false, "Use SSL for the Nexenta API.")
insecure = flag.Bool("insecure", false, "Ignore server certificate if using https.")
showVersion = flag.Bool("version", false, "Print version information.")
)
type ApiResponse struct {
TGFlash string `json:"tg_flash"`
Result interface{} `json:"result"`
Error interface{} `json:"error"`
}
type ResultObject struct {
State []string `json:"state"`
Errors []string `json:"errors"`
}
type Exporter struct {
URI string
mutex sync.Mutex
client *http.Client
up *prometheus.Desc
scrapeFailures prometheus.Counter
volumeOnline *prometheus.Desc
}
func NewExporter(uri string) *Exporter {
return &Exporter{
URI: uri,
up: prometheus.NewDesc(
prometheus.BuildFQName(namespace, "", "up"),
"Could the Nexenta API be reached",
nil,
nil),
scrapeFailures: prometheus.NewCounter(prometheus.CounterOpts{
Namespace: namespace,
Name: "exporter_scrape_failures_total",
Help: "Number of errors while scraping Nexenta API.",
}),
volumeOnline: prometheus.NewDesc(
prometheus.BuildFQName(namespace, "volume", "online"),
"Status of volume.",
[]string{"volume"},
nil,
),
client: &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: *insecure},
},
},
}
}
func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
ch <- e.up
e.scrapeFailures.Describe(ch)
ch <- e.volumeOnline
}
func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
e.mutex.Lock() // To protect metrics from concurrent collects.
defer e.mutex.Unlock()
err := e.collect(ch)
if err != nil {
log.Errorf("Collect(): %v", err)
}
return
}
func (e *Exporter) collect(ch chan<- prometheus.Metric) error {
volumes, err := e.getVolumes(ch)
if err != nil {
log.Errorf("getVolumes(): %v", err)
}
if len(volumes) > 0 {
for _, volume := range volumes {
err := e.getVolumeStatus(ch, volume)
if err != nil {
log.Errorf("getVolumeStatus(): %v", err)
}
}
}
if err != nil {
ch <- prometheus.MustNewConstMetric(e.up, prometheus.GaugeValue, 0)
return fmt.Errorf("Error scraping Nexenta API: %v", err)
}
ch <- prometheus.MustNewConstMetric(e.up, prometheus.GaugeValue, 1)
return err
}
func (e *Exporter) getVolumes(ch chan<- prometheus.Metric) ([]string, error) {
var reqJson = []byte(`{"object": "volume", "params": [""], "method": "get_names"}`)
apiResponse, err := e.queryApi(ch, reqJson); if err != nil { return nil, err }
apiResult, err := json.Marshal(apiResponse.Result)
var volumes []string
err = json.Unmarshal([]byte(apiResult), &volumes)
return volumes, err
}
func (e *Exporter) getVolumeStatus(ch chan<- prometheus.Metric, volume string) error {
var reqJson = []byte(`{"object": "volume", "params": ["` + volume + `"], "method": "get_status"}`)
apiResponse, err := e.queryApi(ch, reqJson); if err != nil { return err }
apiResult, err := json.Marshal(apiResponse.Result)
var result= new(ResultObject)
err = json.Unmarshal(apiResult, &result)
var volumeOnline float64 = 0
if len(result.State) > 0 {
if result.State[0] == "ONLINE" { volumeOnline = 1 }
}
ch <- prometheus.MustNewConstMetric(e.volumeOnline, prometheus.GaugeValue, volumeOnline, "data")
return err
}
func (e *Exporter) queryApi(ch chan<- prometheus.Metric, reqJson []byte) (*ApiResponse, error) {
resp, err := e.client.Post(e.URI, "application/json", bytes.NewBuffer(reqJson))
data, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if resp.StatusCode != 200 {
if err != nil {
data = []byte(err.Error())
log.Errorf("Error scraping Nexenta API: %s", err)
e.scrapeFailures.Inc()
e.scrapeFailures.Collect(ch)
}
return nil, fmt.Errorf("Status %s (%d)", resp.Status, resp.StatusCode)
}
//log.Infof("response: %s", data)
var apiResponse = new(ApiResponse)
err_json := json.Unmarshal(data, &apiResponse)
if err_json != nil {
log.Infof("whoops:", err_json)
}
if apiResponse.Result == nil {
e.scrapeFailures.Inc()
e.scrapeFailures.Collect(ch)
return nil, fmt.Errorf("Field 'result' in API response is null")
}
return apiResponse, nil
}
func main() {
flag.Parse()
if *showVersion {
fmt.Fprintln(os.Stdout, version.Print("nexenta_exporter"))
os.Exit(0)
}
exporter := NewExporter("http://" + *apiUser + ":" + *apiPass + "@" + *apiHost + ":" + *apiPort + apiPath)
prometheus.MustRegister(exporter)
prometheus.MustRegister(version.NewCollector("nexenta_exporter"))
// disable Go metrics
prometheus.Unregister(prometheus.NewProcessCollector(os.Getpid(), ""))
prometheus.Unregister(prometheus.NewGoCollector())
log.Infoln("Starting nexenta_exporter", version.Info())
log.Infoln("Build context", version.BuildContext())
log.Infof("Starting Server: %s", *listenAddr)
http.Handle(*metricsEndpoint, promhttp.Handler())
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`<html>
<head><title>Nexenta Exporter</title></head>
<body>
<h1>Nexenta Exporter</h1>
<p><a href='` + *metricsEndpoint + `'>Metrics</a></p>
</body>
</html>`))
})
log.Fatal(http.ListenAndServe(*listenAddr, nil))
}