Select Git revision
main.go 6.20 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 ApiRequest struct {
Object string `json:"object"`
Method string `json:"method"`
Params []string `json:"params"`
}
type ApiResponse struct {
TGFlash string `json:"tg_flash"`
Result interface{} `json:"result"`
Error ApiError `json:"error"`
}
type ApiError struct {
Message string `json:"message"`
}
type ResultObject struct {
State []string `json:"state"`
Errors []string `json:"errors"`
}
type Exporter struct {
URI string
mutex sync.Mutex
client *http.Client
apiReachable float64
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"),
"Is the Nexenta API reachable",
nil,
nil),
scrapeFailures: prometheus.NewCounter(prometheus.CounterOpts{
Namespace: namespace,
Name: "exporter_scrape_failures_total",
Help: "Number of errors while scraping the 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("%v", err)
}
ch <- prometheus.MustNewConstMetric(e.up, prometheus.GaugeValue, e.apiReachable)
return
}
func (e *Exporter) collect(ch chan<- prometheus.Metric) error {
volumes, err := e.getVolumes(ch)
if err != nil {
err = fmt.Errorf("getVolumes(): %v", err)
}
if len(volumes) > 0 {
for _, volume := range volumes {
err = e.getVolumeStatus(ch, volume)
if err != nil {
err = fmt.Errorf("getVolumeStatus(): %v", err)
}
}
}
return err
}
func (e *Exporter) getVolumes(ch chan<- prometheus.Metric) ([]string, error) {
apiResponse, err := e.queryApi(ch, "volume", "get_names", []string{""}); if err != nil { return nil, err }
apiResult, err := json.Marshal(apiResponse.Result); if err != nil { return nil, err }
var volumes []string
err = json.Unmarshal([]byte(apiResult), &volumes)
return volumes, err
}
func (e *Exporter) getVolumeStatus(ch chan<- prometheus.Metric, volume string) error {
apiResponse, err := e.queryApi(ch, "volume", "get_status", []string{volume}); if err != nil { return err }
apiResult, err := json.Marshal(apiResponse.Result); if err != nil { return err }
var result = new(ResultObject)
err = json.Unmarshal(apiResult, &result); if err != nil { return err }
var volumeOnline float64 = 0
if len(result.State) > 0 {
if result.State[0] == "ONLINE" { volumeOnline = 1 }
}
ch <- prometheus.MustNewConstMetric(e.volumeOnline, prometheus.GaugeValue, volumeOnline, volume)
return err
}
func (e *Exporter) queryApi(ch chan<- prometheus.Metric, object string, method string, params []string) (*ApiResponse, error) {
reqObject := &ApiRequest{
Object: object,
Method: method,
Params: params,
}
e.apiReachable = 0
reqJson, _ := json.Marshal(reqObject)
resp, err := e.client.Post(e.URI, "application/json", bytes.NewBuffer(reqJson))
if err != nil {
return nil, fmt.Errorf("Error scraping Nexenta API: %v", err)
}
e.apiReachable = 1
data, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if resp.StatusCode != 200 {
if err != nil {
data = []byte(err.Error())
}
e.scrapeFailures.Inc()
e.scrapeFailures.Collect(ch)
return nil, fmt.Errorf("Request Error: Status %s %s", resp.Status, data)
}
//log.Infof("response: %s", data)
var apiResponse = new(ApiResponse)
err = json.Unmarshal(data, &apiResponse); if err != nil { return nil, err }
if apiResponse.Result == nil {
e.scrapeFailures.Inc()
e.scrapeFailures.Collect(ch)
return nil, fmt.Errorf("API Error: %v", apiResponse.Error.Message)
}
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))
}