2018-09-04 21:08:00 +00:00
|
|
|
package http
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"net/http"
|
|
|
|
|
2018-09-13 18:21:19 +00:00
|
|
|
"github.com/influxdata/flux"
|
|
|
|
"github.com/influxdata/flux/csv"
|
2018-09-04 21:08:00 +00:00
|
|
|
"github.com/influxdata/platform"
|
|
|
|
pcontext "github.com/influxdata/platform/context"
|
|
|
|
"github.com/influxdata/platform/kit/errors"
|
2018-09-11 22:56:51 +00:00
|
|
|
"github.com/influxdata/platform/query"
|
2018-09-04 21:08:00 +00:00
|
|
|
"github.com/julienschmidt/httprouter"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
fluxPath = "/v2/query"
|
|
|
|
)
|
|
|
|
|
|
|
|
// FluxHandler implements handling flux queries.
|
|
|
|
type FluxHandler struct {
|
|
|
|
*httprouter.Router
|
|
|
|
|
|
|
|
Logger *zap.Logger
|
|
|
|
|
|
|
|
AuthorizationService platform.AuthorizationService
|
|
|
|
OrganizationService platform.OrganizationService
|
2018-09-11 22:56:51 +00:00
|
|
|
ProxyQueryService query.ProxyQueryService
|
2018-09-04 21:08:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewFluxHandler returns a new handler at /v2/query for flux queries.
|
|
|
|
func NewFluxHandler() *FluxHandler {
|
|
|
|
h := &FluxHandler{
|
|
|
|
Router: httprouter.New(),
|
|
|
|
Logger: zap.NewNop(),
|
|
|
|
}
|
|
|
|
|
2018-09-20 15:43:43 +00:00
|
|
|
h.HandlerFunc("POST", fluxPath, h.handlePostQuery)
|
2018-09-04 21:08:00 +00:00
|
|
|
return h
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *FluxHandler) handlePostQuery(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := r.Context()
|
|
|
|
tok, err := pcontext.GetToken(ctx)
|
|
|
|
if err != nil {
|
|
|
|
EncodeError(ctx, err, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
auth, err := h.AuthorizationService.FindAuthorizationByToken(ctx, tok)
|
|
|
|
if err != nil {
|
|
|
|
EncodeError(ctx, errors.Wrap(err, "invalid token", errors.InvalidData), w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if !platform.IsActive(auth) {
|
|
|
|
EncodeError(ctx, errors.Forbiddenf("insufficient permissions for write"), w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-09-13 15:39:08 +00:00
|
|
|
req, err := decodeProxyQueryRequest(ctx, r, auth, h.OrganizationService)
|
2018-09-04 21:08:00 +00:00
|
|
|
if err != nil {
|
|
|
|
EncodeError(ctx, err, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
hd, ok := req.Dialect.(HTTPDialect)
|
|
|
|
if !ok {
|
|
|
|
EncodeError(ctx, fmt.Errorf("unsupported dialect over HTTP %T", req.Dialect), w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
hd.SetHeaders(w)
|
|
|
|
|
|
|
|
n, err := h.ProxyQueryService.Query(ctx, w, req)
|
|
|
|
if err != nil {
|
|
|
|
if n == 0 {
|
|
|
|
// Only record the error headers IFF nothing has been written to w.
|
|
|
|
EncodeError(ctx, err, w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
h.Logger.Info("Error writing response to client",
|
|
|
|
zap.String("handler", "flux"),
|
|
|
|
zap.Error(err),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// PrometheusCollectors satisifies the prom.PrometheusCollector interface.
|
|
|
|
func (h *FluxHandler) PrometheusCollectors() []prometheus.Collector {
|
|
|
|
// TODO: gather and return relevant metrics.
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-09-13 15:39:08 +00:00
|
|
|
var _ query.ProxyQueryService = (*FluxService)(nil)
|
|
|
|
|
2018-09-04 21:08:00 +00:00
|
|
|
// FluxService connects to Influx via HTTP using tokens to run queries.
|
|
|
|
type FluxService struct {
|
|
|
|
URL string
|
|
|
|
Token string
|
|
|
|
InsecureSkipVerify bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// Query runs a flux query against a influx server and sends the results to the io.Writer.
|
2018-09-13 15:39:08 +00:00
|
|
|
// Will use the token from the context over the token within the service struct.
|
|
|
|
func (s *FluxService) Query(ctx context.Context, w io.Writer, r *query.ProxyRequest) (int64, error) {
|
2018-09-04 21:08:00 +00:00
|
|
|
u, err := newURL(s.URL, fluxPath)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
2018-09-13 15:39:08 +00:00
|
|
|
|
2018-09-12 21:10:09 +00:00
|
|
|
qreq, err := QueryRequestFromProxyRequest(r)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
2018-09-04 21:08:00 +00:00
|
|
|
var body bytes.Buffer
|
2018-09-12 21:10:09 +00:00
|
|
|
if err := json.NewEncoder(&body).Encode(qreq); err != nil {
|
2018-09-04 21:08:00 +00:00
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
hreq, err := http.NewRequest("POST", u.String(), &body)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
2018-09-13 15:39:08 +00:00
|
|
|
|
|
|
|
tok, err := pcontext.GetToken(ctx)
|
|
|
|
if err != nil {
|
|
|
|
tok = s.Token
|
|
|
|
}
|
|
|
|
SetToken(tok, hreq)
|
|
|
|
|
2018-09-04 21:08:00 +00:00
|
|
|
hreq.Header.Set("Content-Type", "application/json")
|
2018-09-13 15:39:08 +00:00
|
|
|
hreq.Header.Set("Accept", "text/csv")
|
2018-09-04 21:08:00 +00:00
|
|
|
hreq = hreq.WithContext(ctx)
|
|
|
|
|
|
|
|
hc := newClient(u.Scheme, s.InsecureSkipVerify)
|
|
|
|
resp, err := hc.Do(hreq)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
2018-09-13 18:21:19 +00:00
|
|
|
|
2018-09-04 21:08:00 +00:00
|
|
|
if err := CheckError(resp); err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
return io.Copy(w, resp.Body)
|
|
|
|
}
|
2018-09-13 18:21:19 +00:00
|
|
|
|
|
|
|
var _ query.QueryService = (*FluxQueryService)(nil)
|
|
|
|
|
|
|
|
// FluxQueryService implements query.QueryService by making HTTP requests to the /v2/query API endpoint.
|
|
|
|
type FluxQueryService struct {
|
|
|
|
URL string
|
|
|
|
Token string
|
|
|
|
InsecureSkipVerify bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// Query runs a flux query against a influx server and decodes the result
|
2018-09-13 20:26:36 +00:00
|
|
|
func (s *FluxQueryService) Query(ctx context.Context, r *query.Request) (flux.ResultIterator, error) {
|
2018-09-13 18:21:19 +00:00
|
|
|
u, err := newURL(s.URL, fluxPath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
preq := &query.ProxyRequest{
|
2018-09-13 20:26:36 +00:00
|
|
|
Request: *r,
|
2018-09-13 18:21:19 +00:00
|
|
|
Dialect: csv.DefaultDialect(),
|
|
|
|
}
|
2018-09-12 21:10:09 +00:00
|
|
|
qreq, err := QueryRequestFromProxyRequest(preq)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-09-13 18:21:19 +00:00
|
|
|
var body bytes.Buffer
|
2018-09-12 21:10:09 +00:00
|
|
|
if err := json.NewEncoder(&body).Encode(qreq); err != nil {
|
2018-09-13 18:21:19 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
hreq, err := http.NewRequest("POST", u.String(), &body)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
tok, err := pcontext.GetToken(ctx)
|
|
|
|
if err != nil {
|
|
|
|
tok = s.Token
|
|
|
|
}
|
|
|
|
SetToken(tok, hreq)
|
|
|
|
|
|
|
|
hreq.Header.Set("Content-Type", "application/json")
|
|
|
|
hreq.Header.Set("Accept", "text/csv")
|
|
|
|
hreq = hreq.WithContext(ctx)
|
|
|
|
|
|
|
|
hc := newClient(u.Scheme, s.InsecureSkipVerify)
|
|
|
|
resp, err := hc.Do(hreq)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := CheckError(resp); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
decoder := csv.NewMultiResultDecoder(csv.ResultDecoderConfig{})
|
|
|
|
return decoder.Decode(resp.Body)
|
|
|
|
}
|