Skip to content
Snippets Groups Projects
Select Git revision
  • 1cefeb06002ab64df57896c861b11ab482df2c2d
  • master default protected
2 results

main.go

Blame
  • 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))
    }