2016-09-14 15:21:41 +00:00
|
|
|
package influx
|
|
|
|
|
|
|
|
import (
|
2016-10-25 15:20:06 +00:00
|
|
|
"context"
|
2016-09-22 00:58:46 +00:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
|
2016-10-20 14:38:23 +00:00
|
|
|
"github.com/influxdata/chronograf"
|
2016-09-14 15:21:41 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Client is a device for retrieving time series data from an InfluxDB instance
|
|
|
|
type Client struct {
|
2016-09-22 00:58:46 +00:00
|
|
|
URL *url.URL
|
2016-09-26 16:12:13 +00:00
|
|
|
|
2016-11-12 23:48:49 +00:00
|
|
|
Logger chronograf.Logger
|
2016-09-14 15:21:41 +00:00
|
|
|
}
|
|
|
|
|
2016-09-16 16:11:28 +00:00
|
|
|
// NewClient initializes an HTTP Client for InfluxDB. UDP, although supported
|
|
|
|
// for querying InfluxDB, is not supported here to remove the need to
|
|
|
|
// explicitly Close the client.
|
2016-10-20 14:38:23 +00:00
|
|
|
func NewClient(host string, lg chronograf.Logger) (*Client, error) {
|
2016-09-26 16:12:13 +00:00
|
|
|
l := lg.WithField("host", host)
|
2016-09-22 00:58:46 +00:00
|
|
|
u, err := url.Parse(host)
|
2016-09-14 15:21:41 +00:00
|
|
|
if err != nil {
|
2016-09-26 16:12:13 +00:00
|
|
|
l.Error("Error initialize influx client: err:", err)
|
2016-09-14 15:21:41 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &Client{
|
2016-11-12 23:48:49 +00:00
|
|
|
URL: u,
|
|
|
|
Logger: l,
|
2016-09-14 15:21:41 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2016-09-22 00:58:46 +00:00
|
|
|
type Response struct {
|
|
|
|
Results json.RawMessage
|
|
|
|
Err string `json:"error,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r Response) MarshalJSON() ([]byte, error) {
|
|
|
|
return r.Results, nil
|
|
|
|
}
|
|
|
|
|
2016-10-20 14:38:23 +00:00
|
|
|
func (c *Client) query(u *url.URL, q chronograf.Query) (chronograf.Response, error) {
|
2016-09-22 00:58:46 +00:00
|
|
|
u.Path = "query"
|
|
|
|
req, err := http.NewRequest("POST", u.String(), nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
|
2016-11-12 23:48:49 +00:00
|
|
|
c.Logger.
|
|
|
|
WithField("component", "proxy").
|
|
|
|
WithField("host", req.Host).
|
|
|
|
WithField("command", q.Command).
|
|
|
|
WithField("db", q.DB).
|
|
|
|
WithField("rp", q.RP).
|
|
|
|
Debug("query")
|
|
|
|
|
2016-09-22 00:58:46 +00:00
|
|
|
params := req.URL.Query()
|
|
|
|
params.Set("q", q.Command)
|
|
|
|
params.Set("db", q.DB)
|
|
|
|
params.Set("rp", q.RP)
|
|
|
|
params.Set("epoch", "ms")
|
|
|
|
req.URL.RawQuery = params.Encode()
|
|
|
|
httpClient := &http.Client{}
|
|
|
|
resp, err := httpClient.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
2016-09-30 15:31:20 +00:00
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
return nil, fmt.Errorf("received status code %d from server", resp.StatusCode)
|
|
|
|
}
|
2016-09-22 00:58:46 +00:00
|
|
|
|
|
|
|
var response Response
|
|
|
|
dec := json.NewDecoder(resp.Body)
|
|
|
|
decErr := dec.Decode(&response)
|
|
|
|
|
|
|
|
// ignore this error if we got an invalid status code
|
|
|
|
if decErr != nil && decErr.Error() == "EOF" && resp.StatusCode != http.StatusOK {
|
|
|
|
decErr = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we got a valid decode error, send that back
|
|
|
|
if decErr != nil {
|
2016-11-12 23:48:49 +00:00
|
|
|
c.Logger.
|
|
|
|
WithField("component", "proxy").
|
|
|
|
WithField("host", req.Host).
|
|
|
|
WithField("command", q.Command).
|
|
|
|
WithField("db", q.DB).
|
|
|
|
WithField("rp", q.RP).
|
2016-09-26 16:12:13 +00:00
|
|
|
WithField("influx_status", resp.StatusCode).
|
|
|
|
Error("Error parsing results from influxdb: err:", decErr)
|
|
|
|
return nil, decErr
|
2016-09-22 00:58:46 +00:00
|
|
|
}
|
2016-09-30 15:31:20 +00:00
|
|
|
|
2016-09-22 00:58:46 +00:00
|
|
|
// If we don't have an error in our json response, and didn't get statusOK
|
|
|
|
// then send back an error
|
|
|
|
if resp.StatusCode != http.StatusOK && response.Err != "" {
|
2016-11-12 23:48:49 +00:00
|
|
|
c.Logger.
|
|
|
|
WithField("component", "proxy").
|
|
|
|
WithField("host", req.Host).
|
|
|
|
WithField("command", q.Command).
|
|
|
|
WithField("db", q.DB).
|
|
|
|
WithField("rp", q.RP).
|
2016-09-26 16:12:13 +00:00
|
|
|
WithField("influx_status", resp.StatusCode).
|
|
|
|
Error("Received non-200 response from influxdb")
|
|
|
|
|
2016-09-22 00:58:46 +00:00
|
|
|
return &response, fmt.Errorf("received status code %d from server",
|
|
|
|
resp.StatusCode)
|
|
|
|
}
|
|
|
|
return &response, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type result struct {
|
2016-10-20 14:38:23 +00:00
|
|
|
Response chronograf.Response
|
2016-09-22 00:58:46 +00:00
|
|
|
Err error
|
|
|
|
}
|
|
|
|
|
2016-09-14 15:21:41 +00:00
|
|
|
// Query issues a request to a configured InfluxDB instance for time series
|
|
|
|
// information specified by query. Queries must be "fully-qualified," and
|
2016-09-15 17:42:56 +00:00
|
|
|
// include both the database and retention policy. In-flight requests can be
|
|
|
|
// cancelled using the provided context.
|
2016-10-20 14:38:23 +00:00
|
|
|
func (c *Client) Query(ctx context.Context, q chronograf.Query) (chronograf.Response, error) {
|
2016-09-22 00:58:46 +00:00
|
|
|
resps := make(chan (result))
|
2016-09-15 17:42:56 +00:00
|
|
|
go func() {
|
2016-09-26 16:12:13 +00:00
|
|
|
resp, err := c.query(c.URL, q)
|
2016-09-22 00:58:46 +00:00
|
|
|
resps <- result{resp, err}
|
2016-09-15 17:42:56 +00:00
|
|
|
}()
|
|
|
|
|
|
|
|
select {
|
|
|
|
case resp := <-resps:
|
2016-09-22 00:58:46 +00:00
|
|
|
return resp.Response, resp.Err
|
2016-09-15 17:42:56 +00:00
|
|
|
case <-ctx.Done():
|
2016-10-20 14:38:23 +00:00
|
|
|
return nil, chronograf.ErrUpstreamTimeout
|
2016-09-15 17:42:56 +00:00
|
|
|
}
|
2016-09-14 15:21:41 +00:00
|
|
|
}
|
2016-09-16 16:11:28 +00:00
|
|
|
|
2016-10-20 14:38:23 +00:00
|
|
|
func (c *Client) Connect(ctx context.Context, src *chronograf.Source) error {
|
2016-10-25 15:20:06 +00:00
|
|
|
u, err := url.Parse(src.URL)
|
2016-09-29 22:07:35 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
u.User = url.UserPassword(src.Username, src.Password)
|
|
|
|
c.URL = u
|
|
|
|
return nil
|
|
|
|
}
|