test(http): add unit tests for /api/v2/write (#15128)
test(http): add unit tests for /api/v2/writepull/15138/head
commit
5b4c0db4a0
|
@ -4,7 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
platform "github.com/influxdata/influxdb"
|
"github.com/influxdata/influxdb"
|
||||||
)
|
)
|
||||||
|
|
||||||
type contextKey string
|
type contextKey string
|
||||||
|
@ -14,17 +14,23 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
// SetAuthorizer sets an authorizer on context.
|
// SetAuthorizer sets an authorizer on context.
|
||||||
func SetAuthorizer(ctx context.Context, a platform.Authorizer) context.Context {
|
func SetAuthorizer(ctx context.Context, a influxdb.Authorizer) context.Context {
|
||||||
return context.WithValue(ctx, authorizerCtxKey, a)
|
return context.WithValue(ctx, authorizerCtxKey, a)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAuthorizer retrieves an authorizer from context.
|
// GetAuthorizer retrieves an authorizer from context.
|
||||||
func GetAuthorizer(ctx context.Context) (platform.Authorizer, error) {
|
func GetAuthorizer(ctx context.Context) (influxdb.Authorizer, error) {
|
||||||
a, ok := ctx.Value(authorizerCtxKey).(platform.Authorizer)
|
a, ok := ctx.Value(authorizerCtxKey).(influxdb.Authorizer)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, &platform.Error{
|
return nil, &influxdb.Error{
|
||||||
Msg: "authorizer not found on context",
|
Msg: "authorizer not found on context",
|
||||||
Code: platform.EInternal,
|
Code: influxdb.EInternal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if a == nil {
|
||||||
|
return nil, &influxdb.Error{
|
||||||
|
Code: influxdb.EInternal,
|
||||||
|
Msg: "unexpected invalid authorizer",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,19 +39,19 @@ func GetAuthorizer(ctx context.Context) (platform.Authorizer, error) {
|
||||||
|
|
||||||
// GetToken retrieves a token from the context; errors if no token.
|
// GetToken retrieves a token from the context; errors if no token.
|
||||||
func GetToken(ctx context.Context) (string, error) {
|
func GetToken(ctx context.Context) (string, error) {
|
||||||
a, ok := ctx.Value(authorizerCtxKey).(platform.Authorizer)
|
a, ok := ctx.Value(authorizerCtxKey).(influxdb.Authorizer)
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", &platform.Error{
|
return "", &influxdb.Error{
|
||||||
Msg: "authorizer not found on context",
|
Msg: "authorizer not found on context",
|
||||||
Code: platform.EInternal,
|
Code: influxdb.EInternal,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auth, ok := a.(*platform.Authorization)
|
auth, ok := a.(*influxdb.Authorization)
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", &platform.Error{
|
return "", &influxdb.Error{
|
||||||
Msg: fmt.Sprintf("authorizer not an authorization but a %T", a),
|
Msg: fmt.Sprintf("authorizer not an authorization but a %T", a),
|
||||||
Code: platform.EInternal,
|
Code: influxdb.EInternal,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -121,16 +121,16 @@ func (h *AuthenticationHandler) extractSession(ctx context.Context, r *http.Requ
|
||||||
return ctx, err
|
return ctx, err
|
||||||
}
|
}
|
||||||
|
|
||||||
s, e := h.SessionService.FindSession(ctx, k)
|
s, err := h.SessionService.FindSession(ctx, k)
|
||||||
if e != nil {
|
if err != nil {
|
||||||
return ctx, e
|
return ctx, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !h.SessionRenewDisabled {
|
if !h.SessionRenewDisabled {
|
||||||
// if the session is not expired, renew the session
|
// if the session is not expired, renew the session
|
||||||
e = h.SessionService.RenewSession(ctx, s, time.Now().Add(platform.RenewSessionTime))
|
err = h.SessionService.RenewSession(ctx, s, time.Now().Add(platform.RenewSessionTime))
|
||||||
if e != nil {
|
if err != nil {
|
||||||
return ctx, e
|
return ctx, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,3 +19,9 @@ type Event struct {
|
||||||
ResponseBytes int
|
ResponseBytes int
|
||||||
Status int
|
Status int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NopEventRecorder never records events.
|
||||||
|
type NopEventRecorder struct{}
|
||||||
|
|
||||||
|
// Record never records events.
|
||||||
|
func (n *NopEventRecorder) Record(ctx context.Context, e Event) {}
|
||||||
|
|
|
@ -15,6 +15,12 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
// queryOrganization returns the organization for any http request.
|
// queryOrganization returns the organization for any http request.
|
||||||
|
//
|
||||||
|
// It checks the org= and then orgID= parameter of the request.
|
||||||
|
//
|
||||||
|
// This will try to find the organization using an ID string or
|
||||||
|
// the name. It interprets the &org= parameter as either the name
|
||||||
|
// or the ID.
|
||||||
func queryOrganization(ctx context.Context, r *http.Request, svc platform.OrganizationService) (o *platform.Organization, err error) {
|
func queryOrganization(ctx context.Context, r *http.Request, svc platform.OrganizationService) (o *platform.Organization, err error) {
|
||||||
|
|
||||||
filter := platform.OrganizationFilter{}
|
filter := platform.OrganizationFilter{}
|
||||||
|
|
|
@ -125,7 +125,7 @@ type signoutRequest struct {
|
||||||
Key string
|
Key string
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeSignoutRequest(ctx context.Context, r *http.Request) (*signoutRequest, *platform.Error) {
|
func decodeSignoutRequest(ctx context.Context, r *http.Request) (*signoutRequest, error) {
|
||||||
key, err := decodeCookieSession(ctx, r)
|
key, err := decodeCookieSession(ctx, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -145,12 +145,12 @@ func encodeCookieSession(w http.ResponseWriter, s *platform.Session) {
|
||||||
|
|
||||||
http.SetCookie(w, c)
|
http.SetCookie(w, c)
|
||||||
}
|
}
|
||||||
func decodeCookieSession(ctx context.Context, r *http.Request) (string, *platform.Error) {
|
func decodeCookieSession(ctx context.Context, r *http.Request) (string, error) {
|
||||||
c, err := r.Cookie(cookieSessionName)
|
c, err := r.Cookie(cookieSessionName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", &platform.Error{
|
return "", &platform.Error{
|
||||||
Err: err,
|
|
||||||
Code: platform.EInvalid,
|
Code: platform.EInvalid,
|
||||||
|
Err: err,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return c.Value, nil
|
return c.Value, nil
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
platform "github.com/influxdata/influxdb"
|
"github.com/influxdata/influxdb"
|
||||||
pcontext "github.com/influxdata/influxdb/context"
|
pcontext "github.com/influxdata/influxdb/context"
|
||||||
"github.com/influxdata/influxdb/kit/tracing"
|
"github.com/influxdata/influxdb/kit/tracing"
|
||||||
"github.com/influxdata/influxdb/models"
|
"github.com/influxdata/influxdb/models"
|
||||||
|
@ -24,13 +24,13 @@ import (
|
||||||
// WriteBackend is all services and associated parameters required to construct
|
// WriteBackend is all services and associated parameters required to construct
|
||||||
// the WriteHandler.
|
// the WriteHandler.
|
||||||
type WriteBackend struct {
|
type WriteBackend struct {
|
||||||
platform.HTTPErrorHandler
|
influxdb.HTTPErrorHandler
|
||||||
Logger *zap.Logger
|
Logger *zap.Logger
|
||||||
WriteEventRecorder metric.EventRecorder
|
WriteEventRecorder metric.EventRecorder
|
||||||
|
|
||||||
PointsWriter storage.PointsWriter
|
PointsWriter storage.PointsWriter
|
||||||
BucketService platform.BucketService
|
BucketService influxdb.BucketService
|
||||||
OrganizationService platform.OrganizationService
|
OrganizationService influxdb.OrganizationService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWriteBackend returns a new instance of WriteBackend.
|
// NewWriteBackend returns a new instance of WriteBackend.
|
||||||
|
@ -49,11 +49,11 @@ func NewWriteBackend(b *APIBackend) *WriteBackend {
|
||||||
// WriteHandler receives line protocol and sends to a publish function.
|
// WriteHandler receives line protocol and sends to a publish function.
|
||||||
type WriteHandler struct {
|
type WriteHandler struct {
|
||||||
*httprouter.Router
|
*httprouter.Router
|
||||||
platform.HTTPErrorHandler
|
influxdb.HTTPErrorHandler
|
||||||
Logger *zap.Logger
|
Logger *zap.Logger
|
||||||
|
|
||||||
BucketService platform.BucketService
|
BucketService influxdb.BucketService
|
||||||
OrganizationService platform.OrganizationService
|
OrganizationService influxdb.OrganizationService
|
||||||
|
|
||||||
PointsWriter storage.PointsWriter
|
PointsWriter storage.PointsWriter
|
||||||
|
|
||||||
|
@ -92,7 +92,7 @@ func (h *WriteHandler) handleWrite(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// TODO(desa): I really don't like how we're recording the usage metrics here
|
// TODO(desa): I really don't like how we're recording the usage metrics here
|
||||||
// Ideally this will be moved when we solve https://github.com/influxdata/influxdb/issues/13403
|
// Ideally this will be moved when we solve https://github.com/influxdata/influxdb/issues/13403
|
||||||
var orgID platform.ID
|
var orgID influxdb.ID
|
||||||
var requestBytes int
|
var requestBytes int
|
||||||
sw := newStatusResponseWriter(w)
|
sw := newStatusResponseWriter(w)
|
||||||
w = sw
|
w = sw
|
||||||
|
@ -111,8 +111,8 @@ func (h *WriteHandler) handleWrite(w http.ResponseWriter, r *http.Request) {
|
||||||
var err error
|
var err error
|
||||||
in, err = gzip.NewReader(r.Body)
|
in, err = gzip.NewReader(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.HandleHTTPError(ctx, &platform.Error{
|
h.HandleHTTPError(ctx, &influxdb.Error{
|
||||||
Code: platform.EInvalid,
|
Code: influxdb.EInvalid,
|
||||||
Op: "http/handleWrite",
|
Op: "http/handleWrite",
|
||||||
Msg: errInvalidGzipHeader,
|
Msg: errInvalidGzipHeader,
|
||||||
Err: err,
|
Err: err,
|
||||||
|
@ -136,7 +136,7 @@ func (h *WriteHandler) handleWrite(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
logger := h.Logger.With(zap.String("org", req.Org), zap.String("bucket", req.Bucket))
|
logger := h.Logger.With(zap.String("org", req.Org), zap.String("bucket", req.Bucket))
|
||||||
|
|
||||||
var org *platform.Organization
|
var org *influxdb.Organization
|
||||||
org, err = queryOrganization(ctx, r, h.OrganizationService)
|
org, err = queryOrganization(ctx, r, h.OrganizationService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Info("Failed to find organization", zap.Error(err))
|
logger.Info("Failed to find organization", zap.Error(err))
|
||||||
|
@ -146,31 +146,28 @@ func (h *WriteHandler) handleWrite(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
orgID = org.ID
|
orgID = org.ID
|
||||||
|
|
||||||
var bucket *platform.Bucket
|
var bucket *influxdb.Bucket
|
||||||
if id, err := platform.IDFromString(req.Bucket); err == nil {
|
if id, err := influxdb.IDFromString(req.Bucket); err == nil {
|
||||||
// Decoded ID successfully. Make sure it's a real bucket.
|
// Decoded ID successfully. Make sure it's a real bucket.
|
||||||
b, err := h.BucketService.FindBucket(ctx, platform.BucketFilter{
|
b, err := h.BucketService.FindBucket(ctx, influxdb.BucketFilter{
|
||||||
OrganizationID: &org.ID,
|
OrganizationID: &org.ID,
|
||||||
ID: id,
|
ID: id,
|
||||||
})
|
})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
bucket = b
|
bucket = b
|
||||||
} else if platform.ErrorCode(err) != platform.ENotFound {
|
} else if influxdb.ErrorCode(err) != influxdb.ENotFound {
|
||||||
h.HandleHTTPError(ctx, err, w)
|
h.HandleHTTPError(ctx, err, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if bucket == nil {
|
if bucket == nil {
|
||||||
b, err := h.BucketService.FindBucket(ctx, platform.BucketFilter{
|
b, err := h.BucketService.FindBucket(ctx, influxdb.BucketFilter{
|
||||||
OrganizationID: &org.ID,
|
OrganizationID: &org.ID,
|
||||||
Name: &req.Bucket,
|
Name: &req.Bucket,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.HandleHTTPError(ctx, &platform.Error{
|
h.HandleHTTPError(ctx, err, w)
|
||||||
Op: "http/handleWrite",
|
|
||||||
Err: err,
|
|
||||||
}, w)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,18 +176,18 @@ func (h *WriteHandler) handleWrite(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// TODO(jade): remove this after system buckets issue is resolved
|
// TODO(jade): remove this after system buckets issue is resolved
|
||||||
if bucket.IsSystem() {
|
if bucket.IsSystem() {
|
||||||
h.HandleHTTPError(ctx, &platform.Error{
|
h.HandleHTTPError(ctx, &influxdb.Error{
|
||||||
Code: platform.EForbidden,
|
Code: influxdb.EForbidden,
|
||||||
Op: "http/handleWrite",
|
Op: "http/handleWrite",
|
||||||
Msg: fmt.Sprintf("cannot write to internal bucket %s", bucket.Name),
|
Msg: fmt.Sprintf("cannot write to internal bucket %s", bucket.Name),
|
||||||
}, w)
|
}, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
p, err := platform.NewPermissionAtID(bucket.ID, platform.WriteAction, platform.BucketsResourceType, org.ID)
|
p, err := influxdb.NewPermissionAtID(bucket.ID, influxdb.WriteAction, influxdb.BucketsResourceType, org.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.HandleHTTPError(ctx, &platform.Error{
|
h.HandleHTTPError(ctx, &influxdb.Error{
|
||||||
Code: platform.EInternal,
|
Code: influxdb.EInternal,
|
||||||
Op: "http/handleWrite",
|
Op: "http/handleWrite",
|
||||||
Msg: fmt.Sprintf("unable to create permission for bucket: %v", err),
|
Msg: fmt.Sprintf("unable to create permission for bucket: %v", err),
|
||||||
Err: err,
|
Err: err,
|
||||||
|
@ -199,8 +196,8 @@ func (h *WriteHandler) handleWrite(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !a.Allowed(*p) {
|
if !a.Allowed(*p) {
|
||||||
h.HandleHTTPError(ctx, &platform.Error{
|
h.HandleHTTPError(ctx, &influxdb.Error{
|
||||||
Code: platform.EForbidden,
|
Code: influxdb.EForbidden,
|
||||||
Op: "http/handleWrite",
|
Op: "http/handleWrite",
|
||||||
Msg: "insufficient permissions for write",
|
Msg: "insufficient permissions for write",
|
||||||
}, w)
|
}, w)
|
||||||
|
@ -213,36 +210,43 @@ func (h *WriteHandler) handleWrite(w http.ResponseWriter, r *http.Request) {
|
||||||
data, err := ioutil.ReadAll(in)
|
data, err := ioutil.ReadAll(in)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Error reading body", zap.Error(err))
|
logger.Error("Error reading body", zap.Error(err))
|
||||||
h.HandleHTTPError(ctx, &platform.Error{
|
h.HandleHTTPError(ctx, &influxdb.Error{
|
||||||
Code: platform.EInternal,
|
Code: influxdb.EInternal,
|
||||||
Op: "http/handleWrite",
|
Op: "http/handleWrite",
|
||||||
Msg: fmt.Sprintf("unable to read data: %v", err),
|
Msg: fmt.Sprintf("unable to read data: %v", err),
|
||||||
Err: err,
|
Err: err,
|
||||||
}, w)
|
}, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
requestBytes = len(data)
|
requestBytes = len(data)
|
||||||
|
if requestBytes == 0 {
|
||||||
|
h.HandleHTTPError(ctx, &influxdb.Error{
|
||||||
|
Code: influxdb.EInvalid,
|
||||||
|
Op: "http/handleWrite",
|
||||||
|
Msg: "writing requires points",
|
||||||
|
}, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
encoded := tsdb.EncodeName(org.ID, bucket.ID)
|
encoded := tsdb.EncodeName(org.ID, bucket.ID)
|
||||||
mm := models.EscapeMeasurement(encoded[:])
|
mm := models.EscapeMeasurement(encoded[:])
|
||||||
points, err := models.ParsePointsWithPrecision(data, mm, time.Now(), req.Precision)
|
points, err := models.ParsePointsWithPrecision(data, mm, time.Now(), req.Precision)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Error parsing points", zap.Error(err))
|
logger.Error("Error parsing points", zap.Error(err))
|
||||||
h.HandleHTTPError(ctx, &platform.Error{
|
h.HandleHTTPError(ctx, &influxdb.Error{
|
||||||
Code: platform.EInvalid,
|
Code: influxdb.EInvalid,
|
||||||
Op: "http/handleWrite",
|
Msg: err.Error(),
|
||||||
Msg: fmt.Sprintf("unable to parse points: %v", err),
|
|
||||||
Err: err,
|
|
||||||
}, w)
|
}, w)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.PointsWriter.WritePoints(ctx, points); err != nil {
|
if err := h.PointsWriter.WritePoints(ctx, points); err != nil {
|
||||||
logger.Error("Error writing points", zap.Error(err))
|
logger.Error("Error writing points", zap.Error(err))
|
||||||
h.HandleHTTPError(ctx, &platform.Error{
|
h.HandleHTTPError(ctx, &influxdb.Error{
|
||||||
Code: platform.EInternal,
|
Code: influxdb.EInternal,
|
||||||
Op: "http/handleWrite",
|
Op: "http/handleWrite",
|
||||||
Msg: fmt.Sprintf("unable to write points to database: %v", err),
|
Msg: "unexpected error writing points to database",
|
||||||
Err: err,
|
Err: err,
|
||||||
}, w)
|
}, w)
|
||||||
return
|
return
|
||||||
|
@ -259,8 +263,8 @@ func decodeWriteRequest(ctx context.Context, r *http.Request) (*postWriteRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
if !models.ValidPrecision(p) {
|
if !models.ValidPrecision(p) {
|
||||||
return nil, &platform.Error{
|
return nil, &influxdb.Error{
|
||||||
Code: platform.EInvalid,
|
Code: influxdb.EInvalid,
|
||||||
Op: "http/decodeWriteRequest",
|
Op: "http/decodeWriteRequest",
|
||||||
Msg: errInvalidPrecision,
|
Msg: errInvalidPrecision,
|
||||||
}
|
}
|
||||||
|
@ -287,17 +291,17 @@ type WriteService struct {
|
||||||
InsecureSkipVerify bool
|
InsecureSkipVerify bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ platform.WriteService = (*WriteService)(nil)
|
var _ influxdb.WriteService = (*WriteService)(nil)
|
||||||
|
|
||||||
func (s *WriteService) Write(ctx context.Context, orgID, bucketID platform.ID, r io.Reader) error {
|
func (s *WriteService) Write(ctx context.Context, orgID, bucketID influxdb.ID, r io.Reader) error {
|
||||||
precision := s.Precision
|
precision := s.Precision
|
||||||
if precision == "" {
|
if precision == "" {
|
||||||
precision = "ns"
|
precision = "ns"
|
||||||
}
|
}
|
||||||
|
|
||||||
if !models.ValidPrecision(precision) {
|
if !models.ValidPrecision(precision) {
|
||||||
return &platform.Error{
|
return &influxdb.Error{
|
||||||
Code: platform.EInvalid,
|
Code: influxdb.EInvalid,
|
||||||
Op: "http/Write",
|
Op: "http/Write",
|
||||||
Msg: errInvalidPrecision,
|
Msg: errInvalidPrecision,
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package http
|
||||||
import (
|
import (
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -10,13 +11,18 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
platform "github.com/influxdata/influxdb"
|
"github.com/influxdata/influxdb"
|
||||||
|
"github.com/influxdata/influxdb/http/metric"
|
||||||
|
httpmock "github.com/influxdata/influxdb/http/mock"
|
||||||
|
"github.com/influxdata/influxdb/mock"
|
||||||
|
influxtesting "github.com/influxdata/influxdb/testing"
|
||||||
|
"go.uber.org/zap/zaptest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestWriteService_Write(t *testing.T) {
|
func TestWriteService_Write(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
org platform.ID
|
org influxdb.ID
|
||||||
bucket platform.ID
|
bucket influxdb.ID
|
||||||
r io.Reader
|
r io.Reader
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
@ -38,11 +44,11 @@ func TestWriteService_Write(t *testing.T) {
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
var org, bucket *platform.ID
|
var org, bucket *influxdb.ID
|
||||||
var lp []byte
|
var lp []byte
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
org, _ = platform.IDFromString(r.URL.Query().Get("org"))
|
org, _ = influxdb.IDFromString(r.URL.Query().Get("org"))
|
||||||
bucket, _ = platform.IDFromString(r.URL.Query().Get("bucket"))
|
bucket, _ = influxdb.IDFromString(r.URL.Query().Get("bucket"))
|
||||||
defer r.Body.Close()
|
defer r.Body.Close()
|
||||||
in, _ := gzip.NewReader(r.Body)
|
in, _ := gzip.NewReader(r.Body)
|
||||||
defer in.Close()
|
defer in.Close()
|
||||||
|
@ -69,3 +75,284 @@ func TestWriteService_Write(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWriteHandler_handleWrite(t *testing.T) {
|
||||||
|
// state is the internal state of org and bucket services
|
||||||
|
type state struct {
|
||||||
|
org *influxdb.Organization // org to return in org service
|
||||||
|
orgErr error // err to return in org servce
|
||||||
|
bucket *influxdb.Bucket // bucket to return in bucket service
|
||||||
|
bucketErr error // err to return in bucket service
|
||||||
|
writeErr error // err to return from the points writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// want is the expected output of the HTTP endpoint
|
||||||
|
type wants struct {
|
||||||
|
body string
|
||||||
|
code int
|
||||||
|
}
|
||||||
|
|
||||||
|
// request is sent to the HTTP endpoint
|
||||||
|
type request struct {
|
||||||
|
auth influxdb.Authorizer
|
||||||
|
org string
|
||||||
|
bucket string
|
||||||
|
body string
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
request request
|
||||||
|
state state
|
||||||
|
wants wants
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "simple body is accepted",
|
||||||
|
request: request{
|
||||||
|
org: "043e0780ee2b1000",
|
||||||
|
bucket: "04504b356e23b000",
|
||||||
|
body: "m1,t1=v1 f1=1",
|
||||||
|
auth: bucketWritePermission("043e0780ee2b1000", "04504b356e23b000"),
|
||||||
|
},
|
||||||
|
state: state{
|
||||||
|
org: testOrg("043e0780ee2b1000"),
|
||||||
|
bucket: testBucket("043e0780ee2b1000", "04504b356e23b000"),
|
||||||
|
},
|
||||||
|
wants: wants{
|
||||||
|
code: 204,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "points writer error is an internal error",
|
||||||
|
request: request{
|
||||||
|
org: "043e0780ee2b1000",
|
||||||
|
bucket: "04504b356e23b000",
|
||||||
|
body: "m1,t1=v1 f1=1",
|
||||||
|
auth: bucketWritePermission("043e0780ee2b1000", "04504b356e23b000"),
|
||||||
|
},
|
||||||
|
state: state{
|
||||||
|
org: testOrg("043e0780ee2b1000"),
|
||||||
|
bucket: testBucket("043e0780ee2b1000", "04504b356e23b000"),
|
||||||
|
writeErr: fmt.Errorf("error"),
|
||||||
|
},
|
||||||
|
wants: wants{
|
||||||
|
code: 500,
|
||||||
|
body: `{"code":"internal error","message":"unexpected error writing points to database","op":"http/handleWrite","error":"error"}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty request body returns 400 error",
|
||||||
|
request: request{
|
||||||
|
org: "043e0780ee2b1000",
|
||||||
|
bucket: "04504b356e23b000",
|
||||||
|
auth: bucketWritePermission("043e0780ee2b1000", "04504b356e23b000"),
|
||||||
|
},
|
||||||
|
state: state{
|
||||||
|
org: testOrg("043e0780ee2b1000"),
|
||||||
|
bucket: testBucket("043e0780ee2b1000", "04504b356e23b000"),
|
||||||
|
},
|
||||||
|
wants: wants{
|
||||||
|
code: 400,
|
||||||
|
body: `{"code":"invalid","message":"writing requires points","op":"http/handleWrite"}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "org error returns 404 error",
|
||||||
|
request: request{
|
||||||
|
org: "043e0780ee2b1000",
|
||||||
|
bucket: "04504b356e23b000",
|
||||||
|
body: "m1,t1=v1 f1=1",
|
||||||
|
auth: bucketWritePermission("043e0780ee2b1000", "04504b356e23b000"),
|
||||||
|
},
|
||||||
|
state: state{
|
||||||
|
orgErr: &influxdb.Error{Code: influxdb.ENotFound, Msg: "not found"},
|
||||||
|
},
|
||||||
|
wants: wants{
|
||||||
|
code: 404,
|
||||||
|
body: `{"code":"not found","message":"not found"}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bucket error returns 404 error",
|
||||||
|
request: request{
|
||||||
|
org: "043e0780ee2b1000",
|
||||||
|
bucket: "04504b356e23b000",
|
||||||
|
body: "m1,t1=v1 f1=1",
|
||||||
|
auth: bucketWritePermission("043e0780ee2b1000", "04504b356e23b000"),
|
||||||
|
},
|
||||||
|
state: state{
|
||||||
|
org: testOrg("043e0780ee2b1000"),
|
||||||
|
bucketErr: &influxdb.Error{Code: influxdb.ENotFound, Msg: "not found"},
|
||||||
|
},
|
||||||
|
wants: wants{
|
||||||
|
code: 404,
|
||||||
|
body: `{"code":"not found","message":"not found"}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "500 when bucket service returns internal error",
|
||||||
|
request: request{
|
||||||
|
org: "043e0780ee2b1000",
|
||||||
|
bucket: "04504b356e23b000",
|
||||||
|
auth: bucketWritePermission("043e0780ee2b1000", "04504b356e23b000"),
|
||||||
|
},
|
||||||
|
state: state{
|
||||||
|
org: testOrg("043e0780ee2b1000"),
|
||||||
|
bucketErr: &influxdb.Error{Code: influxdb.EInternal, Msg: "internal error"},
|
||||||
|
},
|
||||||
|
wants: wants{
|
||||||
|
code: 500,
|
||||||
|
body: `{"code":"internal error","message":"internal error"}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid line protocol returns 400",
|
||||||
|
request: request{
|
||||||
|
org: "043e0780ee2b1000",
|
||||||
|
bucket: "04504b356e23b000",
|
||||||
|
auth: bucketWritePermission("043e0780ee2b1000", "04504b356e23b000"),
|
||||||
|
body: "invalid",
|
||||||
|
},
|
||||||
|
state: state{
|
||||||
|
org: testOrg("043e0780ee2b1000"),
|
||||||
|
bucket: testBucket("043e0780ee2b1000", "04504b356e23b000"),
|
||||||
|
},
|
||||||
|
wants: wants{
|
||||||
|
code: 400,
|
||||||
|
body: `{"code":"invalid","message":"unable to parse 'invalid': missing fields"}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "forbidden to write to system buckets",
|
||||||
|
request: request{
|
||||||
|
org: "043e0780ee2b1000",
|
||||||
|
bucket: "000000000000000a",
|
||||||
|
auth: bucketWritePermission("043e0780ee2b1000", "000000000000000a"),
|
||||||
|
body: "invalid",
|
||||||
|
},
|
||||||
|
state: state{
|
||||||
|
org: testOrg("043e0780ee2b1000"),
|
||||||
|
bucket: testBucket("043e0780ee2b1000", "000000000000000a"),
|
||||||
|
},
|
||||||
|
wants: wants{
|
||||||
|
code: 403,
|
||||||
|
body: `{"code":"forbidden","message":"cannot write to internal bucket ","op":"http/handleWrite"}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "forbidden to write with insufficient permission",
|
||||||
|
request: request{
|
||||||
|
org: "043e0780ee2b1000",
|
||||||
|
bucket: "04504b356e23b000",
|
||||||
|
body: "m1,t1=v1 f1=1",
|
||||||
|
auth: bucketWritePermission("043e0780ee2b1000", "000000000000000a"),
|
||||||
|
},
|
||||||
|
state: state{
|
||||||
|
org: testOrg("043e0780ee2b1000"),
|
||||||
|
bucket: testBucket("043e0780ee2b1000", "04504b356e23b000"),
|
||||||
|
},
|
||||||
|
wants: wants{
|
||||||
|
code: 403,
|
||||||
|
body: `{"code":"forbidden","message":"insufficient permissions for write","op":"http/handleWrite"}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// authorization extraction happens in a different middleware.
|
||||||
|
name: "no authorizer is an internal error",
|
||||||
|
request: request{
|
||||||
|
org: "043e0780ee2b1000",
|
||||||
|
bucket: "04504b356e23b000",
|
||||||
|
},
|
||||||
|
state: state{
|
||||||
|
org: testOrg("043e0780ee2b1000"),
|
||||||
|
bucket: testBucket("043e0780ee2b1000", "04504b356e23b000"),
|
||||||
|
},
|
||||||
|
wants: wants{
|
||||||
|
code: 500,
|
||||||
|
body: `{"code":"internal error","message":"authorizer not found on context"}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
orgs := mock.NewOrganizationService()
|
||||||
|
orgs.FindOrganizationF = func(ctx context.Context, filter influxdb.OrganizationFilter) (*influxdb.Organization, error) {
|
||||||
|
return tt.state.org, tt.state.orgErr
|
||||||
|
}
|
||||||
|
buckets := mock.NewBucketService()
|
||||||
|
buckets.FindBucketFn = func(context.Context, influxdb.BucketFilter) (*influxdb.Bucket, error) {
|
||||||
|
return tt.state.bucket, tt.state.bucketErr
|
||||||
|
}
|
||||||
|
|
||||||
|
b := &APIBackend{
|
||||||
|
HTTPErrorHandler: DefaultErrorHandler,
|
||||||
|
Logger: zaptest.NewLogger(t),
|
||||||
|
OrganizationService: orgs,
|
||||||
|
BucketService: buckets,
|
||||||
|
PointsWriter: &mock.PointsWriter{Err: tt.state.writeErr},
|
||||||
|
WriteEventRecorder: &metric.NopEventRecorder{},
|
||||||
|
}
|
||||||
|
writeHandler := NewWriteHandler(NewWriteBackend(b))
|
||||||
|
handler := httpmock.NewAuthMiddlewareHandler(writeHandler, tt.request.auth)
|
||||||
|
|
||||||
|
r := httptest.NewRequest(
|
||||||
|
"POST",
|
||||||
|
"http://localhost:9999/api/v2/write",
|
||||||
|
strings.NewReader(tt.request.body),
|
||||||
|
)
|
||||||
|
|
||||||
|
params := r.URL.Query()
|
||||||
|
params.Set("org", tt.request.org)
|
||||||
|
params.Set("bucket", tt.request.bucket)
|
||||||
|
r.URL.RawQuery = params.Encode()
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
handler.ServeHTTP(w, r)
|
||||||
|
if got, want := w.Code, tt.wants.code; got != want {
|
||||||
|
t.Errorf("unexpected status code: got %d want %d", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got, want := w.Body.String(), tt.wants.body; got != want {
|
||||||
|
t.Errorf("unexpected body: got %s want %s", got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var DefaultErrorHandler = ErrorHandler(0)
|
||||||
|
|
||||||
|
func bucketWritePermission(org, bucket string) *influxdb.Authorization {
|
||||||
|
oid := influxtesting.MustIDBase16(org)
|
||||||
|
bid := influxtesting.MustIDBase16(bucket)
|
||||||
|
return &influxdb.Authorization{
|
||||||
|
OrgID: oid,
|
||||||
|
Status: influxdb.Active,
|
||||||
|
Permissions: []influxdb.Permission{
|
||||||
|
{
|
||||||
|
Action: influxdb.WriteAction,
|
||||||
|
Resource: influxdb.Resource{
|
||||||
|
Type: influxdb.BucketsResourceType,
|
||||||
|
OrgID: &oid,
|
||||||
|
ID: &bid,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testOrg(org string) *influxdb.Organization {
|
||||||
|
oid := influxtesting.MustIDBase16(org)
|
||||||
|
return &influxdb.Organization{
|
||||||
|
ID: oid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testBucket(org, bucket string) *influxdb.Bucket {
|
||||||
|
oid := influxtesting.MustIDBase16(org)
|
||||||
|
bid := influxtesting.MustIDBase16(bucket)
|
||||||
|
|
||||||
|
return &influxdb.Bucket{
|
||||||
|
ID: bid,
|
||||||
|
OrgID: oid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue