Datasources: add support for POST HTTP verb for InfluxDB (#16690)

A new parameter `queryMode` is added to the InfluxDB datasource to provide a way to use POST instead of GET when querying the database. This prevents to get any error when querying the database with a heavy request.
Default configuration is kept to GET for backward compatibility. Tests and documentation have been added for this new behaviour.
This commit is contained in:
Stephen SORRIAUX
2019-05-02 15:30:37 +02:00
committed by Andrej Ocenas
parent 2596ce5076
commit 3866839b19
7 changed files with 206 additions and 8 deletions

View File

@@ -3,10 +3,12 @@ package influxdb
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"path"
"strings"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/models"
@@ -33,6 +35,8 @@ var (
glog log.Logger
)
var ErrInvalidHttpMode error = errors.New("'httpMode' should be either 'GET' or 'POST'")
func init() {
glog = log.New("tsdb.influxdb")
tsdb.RegisterTsdbQueryEndpoint("influxdb", NewInfluxDBExecutor)
@@ -108,21 +112,42 @@ func (e *InfluxDBExecutor) getQuery(dsInfo *models.DataSource, queries []*tsdb.Q
}
func (e *InfluxDBExecutor) createRequest(dsInfo *models.DataSource, query string) (*http.Request, error) {
u, _ := url.Parse(dsInfo.Url)
u.Path = path.Join(u.Path, "query")
httpMode := dsInfo.JsonData.Get("httpMode").MustString("GET")
req, err := func() (*http.Request, error) {
switch httpMode {
case "GET":
return http.NewRequest(http.MethodGet, u.String(), nil)
case "POST":
bodyValues := url.Values{}
bodyValues.Add("q", query)
body := bodyValues.Encode()
return http.NewRequest(http.MethodPost, u.String(), strings.NewReader(body))
default:
return nil, ErrInvalidHttpMode
}
}()
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", "Grafana")
params := req.URL.Query()
params.Set("q", query)
params.Set("db", dsInfo.Database)
params.Set("epoch", "s")
req.URL.RawQuery = params.Encode()
req.Header.Set("User-Agent", "Grafana")
if httpMode == "GET" {
params.Set("q", query)
} else if httpMode == "POST" {
req.Header.Set("Content-type", "application/x-www-form-urlencoded")
}
req.URL.RawQuery = params.Encode()
if dsInfo.BasicAuth {
req.SetBasicAuth(dsInfo.BasicAuthUser, dsInfo.DecryptedBasicAuthPassword())

View File

@@ -0,0 +1,77 @@
package influxdb
import (
"io/ioutil"
"net/url"
"testing"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
. "github.com/smartystreets/goconvey/convey"
)
func TestInfluxDB(t *testing.T) {
Convey("InfluxDB", t, func() {
datasource := &models.DataSource{
Url: "http://awesome-influxdb:1337",
Database: "awesome-db",
JsonData: simplejson.New(),
}
query := "SELECT awesomeness FROM somewhere"
e := &InfluxDBExecutor{
QueryParser: &InfluxdbQueryParser{},
ResponseParser: &ResponseParser{},
}
Convey("createRequest with GET httpMode", func() {
req, _ := e.createRequest(datasource, query)
Convey("as default", func() {
So(req.Method, ShouldEqual, "GET")
})
Convey("has a 'q' GET param that equals to query", func() {
q := req.URL.Query().Get("q")
So(q, ShouldEqual, query)
})
Convey("has an empty body", func() {
So(req.Body, ShouldEqual, nil)
})
})
Convey("createRequest with POST httpMode", func() {
datasource.JsonData.Set("httpMode", "POST")
req, _ := e.createRequest(datasource, query)
Convey("method should be POST", func() {
So(req.Method, ShouldEqual, "POST")
})
Convey("has no 'q' GET param", func() {
q := req.URL.Query().Get("q")
So(q, ShouldEqual, "")
})
Convey("has the request as GET param in body", func() {
body, _ := ioutil.ReadAll(req.Body)
testBodyValues := url.Values{}
testBodyValues.Add("q", query)
testBody := testBodyValues.Encode()
So(string(body[:]), ShouldEqual, testBody)
})
})
Convey("createRequest with PUT httpMode", func() {
datasource.JsonData.Set("httpMode", "PUT")
_, err := e.createRequest(datasource, query)
Convey("should miserably fail", func() {
So(err, ShouldEqual, ErrInvalidHttpMode)
})
})
})
}