2018-09-13 16:56:49 +00:00
|
|
|
package http
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
2019-05-04 00:07:43 +00:00
|
|
|
"encoding/json"
|
2018-09-13 16:56:49 +00:00
|
|
|
"fmt"
|
2019-05-04 00:07:43 +00:00
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
2018-09-13 16:56:49 +00:00
|
|
|
"net/http"
|
|
|
|
"net/http/httptest"
|
2018-09-13 20:26:36 +00:00
|
|
|
"reflect"
|
|
|
|
"regexp"
|
2019-05-04 00:07:43 +00:00
|
|
|
"strings"
|
2018-09-13 16:56:49 +00:00
|
|
|
"testing"
|
2019-07-29 19:47:55 +00:00
|
|
|
"time"
|
2018-09-13 20:26:36 +00:00
|
|
|
|
2019-03-07 15:32:13 +00:00
|
|
|
"github.com/google/go-cmp/cmp"
|
|
|
|
"github.com/influxdata/flux"
|
2018-09-13 20:26:36 +00:00
|
|
|
"github.com/influxdata/flux/csv"
|
|
|
|
"github.com/influxdata/flux/lang"
|
2020-04-03 17:39:20 +00:00
|
|
|
"github.com/influxdata/influxdb/v2"
|
|
|
|
icontext "github.com/influxdata/influxdb/v2/context"
|
|
|
|
"github.com/influxdata/influxdb/v2/http/metric"
|
|
|
|
"github.com/influxdata/influxdb/v2/kit/check"
|
2020-06-26 23:54:09 +00:00
|
|
|
"github.com/influxdata/influxdb/v2/kit/feature"
|
2020-04-03 17:39:20 +00:00
|
|
|
tracetesting "github.com/influxdata/influxdb/v2/kit/tracing/testing"
|
|
|
|
kithttp "github.com/influxdata/influxdb/v2/kit/transport/http"
|
|
|
|
influxmock "github.com/influxdata/influxdb/v2/mock"
|
|
|
|
"github.com/influxdata/influxdb/v2/query"
|
2020-04-06 17:17:47 +00:00
|
|
|
"github.com/influxdata/influxdb/v2/query/fluxlang"
|
2020-04-03 17:39:20 +00:00
|
|
|
"github.com/influxdata/influxdb/v2/query/mock"
|
refactor(kv): delete deprecated kv service code
This includes removal of a lot of kv.Service responsibilities. However,
it does not finish the re-wiring. It removes documents, telegrafs,
notification rules + endpoints, checks, orgs, users, buckets, passwords,
urms, labels and authorizations. There are some oustanding pieces that
are needed to get kv service compiling (dashboard service urm
dependency). Then all the call sites for kv service need updating and
the new implementations of telegraf and notification rules + endpoints
needed installing (along with any necessary migrations).
2020-10-20 13:25:36 +00:00
|
|
|
"github.com/influxdata/influxdb/v2/tenant"
|
2019-05-04 00:07:43 +00:00
|
|
|
"go.uber.org/zap/zaptest"
|
2018-09-13 16:56:49 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestFluxService_Query(t *testing.T) {
|
2019-06-27 21:33:22 +00:00
|
|
|
orgID, err := influxdb.IDFromString("abcdabcdabcdabcd")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2018-09-13 16:56:49 +00:00
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
token string
|
|
|
|
ctx context.Context
|
|
|
|
r *query.ProxyRequest
|
|
|
|
status int
|
2019-03-07 15:32:13 +00:00
|
|
|
want flux.Statistics
|
2018-09-13 16:56:49 +00:00
|
|
|
wantW string
|
|
|
|
wantErr bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "query",
|
|
|
|
ctx: context.Background(),
|
|
|
|
token: "mytoken",
|
|
|
|
r: &query.ProxyRequest{
|
|
|
|
Request: query.Request{
|
2019-06-27 21:33:22 +00:00
|
|
|
OrganizationID: *orgID,
|
2018-09-13 16:56:49 +00:00
|
|
|
Compiler: lang.FluxCompiler{
|
|
|
|
Query: "from()",
|
|
|
|
},
|
|
|
|
},
|
2018-09-13 18:00:27 +00:00
|
|
|
Dialect: csv.DefaultDialect(),
|
2018-09-13 16:56:49 +00:00
|
|
|
},
|
|
|
|
status: http.StatusOK,
|
2019-03-07 15:32:13 +00:00
|
|
|
want: flux.Statistics{},
|
2018-09-13 16:56:49 +00:00
|
|
|
wantW: "howdy\n",
|
|
|
|
},
|
2019-06-27 21:33:22 +00:00
|
|
|
{
|
|
|
|
name: "missing org id",
|
|
|
|
ctx: context.Background(),
|
|
|
|
token: "mytoken",
|
|
|
|
r: &query.ProxyRequest{
|
|
|
|
Request: query.Request{
|
|
|
|
Compiler: lang.FluxCompiler{
|
|
|
|
Query: "from()",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Dialect: csv.DefaultDialect(),
|
|
|
|
},
|
|
|
|
wantErr: true,
|
|
|
|
},
|
2018-09-13 16:56:49 +00:00
|
|
|
{
|
|
|
|
name: "error status",
|
|
|
|
token: "mytoken",
|
|
|
|
ctx: context.Background(),
|
|
|
|
r: &query.ProxyRequest{
|
|
|
|
Request: query.Request{
|
2019-06-27 21:33:22 +00:00
|
|
|
OrganizationID: *orgID,
|
2018-09-13 16:56:49 +00:00
|
|
|
Compiler: lang.FluxCompiler{
|
|
|
|
Query: "from()",
|
|
|
|
},
|
|
|
|
},
|
2018-09-13 18:00:27 +00:00
|
|
|
Dialect: csv.DefaultDialect(),
|
2018-09-13 16:56:49 +00:00
|
|
|
},
|
|
|
|
status: http.StatusUnauthorized,
|
|
|
|
wantErr: true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
2019-06-27 21:33:22 +00:00
|
|
|
if reqID := r.URL.Query().Get(OrgID); reqID == "" {
|
2019-08-06 04:20:26 +00:00
|
|
|
if name := r.URL.Query().Get(Org); name == "" {
|
2019-06-27 21:33:22 +00:00
|
|
|
// Request must have org or orgID.
|
2020-02-03 19:07:43 +00:00
|
|
|
kithttp.ErrorHandler(0).HandleHTTPError(context.TODO(), influxdb.ErrInvalidOrgFilter, w)
|
2019-06-27 21:33:22 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2018-09-13 16:56:49 +00:00
|
|
|
w.WriteHeader(tt.status)
|
2019-06-27 21:33:22 +00:00
|
|
|
_, _ = fmt.Fprintln(w, "howdy")
|
2018-09-13 16:56:49 +00:00
|
|
|
}))
|
|
|
|
defer ts.Close()
|
|
|
|
s := &FluxService{
|
2018-10-04 19:11:45 +00:00
|
|
|
Addr: ts.URL,
|
2018-09-13 16:56:49 +00:00
|
|
|
Token: tt.token,
|
|
|
|
}
|
|
|
|
|
|
|
|
w := &bytes.Buffer{}
|
|
|
|
got, err := s.Query(tt.ctx, w, tt.r)
|
|
|
|
if (err != nil) != tt.wantErr {
|
|
|
|
t.Errorf("FluxService.Query() error = %v, wantErr %v", err, tt.wantErr)
|
|
|
|
return
|
|
|
|
}
|
2019-03-07 15:32:13 +00:00
|
|
|
if diff := cmp.Diff(tt.want, got); diff != "" {
|
|
|
|
t.Errorf("FluxService.Query() = -want/+got: %v", diff)
|
2018-09-13 16:56:49 +00:00
|
|
|
}
|
|
|
|
if gotW := w.String(); gotW != tt.wantW {
|
|
|
|
t.Errorf("FluxService.Query() = %v, want %v", gotW, tt.wantW)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2018-09-13 20:26:36 +00:00
|
|
|
|
|
|
|
func TestFluxQueryService_Query(t *testing.T) {
|
2019-08-22 02:08:51 +00:00
|
|
|
var orgID influxdb.ID
|
2018-09-26 16:36:39 +00:00
|
|
|
orgID.DecodeFromString("aaaaaaaaaaaaaaaa")
|
2018-09-13 20:26:36 +00:00
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
token string
|
|
|
|
ctx context.Context
|
|
|
|
r *query.Request
|
|
|
|
csv string
|
|
|
|
status int
|
|
|
|
want string
|
|
|
|
wantErr bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "error status",
|
|
|
|
token: "mytoken",
|
|
|
|
ctx: context.Background(),
|
|
|
|
r: &query.Request{
|
2018-09-26 16:36:39 +00:00
|
|
|
OrganizationID: orgID,
|
2018-09-13 20:26:36 +00:00
|
|
|
Compiler: lang.FluxCompiler{
|
|
|
|
Query: "from()",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
status: http.StatusUnauthorized,
|
|
|
|
wantErr: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "returns csv",
|
|
|
|
token: "mytoken",
|
|
|
|
ctx: context.Background(),
|
|
|
|
r: &query.Request{
|
2018-09-26 16:36:39 +00:00
|
|
|
OrganizationID: orgID,
|
2018-09-13 20:26:36 +00:00
|
|
|
Compiler: lang.FluxCompiler{
|
|
|
|
Query: "from()",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
status: http.StatusOK,
|
|
|
|
csv: `#datatype,string,long,dateTime:RFC3339,double,long,string,boolean,string,string,string
|
|
|
|
#group,false,false,false,false,false,false,false,true,true,true
|
2019-03-05 23:43:18 +00:00
|
|
|
#default,_result,,,,,,,,,
|
2018-09-13 20:26:36 +00:00
|
|
|
,result,table,_time,usage_user,test,mystr,this,cpu,host,_measurement
|
|
|
|
,,0,2018-08-29T13:08:47Z,10.2,10,yay,true,cpu-total,a,cpui
|
|
|
|
`,
|
2019-03-05 23:43:18 +00:00
|
|
|
want: toCRLF(`,_result,0,2018-08-29T13:08:47Z,10.2,10,yay,true,cpu-total,a,cpui
|
2018-09-13 20:26:36 +00:00
|
|
|
|
|
|
|
`),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
2018-09-26 16:36:39 +00:00
|
|
|
var orgIDStr string
|
2018-09-13 20:26:36 +00:00
|
|
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
2018-09-26 16:36:39 +00:00
|
|
|
orgIDStr = r.URL.Query().Get(OrgID)
|
2018-09-13 20:26:36 +00:00
|
|
|
w.WriteHeader(tt.status)
|
|
|
|
fmt.Fprintln(w, tt.csv)
|
|
|
|
}))
|
|
|
|
s := &FluxQueryService{
|
2018-10-04 19:11:45 +00:00
|
|
|
Addr: ts.URL,
|
2018-09-13 20:26:36 +00:00
|
|
|
Token: tt.token,
|
|
|
|
}
|
|
|
|
res, err := s.Query(tt.ctx, tt.r)
|
|
|
|
if (err != nil) != tt.wantErr {
|
|
|
|
t.Errorf("FluxQueryService.Query() error = %v, wantErr %v", err, tt.wantErr)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if res != nil && res.Err() != nil {
|
|
|
|
t.Errorf("FluxQueryService.Query() result error = %v", res.Err())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if tt.wantErr {
|
|
|
|
return
|
|
|
|
}
|
2018-11-08 20:49:35 +00:00
|
|
|
defer res.Release()
|
2018-09-13 20:26:36 +00:00
|
|
|
|
|
|
|
enc := csv.NewMultiResultEncoder(csv.ResultEncoderConfig{
|
|
|
|
NoHeader: true,
|
|
|
|
Delimiter: ',',
|
|
|
|
})
|
|
|
|
b := bytes.Buffer{}
|
|
|
|
n, err := enc.Encode(&b, res)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("FluxQueryService.Query() encode error = %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if n != int64(len(tt.want)) {
|
|
|
|
t.Errorf("FluxQueryService.Query() encode result = %d, want %d", n, len(tt.want))
|
|
|
|
}
|
2018-09-26 16:36:39 +00:00
|
|
|
if orgIDStr == "" {
|
|
|
|
t.Error("FluxQueryService.Query() encoded orgID is empty")
|
|
|
|
}
|
|
|
|
if got, want := orgIDStr, tt.r.OrganizationID.String(); got != want {
|
|
|
|
t.Errorf("FluxQueryService.Query() encoded orgID = %s, want %s", got, want)
|
|
|
|
}
|
2018-09-13 20:26:36 +00:00
|
|
|
|
|
|
|
got := b.String()
|
|
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
|
|
t.Errorf("FluxQueryService.Query() =\n%s\n%s", got, tt.want)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-04 18:21:53 +00:00
|
|
|
func TestFluxHandler_postFluxAST(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
w *httptest.ResponseRecorder
|
|
|
|
r *http.Request
|
|
|
|
want string
|
|
|
|
status int
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "get ast from()",
|
|
|
|
w: httptest.NewRecorder(),
|
2018-11-02 00:09:01 +00:00
|
|
|
r: httptest.NewRequest("POST", "/api/v2/query/ast", bytes.NewBufferString(`{"query": "from()"}`)),
|
2020-09-17 00:59:15 +00:00
|
|
|
want: `{"ast":{"type":"Package","package":"main","files":[{"type":"File","location":{"start":{"line":1,"column":1},"end":{"line":1,"column":7},"source":"from()"},"metadata":"parser-type=rust","package":null,"imports":null,"body":[{"type":"ExpressionStatement","location":{"start":{"line":1,"column":1},"end":{"line":1,"column":7},"source":"from()"},"expression":{"type":"CallExpression","location":{"start":{"line":1,"column":1},"end":{"line":1,"column":7},"source":"from()"},"callee":{"type":"Identifier","location":{"start":{"line":1,"column":1},"end":{"line":1,"column":5},"source":"from"},"name":"from"}}}]}]}}
|
2018-10-04 18:21:53 +00:00
|
|
|
`,
|
|
|
|
status: http.StatusOK,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "error from bad json",
|
|
|
|
w: httptest.NewRecorder(),
|
2018-11-02 00:09:01 +00:00
|
|
|
r: httptest.NewRequest("POST", "/api/v2/query/ast", bytes.NewBufferString(`error!`)),
|
2019-09-19 15:06:47 +00:00
|
|
|
want: `{"code":"invalid","message":"invalid json: invalid character 'e' looking for beginning of value"}`,
|
2018-10-04 18:21:53 +00:00
|
|
|
status: http.StatusBadRequest,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
2019-06-27 01:33:20 +00:00
|
|
|
h := &FluxHandler{
|
2020-03-09 18:30:43 +00:00
|
|
|
HTTPErrorHandler: kithttp.ErrorHandler(0),
|
|
|
|
FluxLanguageService: fluxlang.DefaultService,
|
2019-06-27 01:33:20 +00:00
|
|
|
}
|
2018-10-04 18:21:53 +00:00
|
|
|
h.postFluxAST(tt.w, tt.r)
|
|
|
|
if got := tt.w.Body.String(); got != tt.want {
|
|
|
|
t.Errorf("http.postFluxAST = got\n%vwant\n%v", got, tt.want)
|
|
|
|
}
|
|
|
|
if got := tt.w.Code; got != tt.status {
|
|
|
|
t.Errorf("http.postFluxAST = got %d\nwant %d", got, tt.status)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-26 03:05:44 +00:00
|
|
|
func TestFluxService_Check(t *testing.T) {
|
|
|
|
ts := httptest.NewServer(http.HandlerFunc(HealthHandler))
|
|
|
|
defer ts.Close()
|
|
|
|
s := &FluxService{
|
|
|
|
Addr: ts.URL,
|
|
|
|
}
|
|
|
|
got := s.Check(context.Background())
|
|
|
|
want := check.Response{
|
|
|
|
Name: "influxdb",
|
|
|
|
Status: "pass",
|
|
|
|
Message: "ready for queries and writes",
|
|
|
|
Checks: check.Responses{},
|
|
|
|
}
|
|
|
|
if !cmp.Equal(want, got) {
|
|
|
|
t.Errorf("unexpected response -want/+got: " + cmp.Diff(want, got))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestFluxQueryService_Check(t *testing.T) {
|
|
|
|
ts := httptest.NewServer(http.HandlerFunc(HealthHandler))
|
|
|
|
defer ts.Close()
|
|
|
|
s := &FluxQueryService{
|
|
|
|
Addr: ts.URL,
|
|
|
|
}
|
|
|
|
got := s.Check(context.Background())
|
|
|
|
want := check.Response{
|
|
|
|
Name: "influxdb",
|
|
|
|
Status: "pass",
|
|
|
|
Message: "ready for queries and writes",
|
|
|
|
Checks: check.Responses{},
|
|
|
|
}
|
|
|
|
if !cmp.Equal(want, got) {
|
|
|
|
t.Errorf("unexpected response -want/+got: " + cmp.Diff(want, got))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-13 20:26:36 +00:00
|
|
|
var crlfPattern = regexp.MustCompile(`\r?\n`)
|
|
|
|
|
|
|
|
func toCRLF(data string) string {
|
|
|
|
return crlfPattern.ReplaceAllString(data, "\r\n")
|
|
|
|
}
|
2019-05-04 00:07:43 +00:00
|
|
|
|
|
|
|
type noopEventRecorder struct{}
|
|
|
|
|
|
|
|
func (noopEventRecorder) Record(context.Context, metric.Event) {}
|
|
|
|
|
|
|
|
var _ metric.EventRecorder = noopEventRecorder{}
|
|
|
|
|
|
|
|
// Certain error cases must be encoded as influxdb.Error so they can be properly decoded clientside.
|
|
|
|
func TestFluxHandler_PostQuery_Errors(t *testing.T) {
|
2019-12-18 15:30:38 +00:00
|
|
|
defer tracetesting.SetupInMemoryTracing(t.Name())()
|
2019-12-06 18:24:43 +00:00
|
|
|
|
refactor(kv): delete deprecated kv service code
This includes removal of a lot of kv.Service responsibilities. However,
it does not finish the re-wiring. It removes documents, telegrafs,
notification rules + endpoints, checks, orgs, users, buckets, passwords,
urms, labels and authorizations. There are some oustanding pieces that
are needed to get kv service compiling (dashboard service urm
dependency). Then all the call sites for kv service need updating and
the new implementations of telegraf and notification rules + endpoints
needed installing (along with any necessary migrations).
2020-10-20 13:25:36 +00:00
|
|
|
store := NewTestInmemStore(t)
|
|
|
|
orgSVC := tenant.NewService(tenant.NewStore(store))
|
2019-05-04 00:07:43 +00:00
|
|
|
b := &FluxBackend{
|
2020-02-03 19:07:43 +00:00
|
|
|
HTTPErrorHandler: kithttp.ErrorHandler(0),
|
2019-12-04 23:10:23 +00:00
|
|
|
log: zaptest.NewLogger(t),
|
2019-05-04 00:07:43 +00:00
|
|
|
QueryEventRecorder: noopEventRecorder{},
|
2019-12-28 00:58:57 +00:00
|
|
|
OrganizationService: orgSVC,
|
2019-05-04 00:07:43 +00:00
|
|
|
ProxyQueryService: &mock.ProxyQueryService{
|
|
|
|
QueryF: func(ctx context.Context, w io.Writer, req *query.ProxyRequest) (flux.Statistics, error) {
|
2019-07-18 16:43:15 +00:00
|
|
|
return flux.Statistics{}, &influxdb.Error{
|
|
|
|
Code: influxdb.EInvalid,
|
2019-07-15 17:50:53 +00:00
|
|
|
Msg: "some query error",
|
|
|
|
}
|
2019-05-04 00:07:43 +00:00
|
|
|
},
|
|
|
|
},
|
2020-03-05 16:32:17 +00:00
|
|
|
FluxLanguageService: fluxlang.DefaultService,
|
2020-06-26 23:54:09 +00:00
|
|
|
Flagger: feature.DefaultFlagger(),
|
2019-05-04 00:07:43 +00:00
|
|
|
}
|
2019-12-04 23:10:23 +00:00
|
|
|
h := NewFluxHandler(zaptest.NewLogger(t), b)
|
2019-05-04 00:07:43 +00:00
|
|
|
|
|
|
|
t.Run("missing authorizer", func(t *testing.T) {
|
|
|
|
ts := httptest.NewServer(h)
|
|
|
|
defer ts.Close()
|
|
|
|
|
|
|
|
resp, err := http.Post(ts.URL+"/api/v2/query", "application/json", strings.NewReader("{}"))
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
2019-12-06 18:24:43 +00:00
|
|
|
if actual := resp.Header.Get("Trace-Id"); actual == "" {
|
|
|
|
t.Error("expected trace ID header")
|
|
|
|
}
|
|
|
|
|
2019-05-04 00:07:43 +00:00
|
|
|
if resp.StatusCode != http.StatusUnauthorized {
|
|
|
|
t.Errorf("expected unauthorized status, got %d", resp.StatusCode)
|
|
|
|
}
|
|
|
|
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var ierr influxdb.Error
|
|
|
|
if err := json.Unmarshal(body, &ierr); err != nil {
|
|
|
|
t.Logf("failed to json unmarshal into influxdb.error: %q", body)
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !strings.Contains(ierr.Msg, "authorization is") {
|
|
|
|
t.Fatalf("expected error to mention authorization, got %s", ierr.Msg)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("authorizer but syntactically invalid JSON request", func(t *testing.T) {
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
req, err := http.NewRequest("POST", "/api/v2/query", strings.NewReader("oops"))
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
authz := &influxdb.Authorization{}
|
|
|
|
req = req.WithContext(icontext.SetAuthorizer(req.Context(), authz))
|
|
|
|
|
|
|
|
h.handleQuery(w, req)
|
|
|
|
|
2019-12-06 18:24:43 +00:00
|
|
|
if actual := w.Header().Get("Trace-Id"); actual == "" {
|
|
|
|
t.Error("expected trace ID header")
|
|
|
|
}
|
|
|
|
|
2019-05-04 00:07:43 +00:00
|
|
|
if w.Code != http.StatusBadRequest {
|
|
|
|
t.Errorf("expected bad request status, got %d", w.Code)
|
|
|
|
}
|
|
|
|
|
|
|
|
body := w.Body.Bytes()
|
|
|
|
var ierr influxdb.Error
|
|
|
|
if err := json.Unmarshal(body, &ierr); err != nil {
|
|
|
|
t.Logf("failed to json unmarshal into influxdb.error: %q", body)
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !strings.Contains(ierr.Msg, "decode request body") {
|
|
|
|
t.Fatalf("expected error to mention decoding, got %s", ierr.Msg)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2019-07-15 17:50:53 +00:00
|
|
|
t.Run("valid request but executing query results in client error", func(t *testing.T) {
|
2019-05-04 00:07:43 +00:00
|
|
|
org := influxdb.Organization{Name: t.Name()}
|
2019-12-28 00:58:57 +00:00
|
|
|
if err := orgSVC.CreateOrganization(context.Background(), &org); err != nil {
|
2019-05-04 00:07:43 +00:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
req, err := http.NewRequest("POST", "/api/v2/query?orgID="+org.ID.String(), bytes.NewReader([]byte("buckets()")))
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
authz := &influxdb.Authorization{}
|
|
|
|
req = req.WithContext(icontext.SetAuthorizer(req.Context(), authz))
|
|
|
|
req.Header.Set("Content-Type", "application/vnd.flux")
|
|
|
|
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
h.handleQuery(w, req)
|
|
|
|
|
2019-12-06 18:24:43 +00:00
|
|
|
if actual := w.Header().Get("Trace-Id"); actual == "" {
|
|
|
|
t.Error("expected trace ID header")
|
|
|
|
}
|
|
|
|
|
2019-07-15 17:50:53 +00:00
|
|
|
if w.Code != http.StatusBadRequest {
|
|
|
|
t.Errorf("expected bad request status, got %d", w.Code)
|
2019-05-04 00:07:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
body := w.Body.Bytes()
|
|
|
|
t.Logf("%s", body)
|
|
|
|
var ierr influxdb.Error
|
|
|
|
if err := json.Unmarshal(body, &ierr); err != nil {
|
|
|
|
t.Logf("failed to json unmarshal into influxdb.error: %q", body)
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2019-07-15 17:50:53 +00:00
|
|
|
if got, want := ierr.Code, influxdb.EInvalid; got != want {
|
|
|
|
t.Fatalf("unexpected error code -want/+got:\n\t- %v\n\t+ %v", want, got)
|
2019-05-04 00:07:43 +00:00
|
|
|
}
|
2019-07-15 17:50:53 +00:00
|
|
|
if ierr.Msg != "some query error" {
|
|
|
|
t.Fatalf("expected error message to mention 'some query error', got %s", ierr.Err.Error())
|
2019-05-04 00:07:43 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2019-07-29 19:47:55 +00:00
|
|
|
|
|
|
|
func TestFluxService_Query_gzip(t *testing.T) {
|
|
|
|
// orgService is just to mock out orgs by returning
|
|
|
|
// the same org every time.
|
|
|
|
orgService := &influxmock.OrganizationService{
|
2019-08-22 02:08:51 +00:00
|
|
|
FindOrganizationByIDF: func(ctx context.Context, id influxdb.ID) (*influxdb.Organization, error) {
|
|
|
|
return &influxdb.Organization{
|
2019-07-29 19:47:55 +00:00
|
|
|
ID: id,
|
|
|
|
Name: id.String(),
|
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
|
2019-08-22 02:08:51 +00:00
|
|
|
FindOrganizationF: func(ctx context.Context, filter influxdb.OrganizationFilter) (*influxdb.Organization, error) {
|
|
|
|
return &influxdb.Organization{
|
|
|
|
ID: influxdb.ID(1),
|
|
|
|
Name: influxdb.ID(1).String(),
|
2019-07-29 19:47:55 +00:00
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// queryService is test setup that returns the same CSV for all queries.
|
|
|
|
queryService := &mock.ProxyQueryService{
|
|
|
|
QueryF: func(ctx context.Context, w io.Writer, req *query.ProxyRequest) (flux.Statistics, error) {
|
|
|
|
_, _ = w.Write([]byte(`#datatype,string,long,dateTime:RFC3339,double,long,string,boolean,string,string,string
|
|
|
|
#group,false,false,false,false,false,false,false,true,true,true
|
|
|
|
#default,_result,,,,,,,,,
|
|
|
|
,result,table,_time,usage_user,test,mystr,this,cpu,host,_measurement
|
|
|
|
,,0,2018-08-29T13:08:47Z,10.2,10,yay,true,cpu-total,a,cpui`))
|
|
|
|
return flux.Statistics{}, nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// authService is yet more test setup that returns an operator auth for any token.
|
|
|
|
authService := &influxmock.AuthorizationService{
|
2019-08-22 02:08:51 +00:00
|
|
|
FindAuthorizationByTokenFn: func(ctx context.Context, token string) (*influxdb.Authorization, error) {
|
|
|
|
return &influxdb.Authorization{
|
|
|
|
ID: influxdb.ID(1),
|
|
|
|
OrgID: influxdb.ID(1),
|
|
|
|
Permissions: influxdb.OperPermissions(),
|
2019-07-29 19:47:55 +00:00
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
fluxBackend := &FluxBackend{
|
2020-02-03 19:07:43 +00:00
|
|
|
HTTPErrorHandler: kithttp.ErrorHandler(0),
|
2019-12-04 23:10:23 +00:00
|
|
|
log: zaptest.NewLogger(t),
|
2019-07-29 19:47:55 +00:00
|
|
|
QueryEventRecorder: noopEventRecorder{},
|
|
|
|
OrganizationService: orgService,
|
|
|
|
ProxyQueryService: queryService,
|
2020-03-05 16:32:17 +00:00
|
|
|
FluxLanguageService: fluxlang.DefaultService,
|
2020-06-26 23:54:09 +00:00
|
|
|
Flagger: feature.DefaultFlagger(),
|
2019-07-29 19:47:55 +00:00
|
|
|
}
|
|
|
|
|
2019-12-04 23:10:23 +00:00
|
|
|
fluxHandler := NewFluxHandler(zaptest.NewLogger(t), fluxBackend)
|
2019-07-29 19:47:55 +00:00
|
|
|
|
|
|
|
// fluxHandling expects authorization to be on the request context.
|
|
|
|
// AuthenticationHandler extracts the token from headers and places
|
|
|
|
// the auth on context.
|
2020-02-03 19:07:43 +00:00
|
|
|
auth := NewAuthenticationHandler(zaptest.NewLogger(t), kithttp.ErrorHandler(0))
|
2019-07-29 19:47:55 +00:00
|
|
|
auth.AuthorizationService = authService
|
|
|
|
auth.Handler = fluxHandler
|
2019-09-18 20:19:51 +00:00
|
|
|
auth.UserService = &influxmock.UserService{
|
2020-06-26 23:54:09 +00:00
|
|
|
FindUserByIDFn: func(ctx context.Context, id influxdb.ID) (*influxdb.User, error) {
|
2019-09-18 20:19:51 +00:00
|
|
|
return &influxdb.User{}, nil
|
|
|
|
},
|
|
|
|
}
|
2019-07-29 19:47:55 +00:00
|
|
|
|
|
|
|
ts := httptest.NewServer(auth)
|
|
|
|
defer ts.Close()
|
|
|
|
|
|
|
|
newFakeRequest := func() *http.Request {
|
|
|
|
req, err := http.NewRequest("POST", ts.URL+"/api/v2/query?orgID=0000000000000001", bytes.NewReader([]byte("buckets()")))
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
req.Header.Set("Content-Type", "application/vnd.flux")
|
|
|
|
SetToken("not important hard coded test response", req)
|
|
|
|
return req
|
|
|
|
}
|
|
|
|
|
|
|
|
// disable any gzip compression
|
|
|
|
client := &http.Client{
|
|
|
|
Transport: &http.Transport{
|
|
|
|
MaxIdleConns: 10,
|
|
|
|
IdleConnTimeout: 30 * time.Second,
|
|
|
|
DisableCompression: true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
req := newFakeRequest()
|
|
|
|
res, err := client.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unable to POST to server: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if res.StatusCode != http.StatusOK {
|
|
|
|
t.Errorf("unexpected status code %s", res.Status)
|
|
|
|
}
|
|
|
|
|
|
|
|
identityBody, _ := ioutil.ReadAll(res.Body)
|
|
|
|
_ = res.Body.Close()
|
|
|
|
|
|
|
|
// now, we try to use gzip
|
|
|
|
req = newFakeRequest()
|
|
|
|
// If we enable compression, we should get the same response.
|
|
|
|
client = &http.Client{
|
|
|
|
Transport: &http.Transport{
|
|
|
|
MaxIdleConns: 10,
|
|
|
|
IdleConnTimeout: 30 * time.Second,
|
|
|
|
DisableCompression: false,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
res, err = client.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unable to POST to server: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
gzippedBody, _ := ioutil.ReadAll(res.Body)
|
|
|
|
_ = res.Body.Close()
|
|
|
|
|
|
|
|
if res.StatusCode != http.StatusOK {
|
|
|
|
t.Errorf("unexpected status code %s", res.Status)
|
|
|
|
}
|
|
|
|
|
|
|
|
if string(identityBody) != string(gzippedBody) {
|
|
|
|
t.Errorf("unexpected difference in identity and compressed bodies:\n%s\n%s", string(identityBody), string(gzippedBody))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func Benchmark_Query_no_gzip(b *testing.B) {
|
|
|
|
benchmarkQuery(b, true)
|
|
|
|
}
|
|
|
|
|
|
|
|
func Benchmark_Query_gzip(b *testing.B) {
|
|
|
|
benchmarkQuery(b, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
func benchmarkQuery(b *testing.B, disableCompression bool) {
|
|
|
|
// orgService is just to mock out orgs by returning
|
|
|
|
// the same org every time.
|
|
|
|
orgService := &influxmock.OrganizationService{
|
2019-08-22 02:08:51 +00:00
|
|
|
FindOrganizationByIDF: func(ctx context.Context, id influxdb.ID) (*influxdb.Organization, error) {
|
|
|
|
return &influxdb.Organization{
|
2019-07-29 19:47:55 +00:00
|
|
|
ID: id,
|
|
|
|
Name: id.String(),
|
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
|
2019-08-22 02:08:51 +00:00
|
|
|
FindOrganizationF: func(ctx context.Context, filter influxdb.OrganizationFilter) (*influxdb.Organization, error) {
|
|
|
|
return &influxdb.Organization{
|
|
|
|
ID: influxdb.ID(1),
|
|
|
|
Name: influxdb.ID(1).String(),
|
2019-07-29 19:47:55 +00:00
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// queryService is test setup that returns the same CSV for all queries.
|
|
|
|
queryService := &mock.ProxyQueryService{
|
|
|
|
QueryF: func(ctx context.Context, w io.Writer, req *query.ProxyRequest) (flux.Statistics, error) {
|
|
|
|
_, _ = w.Write([]byte(`#datatype,string,long,dateTime:RFC3339,double,long,string,boolean,string,string,string
|
|
|
|
#group,false,false,false,false,false,false,false,true,true,true
|
|
|
|
#default,_result,,,,,,,,,
|
|
|
|
,result,table,_time,usage_user,test,mystr,this,cpu,host,_measurement
|
|
|
|
,,0,2018-08-29T13:08:47Z,10.2,10,yay,true,cpu-total,a,cpui`))
|
|
|
|
return flux.Statistics{}, nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// authService is yet more test setup that returns an operator auth for any token.
|
|
|
|
authService := &influxmock.AuthorizationService{
|
2019-08-22 02:08:51 +00:00
|
|
|
FindAuthorizationByTokenFn: func(ctx context.Context, token string) (*influxdb.Authorization, error) {
|
|
|
|
return &influxdb.Authorization{
|
|
|
|
ID: influxdb.ID(1),
|
|
|
|
OrgID: influxdb.ID(1),
|
|
|
|
Permissions: influxdb.OperPermissions(),
|
2019-07-29 19:47:55 +00:00
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
fluxBackend := &FluxBackend{
|
2020-02-03 19:07:43 +00:00
|
|
|
HTTPErrorHandler: kithttp.ErrorHandler(0),
|
2019-12-04 23:10:23 +00:00
|
|
|
log: zaptest.NewLogger(b),
|
2019-07-29 19:47:55 +00:00
|
|
|
QueryEventRecorder: noopEventRecorder{},
|
|
|
|
OrganizationService: orgService,
|
|
|
|
ProxyQueryService: queryService,
|
2020-03-05 16:32:17 +00:00
|
|
|
FluxLanguageService: fluxlang.DefaultService,
|
2020-06-26 23:54:09 +00:00
|
|
|
Flagger: feature.DefaultFlagger(),
|
2019-07-29 19:47:55 +00:00
|
|
|
}
|
|
|
|
|
2019-12-04 23:10:23 +00:00
|
|
|
fluxHandler := NewFluxHandler(zaptest.NewLogger(b), fluxBackend)
|
2019-07-29 19:47:55 +00:00
|
|
|
|
|
|
|
// fluxHandling expects authorization to be on the request context.
|
|
|
|
// AuthenticationHandler extracts the token from headers and places
|
|
|
|
// the auth on context.
|
2020-02-03 19:07:43 +00:00
|
|
|
auth := NewAuthenticationHandler(zaptest.NewLogger(b), kithttp.ErrorHandler(0))
|
2019-07-29 19:47:55 +00:00
|
|
|
auth.AuthorizationService = authService
|
|
|
|
auth.Handler = fluxHandler
|
|
|
|
|
|
|
|
ts := httptest.NewServer(auth)
|
|
|
|
defer ts.Close()
|
|
|
|
|
|
|
|
newFakeRequest := func() *http.Request {
|
|
|
|
req, err := http.NewRequest("POST", ts.URL+"/api/v2/query?orgID=0000000000000001", bytes.NewReader([]byte("buckets()")))
|
|
|
|
if err != nil {
|
|
|
|
b.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
req.Header.Set("Content-Type", "application/vnd.flux")
|
|
|
|
SetToken("not important hard coded test response", req)
|
|
|
|
return req
|
|
|
|
}
|
|
|
|
|
|
|
|
client := &http.Client{
|
|
|
|
Transport: &http.Transport{
|
|
|
|
MaxIdleConns: 10,
|
|
|
|
IdleConnTimeout: 30 * time.Second,
|
|
|
|
DisableCompression: disableCompression,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
b.ResetTimer()
|
|
|
|
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
req := newFakeRequest()
|
|
|
|
|
|
|
|
res, err := client.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
b.Fatalf("unable to POST to server: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if res.StatusCode != http.StatusOK {
|
|
|
|
b.Errorf("unexpected status code %s", res.Status)
|
|
|
|
}
|
|
|
|
|
|
|
|
_, _ = ioutil.ReadAll(res.Body)
|
|
|
|
_ = res.Body.Close()
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|