chore(outputs.influxdb_v2): Cleanup code and tests (#16147)
This commit is contained in:
parent
4f951e6367
commit
18b2d3cdc3
|
|
@ -1,6 +1,7 @@
|
||||||
package influxdb_v2
|
package influxdb_v2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
@ -16,11 +17,12 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
"github.com/influxdata/telegraf"
|
||||||
"github.com/influxdata/telegraf/config"
|
"github.com/influxdata/telegraf/config"
|
||||||
"github.com/influxdata/telegraf/internal"
|
"github.com/influxdata/telegraf/internal"
|
||||||
"github.com/influxdata/telegraf/plugins/serializers/influx"
|
"github.com/influxdata/telegraf/plugins/serializers/influx"
|
||||||
"golang.org/x/net/http2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type APIError struct {
|
type APIError struct {
|
||||||
|
|
@ -37,155 +39,102 @@ func (e APIError) Error() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultRequestTimeout = time.Second * 5
|
|
||||||
defaultMaxWaitSeconds = 60
|
defaultMaxWaitSeconds = 60
|
||||||
defaultMaxWaitRetryAfterSeconds = 10 * 60
|
defaultMaxWaitRetryAfterSeconds = 10 * 60
|
||||||
)
|
)
|
||||||
|
|
||||||
type HTTPConfig struct {
|
|
||||||
URL *url.URL
|
|
||||||
LocalAddr *net.TCPAddr
|
|
||||||
Token config.Secret
|
|
||||||
Organization string
|
|
||||||
Bucket string
|
|
||||||
BucketTag string
|
|
||||||
ExcludeBucketTag bool
|
|
||||||
Timeout time.Duration
|
|
||||||
Headers map[string]string
|
|
||||||
Proxy *url.URL
|
|
||||||
UserAgent string
|
|
||||||
ContentEncoding string
|
|
||||||
PingTimeout config.Duration
|
|
||||||
ReadIdleTimeout config.Duration
|
|
||||||
TLSConfig *tls.Config
|
|
||||||
|
|
||||||
Serializer *influx.Serializer
|
|
||||||
Log telegraf.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
type httpClient struct {
|
type httpClient struct {
|
||||||
ContentEncoding string
|
url *url.URL
|
||||||
Timeout time.Duration
|
localAddr *net.TCPAddr
|
||||||
Headers map[string]string
|
token config.Secret
|
||||||
Organization string
|
organization string
|
||||||
Bucket string
|
bucket string
|
||||||
BucketTag string
|
bucketTag string
|
||||||
ExcludeBucketTag bool
|
excludeBucketTag bool
|
||||||
|
timeout time.Duration
|
||||||
client *http.Client
|
headers map[string]string
|
||||||
serializer *influx.Serializer
|
proxy *url.URL
|
||||||
url *url.URL
|
userAgent string
|
||||||
params url.Values
|
contentEncoding string
|
||||||
retryTime time.Time
|
pingTimeout config.Duration
|
||||||
retryCount int
|
readIdleTimeout config.Duration
|
||||||
log telegraf.Logger
|
tlsConfig *tls.Config
|
||||||
|
serializer *influx.Serializer
|
||||||
|
encoder internal.ContentEncoder
|
||||||
|
client *http.Client
|
||||||
|
params url.Values
|
||||||
|
retryTime time.Time
|
||||||
|
retryCount int
|
||||||
|
log telegraf.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTPClient(cfg *HTTPConfig) (*httpClient, error) {
|
func (c *httpClient) Init() error {
|
||||||
if cfg.URL == nil {
|
token, err := c.token.Get()
|
||||||
return nil, ErrMissingURL
|
|
||||||
}
|
|
||||||
|
|
||||||
timeout := cfg.Timeout
|
|
||||||
if timeout == 0 {
|
|
||||||
timeout = defaultRequestTimeout
|
|
||||||
}
|
|
||||||
|
|
||||||
userAgent := cfg.UserAgent
|
|
||||||
if userAgent == "" {
|
|
||||||
userAgent = internal.ProductToken()
|
|
||||||
}
|
|
||||||
|
|
||||||
var headers = make(map[string]string, len(cfg.Headers)+2)
|
|
||||||
headers["User-Agent"] = userAgent
|
|
||||||
|
|
||||||
token, err := cfg.Token.Get()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("getting token failed: %w", err)
|
return fmt.Errorf("getting token failed: %w", err)
|
||||||
}
|
}
|
||||||
headers["Authorization"] = "Token " + token.String()
|
|
||||||
|
if c.headers == nil {
|
||||||
|
c.headers = make(map[string]string, 2)
|
||||||
|
}
|
||||||
|
c.headers["Authorization"] = "Token " + token.String()
|
||||||
token.Destroy()
|
token.Destroy()
|
||||||
for k, v := range cfg.Headers {
|
c.headers["User-Agent"] = c.userAgent
|
||||||
headers[k] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
var proxy func(*http.Request) (*url.URL, error)
|
var proxy func(*http.Request) (*url.URL, error)
|
||||||
if cfg.Proxy != nil {
|
if c.proxy != nil {
|
||||||
proxy = http.ProxyURL(cfg.Proxy)
|
proxy = http.ProxyURL(c.proxy)
|
||||||
} else {
|
} else {
|
||||||
proxy = http.ProxyFromEnvironment
|
proxy = http.ProxyFromEnvironment
|
||||||
}
|
}
|
||||||
|
|
||||||
serializer := cfg.Serializer
|
|
||||||
if serializer == nil {
|
|
||||||
serializer = &influx.Serializer{}
|
|
||||||
if err := serializer.Init(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var transport *http.Transport
|
var transport *http.Transport
|
||||||
switch cfg.URL.Scheme {
|
switch c.url.Scheme {
|
||||||
case "http", "https":
|
case "http", "https":
|
||||||
var dialerFunc func(ctx context.Context, network, addr string) (net.Conn, error)
|
var dialerFunc func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||||
if cfg.LocalAddr != nil {
|
if c.localAddr != nil {
|
||||||
dialer := &net.Dialer{LocalAddr: cfg.LocalAddr}
|
dialer := &net.Dialer{LocalAddr: c.localAddr}
|
||||||
dialerFunc = dialer.DialContext
|
dialerFunc = dialer.DialContext
|
||||||
}
|
}
|
||||||
transport = &http.Transport{
|
transport = &http.Transport{
|
||||||
Proxy: proxy,
|
Proxy: proxy,
|
||||||
TLSClientConfig: cfg.TLSConfig,
|
TLSClientConfig: c.tlsConfig,
|
||||||
DialContext: dialerFunc,
|
DialContext: dialerFunc,
|
||||||
}
|
}
|
||||||
if cfg.ReadIdleTimeout != 0 || cfg.PingTimeout != 0 {
|
if c.readIdleTimeout != 0 || c.pingTimeout != 0 {
|
||||||
http2Trans, err := http2.ConfigureTransports(transport)
|
http2Trans, err := http2.ConfigureTransports(transport)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
http2Trans.ReadIdleTimeout = time.Duration(cfg.ReadIdleTimeout)
|
http2Trans.ReadIdleTimeout = time.Duration(c.readIdleTimeout)
|
||||||
http2Trans.PingTimeout = time.Duration(cfg.PingTimeout)
|
http2Trans.PingTimeout = time.Duration(c.pingTimeout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "unix":
|
case "unix":
|
||||||
transport = &http.Transport{
|
transport = &http.Transport{
|
||||||
Dial: func(_, _ string) (net.Conn, error) {
|
Dial: func(_, _ string) (net.Conn, error) {
|
||||||
return net.DialTimeout(
|
return net.DialTimeout(
|
||||||
cfg.URL.Scheme,
|
c.url.Scheme,
|
||||||
cfg.URL.Path,
|
c.url.Path,
|
||||||
timeout,
|
c.timeout,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported scheme %q", cfg.URL.Scheme)
|
return fmt.Errorf("unsupported scheme %q", c.url.Scheme)
|
||||||
}
|
}
|
||||||
|
|
||||||
preppedURL, params, err := prepareWriteURL(*cfg.URL, cfg.Organization)
|
preppedURL, params, err := prepareWriteURL(*c.url, c.organization)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
client := &httpClient{
|
c.url = preppedURL
|
||||||
serializer: serializer,
|
c.client = &http.Client{
|
||||||
client: &http.Client{
|
Timeout: c.timeout,
|
||||||
Timeout: timeout,
|
Transport: transport,
|
||||||
Transport: transport,
|
|
||||||
},
|
|
||||||
url: preppedURL,
|
|
||||||
params: params,
|
|
||||||
ContentEncoding: cfg.ContentEncoding,
|
|
||||||
Timeout: timeout,
|
|
||||||
Headers: headers,
|
|
||||||
Organization: cfg.Organization,
|
|
||||||
Bucket: cfg.Bucket,
|
|
||||||
BucketTag: cfg.BucketTag,
|
|
||||||
ExcludeBucketTag: cfg.ExcludeBucketTag,
|
|
||||||
log: cfg.Log,
|
|
||||||
}
|
}
|
||||||
return client, nil
|
c.params = params
|
||||||
}
|
|
||||||
|
|
||||||
// URL returns the origin URL that this client connects too.
|
return nil
|
||||||
func (c *httpClient) URL() string {
|
|
||||||
return c.url.String()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type genericRespError struct {
|
type genericRespError struct {
|
||||||
|
|
@ -211,13 +160,13 @@ func (c *httpClient) Write(ctx context.Context, metrics []telegraf.Metric) error
|
||||||
}
|
}
|
||||||
|
|
||||||
batches := make(map[string][]telegraf.Metric)
|
batches := make(map[string][]telegraf.Metric)
|
||||||
if c.BucketTag == "" {
|
if c.bucketTag == "" {
|
||||||
err := c.writeBatch(ctx, c.Bucket, metrics)
|
err := c.writeBatch(ctx, c.bucket, metrics)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var apiErr *APIError
|
var apiErr *APIError
|
||||||
if errors.As(err, &apiErr) {
|
if errors.As(err, &apiErr) {
|
||||||
if apiErr.StatusCode == http.StatusRequestEntityTooLarge {
|
if apiErr.StatusCode == http.StatusRequestEntityTooLarge {
|
||||||
return c.splitAndWriteBatch(ctx, c.Bucket, metrics)
|
return c.splitAndWriteBatch(ctx, c.bucket, metrics)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -225,20 +174,20 @@ func (c *httpClient) Write(ctx context.Context, metrics []telegraf.Metric) error
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for _, metric := range metrics {
|
for _, metric := range metrics {
|
||||||
bucket, ok := metric.GetTag(c.BucketTag)
|
bucket, ok := metric.GetTag(c.bucketTag)
|
||||||
if !ok {
|
if !ok {
|
||||||
bucket = c.Bucket
|
bucket = c.bucket
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := batches[bucket]; !ok {
|
if _, ok := batches[bucket]; !ok {
|
||||||
batches[bucket] = make([]telegraf.Metric, 0)
|
batches[bucket] = make([]telegraf.Metric, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.ExcludeBucketTag {
|
if c.excludeBucketTag {
|
||||||
// Avoid modifying the metric in case we need to retry the request.
|
// Avoid modifying the metric in case we need to retry the request.
|
||||||
metric = metric.Copy()
|
metric = metric.Copy()
|
||||||
metric.Accept()
|
metric.Accept()
|
||||||
metric.RemoveTag(c.BucketTag)
|
metric.RemoveTag(c.bucketTag)
|
||||||
}
|
}
|
||||||
|
|
||||||
batches[bucket] = append(batches[bucket], metric)
|
batches[bucket] = append(batches[bucket], metric)
|
||||||
|
|
@ -250,7 +199,7 @@ func (c *httpClient) Write(ctx context.Context, metrics []telegraf.Metric) error
|
||||||
var apiErr *APIError
|
var apiErr *APIError
|
||||||
if errors.As(err, &apiErr) {
|
if errors.As(err, &apiErr) {
|
||||||
if apiErr.StatusCode == http.StatusRequestEntityTooLarge {
|
if apiErr.StatusCode == http.StatusRequestEntityTooLarge {
|
||||||
return c.splitAndWriteBatch(ctx, c.Bucket, metrics)
|
return c.splitAndWriteBatch(ctx, c.bucket, metrics)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -273,14 +222,33 @@ func (c *httpClient) splitAndWriteBatch(ctx context.Context, bucket string, metr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *httpClient) writeBatch(ctx context.Context, bucket string, metrics []telegraf.Metric) error {
|
func (c *httpClient) writeBatch(ctx context.Context, bucket string, metrics []telegraf.Metric) error {
|
||||||
reader := c.requestBodyReader(metrics)
|
// Serialize the metrics
|
||||||
defer reader.Close()
|
body, err := c.serializer.SerializeBatch(metrics)
|
||||||
|
|
||||||
req, err := c.makeWriteRequest(makeWriteURL(*c.url, c.params, bucket), reader)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Encode the content if requested
|
||||||
|
if c.encoder != nil {
|
||||||
|
var err error
|
||||||
|
if body, err = c.encoder.Encode(body); err != nil {
|
||||||
|
return fmt.Errorf("encoding failed: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the request
|
||||||
|
address := makeWriteURL(*c.url, c.params, bucket)
|
||||||
|
req, err := http.NewRequest("POST", address, io.NopCloser(bytes.NewBuffer(body)))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating request failed: %w", err)
|
||||||
|
}
|
||||||
|
if c.encoder != nil {
|
||||||
|
req.Header.Set("Content-Encoding", c.contentEncoding)
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "text/plain; charset=utf-8")
|
||||||
|
c.addHeaders(req)
|
||||||
|
|
||||||
|
// Execute the request
|
||||||
resp, err := c.client.Do(req.WithContext(ctx))
|
resp, err := c.client.Do(req.WithContext(ctx))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
internal.OnClientError(c.client, err)
|
internal.OnClientError(c.client, err)
|
||||||
|
|
@ -288,6 +256,7 @@ func (c *httpClient) writeBatch(ctx context.Context, bucket string, metrics []te
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// Check for success
|
||||||
switch resp.StatusCode {
|
switch resp.StatusCode {
|
||||||
case
|
case
|
||||||
// this is the expected response:
|
// this is the expected response:
|
||||||
|
|
@ -303,6 +272,7 @@ func (c *httpClient) writeBatch(ctx context.Context, bucket string, metrics []te
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We got an error and now try to decode further
|
||||||
writeResp := &genericRespError{}
|
writeResp := &genericRespError{}
|
||||||
err = json.NewDecoder(resp.Body).Decode(writeResp)
|
err = json.NewDecoder(resp.Body).Decode(writeResp)
|
||||||
desc := writeResp.Error()
|
desc := writeResp.Error()
|
||||||
|
|
@ -388,38 +358,8 @@ func (c *httpClient) getRetryDuration(headers http.Header) time.Duration {
|
||||||
return time.Duration(retry*1000) * time.Millisecond
|
return time.Duration(retry*1000) * time.Millisecond
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *httpClient) makeWriteRequest(address string, body io.Reader) (*http.Request, error) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", address, body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Set("Content-Type", "text/plain; charset=utf-8")
|
|
||||||
c.addHeaders(req)
|
|
||||||
|
|
||||||
if c.ContentEncoding == "gzip" {
|
|
||||||
req.Header.Set("Content-Encoding", "gzip")
|
|
||||||
}
|
|
||||||
|
|
||||||
return req, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// requestBodyReader warp io.Reader from influx.NewReader to io.ReadCloser, which is useful to fast close the write
|
|
||||||
// side of the connection in case of error
|
|
||||||
func (c *httpClient) requestBodyReader(metrics []telegraf.Metric) io.ReadCloser {
|
|
||||||
reader := influx.NewReader(metrics, c.serializer)
|
|
||||||
|
|
||||||
if c.ContentEncoding == "gzip" {
|
|
||||||
return internal.CompressWithGzip(reader)
|
|
||||||
}
|
|
||||||
|
|
||||||
return io.NopCloser(reader)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *httpClient) addHeaders(req *http.Request) {
|
func (c *httpClient) addHeaders(req *http.Request) {
|
||||||
for header, value := range c.Headers {
|
for header, value := range c.headers {
|
||||||
if strings.EqualFold(header, "host") {
|
if strings.EqualFold(header, "host") {
|
||||||
req.Host = value
|
req.Host = value
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,173 +0,0 @@
|
||||||
package influxdb_v2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"path"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func genURL(u string) *url.URL {
|
|
||||||
//nolint:errcheck // known test urls
|
|
||||||
address, _ := url.Parse(u)
|
|
||||||
return address
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMakeWriteURL(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
err bool
|
|
||||||
url *url.URL
|
|
||||||
act string
|
|
||||||
bkt string
|
|
||||||
org string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
url: genURL("http://localhost:9999"),
|
|
||||||
act: "http://localhost:9999/api/v2/write?bucket=telegraf0&org=influx0",
|
|
||||||
bkt: "telegraf0",
|
|
||||||
org: "influx0",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: genURL("http://localhost:9999?id=abc"),
|
|
||||||
act: "http://localhost:9999/api/v2/write?bucket=telegraf1&id=abc&org=influx1",
|
|
||||||
bkt: "telegraf1",
|
|
||||||
org: "influx1",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: genURL("unix://var/run/influxd.sock"),
|
|
||||||
act: "http://127.0.0.1/api/v2/write?bucket=telegraf2&org=influx2",
|
|
||||||
bkt: "telegraf2",
|
|
||||||
org: "influx2",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
err: true,
|
|
||||||
url: genURL("udp://localhost:9999"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range tests {
|
|
||||||
rURL, params, err := prepareWriteURL(*tests[i].url, tests[i].org)
|
|
||||||
if !tests[i].err {
|
|
||||||
require.NoError(t, err)
|
|
||||||
} else {
|
|
||||||
require.Error(t, err)
|
|
||||||
t.Log(err)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
for j := 0; j < 2; j++ {
|
|
||||||
require.Equal(t, tests[i].act, makeWriteURL(*rURL, params, tests[i].bkt))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExponentialBackoffCalculation(t *testing.T) {
|
|
||||||
c := &httpClient{}
|
|
||||||
tests := []struct {
|
|
||||||
retryCount int
|
|
||||||
expected time.Duration
|
|
||||||
}{
|
|
||||||
{retryCount: 0, expected: 0},
|
|
||||||
{retryCount: 1, expected: 25 * time.Millisecond},
|
|
||||||
{retryCount: 5, expected: 625 * time.Millisecond},
|
|
||||||
{retryCount: 10, expected: 2500 * time.Millisecond},
|
|
||||||
{retryCount: 30, expected: 22500 * time.Millisecond},
|
|
||||||
{retryCount: 40, expected: 40 * time.Second},
|
|
||||||
{retryCount: 50, expected: 60 * time.Second}, // max hit
|
|
||||||
{retryCount: 100, expected: 60 * time.Second},
|
|
||||||
{retryCount: 1000, expected: 60 * time.Second},
|
|
||||||
}
|
|
||||||
for _, test := range tests {
|
|
||||||
t.Run(fmt.Sprintf("%d_retries", test.retryCount), func(t *testing.T) {
|
|
||||||
c.retryCount = test.retryCount
|
|
||||||
require.EqualValues(t, test.expected, c.getRetryDuration(http.Header{}))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExponentialBackoffCalculationWithRetryAfter(t *testing.T) {
|
|
||||||
c := &httpClient{}
|
|
||||||
tests := []struct {
|
|
||||||
retryCount int
|
|
||||||
retryAfter string
|
|
||||||
expected time.Duration
|
|
||||||
}{
|
|
||||||
{retryCount: 0, retryAfter: "0", expected: 0},
|
|
||||||
{retryCount: 0, retryAfter: "10", expected: 10 * time.Second},
|
|
||||||
{retryCount: 0, retryAfter: "60", expected: 60 * time.Second},
|
|
||||||
{retryCount: 0, retryAfter: "600", expected: 600 * time.Second},
|
|
||||||
{retryCount: 0, retryAfter: "601", expected: 600 * time.Second}, // max hit
|
|
||||||
{retryCount: 40, retryAfter: "39", expected: 40 * time.Second}, // retryCount wins
|
|
||||||
{retryCount: 40, retryAfter: "41", expected: 41 * time.Second}, // retryAfter wins
|
|
||||||
{retryCount: 100, retryAfter: "100", expected: 100 * time.Second},
|
|
||||||
}
|
|
||||||
for _, test := range tests {
|
|
||||||
t.Run(fmt.Sprintf("%d_retries", test.retryCount), func(t *testing.T) {
|
|
||||||
c.retryCount = test.retryCount
|
|
||||||
hdr := http.Header{}
|
|
||||||
hdr.Add("Retry-After", test.retryAfter)
|
|
||||||
require.EqualValues(t, test.expected, c.getRetryDuration(hdr))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
bucket = "bkt"
|
|
||||||
org = "org"
|
|
||||||
//nolint:errcheck // known test urls
|
|
||||||
loc, params, _ = prepareWriteURL(*genURL("http://localhost:8086"), org)
|
|
||||||
)
|
|
||||||
|
|
||||||
// goos: linux
|
|
||||||
// goarch: amd64
|
|
||||||
// pkg: github.com/influxdata/telegraf/plugins/outputs/influxdb_v2
|
|
||||||
// cpu: 11th Gen Intel(R) Core(TM) i7-11850H @ 2.50GHz
|
|
||||||
// BenchmarkOldMakeWriteURL
|
|
||||||
// BenchmarkOldMakeWriteURL-16 1556631 683.2 ns/op 424 B/op 14 allocs/op
|
|
||||||
// PASS
|
|
||||||
// ok github.com/influxdata/telegraf/plugins/outputs/influxdb_v2 1.851s
|
|
||||||
func BenchmarkOldMakeWriteURL(b *testing.B) {
|
|
||||||
b.ReportAllocs()
|
|
||||||
for n := 0; n < b.N; n++ {
|
|
||||||
//nolint:errcheck // Skip error for benchmarking
|
|
||||||
oldMakeWriteURL(*loc, org, bucket)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// goos: linux
|
|
||||||
// goarch: amd64
|
|
||||||
// pkg: github.com/influxdata/telegraf/plugins/outputs/influxdb_v2
|
|
||||||
// cpu: 11th Gen Intel(R) Core(TM) i7-11850H @ 2.50GHz
|
|
||||||
// BenchmarkNewMakeWriteURL
|
|
||||||
// BenchmarkNewMakeWriteURL-16 2084415 496.5 ns/op 280 B/op 9 allocs/op
|
|
||||||
// PASS
|
|
||||||
// ok github.com/influxdata/telegraf/plugins/outputs/influxdb_v2 1.626s
|
|
||||||
func BenchmarkNewMakeWriteURL(b *testing.B) {
|
|
||||||
b.ReportAllocs()
|
|
||||||
for n := 0; n < b.N; n++ {
|
|
||||||
makeWriteURL(*loc, params, bucket)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func oldMakeWriteURL(loc url.URL, org, bucket string) (string, error) {
|
|
||||||
params := url.Values{}
|
|
||||||
params.Set("bucket", bucket)
|
|
||||||
params.Set("org", org)
|
|
||||||
|
|
||||||
switch loc.Scheme {
|
|
||||||
case "unix":
|
|
||||||
loc.Scheme = "http"
|
|
||||||
loc.Host = "127.0.0.1"
|
|
||||||
loc.Path = "/api/v2/write"
|
|
||||||
case "http", "https":
|
|
||||||
loc.Path = path.Join(loc.Path, "/api/v2/write")
|
|
||||||
default:
|
|
||||||
return "", fmt.Errorf("unsupported scheme: %q", loc.Scheme)
|
|
||||||
}
|
|
||||||
loc.RawQuery = params.Encode()
|
|
||||||
return loc.String(), nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,283 +1,253 @@
|
||||||
package influxdb_v2_test
|
package influxdb_v2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"path"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
|
||||||
"github.com/influxdata/telegraf/config"
|
"github.com/influxdata/telegraf/config"
|
||||||
influxdb "github.com/influxdata/telegraf/plugins/outputs/influxdb_v2"
|
|
||||||
"github.com/influxdata/telegraf/testutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func genURL(u string) *url.URL {
|
func TestHTTPClientInit(t *testing.T) {
|
||||||
//nolint:errcheck // known test urls
|
|
||||||
address, _ := url.Parse(u)
|
|
||||||
return address
|
|
||||||
}
|
|
||||||
func TestNewHTTPClient(t *testing.T) {
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
err bool
|
name string
|
||||||
cfg *influxdb.HTTPConfig
|
addr string
|
||||||
|
client *httpClient
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
err: true,
|
name: "unix socket",
|
||||||
cfg: &influxdb.HTTPConfig{},
|
addr: "unix://var/run/influxd.sock",
|
||||||
|
client: &httpClient{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
err: true,
|
name: "unix socket with timeouts",
|
||||||
cfg: &influxdb.HTTPConfig{
|
addr: "unix://var/run/influxd.sock",
|
||||||
URL: genURL("udp://localhost:9999"),
|
client: &httpClient{
|
||||||
},
|
pingTimeout: config.Duration(15 * time.Second),
|
||||||
},
|
readIdleTimeout: config.Duration(30 * time.Second),
|
||||||
{
|
|
||||||
cfg: &influxdb.HTTPConfig{
|
|
||||||
URL: genURL("unix://var/run/influxd.sock"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cfg: &influxdb.HTTPConfig{
|
|
||||||
URL: genURL("unix://var/run/influxd.sock"),
|
|
||||||
PingTimeout: config.Duration(15 * time.Second),
|
|
||||||
ReadIdleTimeout: config.Duration(30 * time.Second),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range tests {
|
for _, tt := range tests {
|
||||||
client, err := influxdb.NewHTTPClient(tests[i].cfg)
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
if !tests[i].err {
|
u, err := url.Parse(tt.addr)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
} else {
|
tt.client.url = u
|
||||||
|
|
||||||
|
require.NoError(t, tt.client.Init())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHTTPClientInitFail(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
addr string
|
||||||
|
client *httpClient
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "udp unsupported",
|
||||||
|
addr: "udp://localhost:9999",
|
||||||
|
client: &httpClient{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
u, err := url.Parse(tt.addr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
tt.client.url = u
|
||||||
|
|
||||||
|
require.Error(t, tt.client.Init())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMakeWriteURL(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
addr string
|
||||||
|
expected string
|
||||||
|
bucket string
|
||||||
|
org string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "http default",
|
||||||
|
addr: "http://localhost:9999",
|
||||||
|
expected: "http://localhost:9999/api/v2/write?bucket=telegraf0&org=influx0",
|
||||||
|
bucket: "telegraf0",
|
||||||
|
org: "influx0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "http with param",
|
||||||
|
addr: "http://localhost:9999?id=abc",
|
||||||
|
expected: "http://localhost:9999/api/v2/write?bucket=telegraf1&id=abc&org=influx1",
|
||||||
|
bucket: "telegraf1",
|
||||||
|
org: "influx1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unix socket default",
|
||||||
|
addr: "unix://var/run/influxd.sock",
|
||||||
|
expected: "http://127.0.0.1/api/v2/write?bucket=telegraf2&org=influx2",
|
||||||
|
bucket: "telegraf2",
|
||||||
|
org: "influx2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
u, err := url.Parse(tt.addr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
preppedURL, params, err := prepareWriteURL(*u, tt.org)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, tt.expected, makeWriteURL(*preppedURL, params, tt.bucket))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMakeWriteURLFail(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
addr string
|
||||||
|
expected string
|
||||||
|
bucket string
|
||||||
|
org string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "default values",
|
||||||
|
addr: "udp://localhost:9999",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
u, err := url.Parse(tt.addr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, _, err = prepareWriteURL(*u, tt.org)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
t.Log(err)
|
})
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
client.URL()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWrite(t *testing.T) {
|
func TestExponentialBackoffCalculation(t *testing.T) {
|
||||||
ts := httptest.NewServer(
|
c := &httpClient{}
|
||||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
tests := []struct {
|
||||||
switch r.URL.Path {
|
retryCount int
|
||||||
case "/api/v2/write":
|
expected time.Duration
|
||||||
err := r.ParseForm()
|
}{
|
||||||
require.NoError(t, err)
|
{retryCount: 0, expected: 0},
|
||||||
require.Equal(t, []string{"foobar"}, r.Form["bucket"])
|
{retryCount: 1, expected: 25 * time.Millisecond},
|
||||||
|
{retryCount: 5, expected: 625 * time.Millisecond},
|
||||||
body, err := io.ReadAll(r.Body)
|
{retryCount: 10, expected: 2500 * time.Millisecond},
|
||||||
require.NoError(t, err)
|
{retryCount: 30, expected: 22500 * time.Millisecond},
|
||||||
require.Contains(t, string(body), "cpu value=42.123")
|
{retryCount: 40, expected: 40 * time.Second},
|
||||||
|
{retryCount: 50, expected: 60 * time.Second}, // max hit
|
||||||
w.WriteHeader(http.StatusNoContent)
|
{retryCount: 100, expected: 60 * time.Second},
|
||||||
return
|
{retryCount: 1000, expected: 60 * time.Second},
|
||||||
default:
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
defer ts.Close()
|
|
||||||
|
|
||||||
addr := &url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
Host: ts.Listener.Addr().String(),
|
|
||||||
}
|
}
|
||||||
|
for _, test := range tests {
|
||||||
cfg := &influxdb.HTTPConfig{
|
t.Run(fmt.Sprintf("%d_retries", test.retryCount), func(t *testing.T) {
|
||||||
URL: addr,
|
c.retryCount = test.retryCount
|
||||||
Bucket: "telegraf",
|
require.EqualValues(t, test.expected, c.getRetryDuration(http.Header{}))
|
||||||
BucketTag: "bucket",
|
})
|
||||||
ExcludeBucketTag: true,
|
|
||||||
PingTimeout: config.Duration(15 * time.Second),
|
|
||||||
ReadIdleTimeout: config.Duration(30 * time.Second),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := influxdb.NewHTTPClient(cfg)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
metrics := []telegraf.Metric{
|
|
||||||
testutil.MustMetric(
|
|
||||||
"cpu",
|
|
||||||
map[string]string{
|
|
||||||
"bucket": "foobar",
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"value": 42.123,
|
|
||||||
},
|
|
||||||
time.Unix(0, 0),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
err = client.Write(ctx, metrics)
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = client.Write(ctx, metrics)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWriteBucketTagWorksOnRetry(t *testing.T) {
|
func TestExponentialBackoffCalculationWithRetryAfter(t *testing.T) {
|
||||||
ts := httptest.NewServer(
|
c := &httpClient{}
|
||||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
tests := []struct {
|
||||||
switch r.URL.Path {
|
retryCount int
|
||||||
case "/api/v2/write":
|
retryAfter string
|
||||||
err := r.ParseForm()
|
expected time.Duration
|
||||||
require.NoError(t, err)
|
}{
|
||||||
require.Equal(t, []string{"foo"}, r.Form["bucket"])
|
{retryCount: 0, retryAfter: "0", expected: 0},
|
||||||
|
{retryCount: 0, retryAfter: "10", expected: 10 * time.Second},
|
||||||
body, err := io.ReadAll(r.Body)
|
{retryCount: 0, retryAfter: "60", expected: 60 * time.Second},
|
||||||
require.NoError(t, err)
|
{retryCount: 0, retryAfter: "600", expected: 600 * time.Second},
|
||||||
require.Contains(t, string(body), "cpu value=42")
|
{retryCount: 0, retryAfter: "601", expected: 600 * time.Second}, // max hit
|
||||||
|
{retryCount: 40, retryAfter: "39", expected: 40 * time.Second}, // retryCount wins
|
||||||
w.WriteHeader(http.StatusNoContent)
|
{retryCount: 40, retryAfter: "41", expected: 41 * time.Second}, // retryAfter wins
|
||||||
return
|
{retryCount: 100, retryAfter: "100", expected: 100 * time.Second},
|
||||||
default:
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
defer ts.Close()
|
|
||||||
|
|
||||||
addr := &url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
Host: ts.Listener.Addr().String(),
|
|
||||||
}
|
}
|
||||||
|
for _, test := range tests {
|
||||||
cfg := &influxdb.HTTPConfig{
|
t.Run(fmt.Sprintf("%d_retries", test.retryCount), func(t *testing.T) {
|
||||||
URL: addr,
|
c.retryCount = test.retryCount
|
||||||
Bucket: "telegraf",
|
hdr := http.Header{}
|
||||||
BucketTag: "bucket",
|
hdr.Add("Retry-After", test.retryAfter)
|
||||||
ExcludeBucketTag: true,
|
require.EqualValues(t, test.expected, c.getRetryDuration(hdr))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := influxdb.NewHTTPClient(cfg)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
metrics := []telegraf.Metric{
|
|
||||||
testutil.MustMetric(
|
|
||||||
"cpu",
|
|
||||||
map[string]string{
|
|
||||||
"bucket": "foo",
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"value": 42.0,
|
|
||||||
},
|
|
||||||
time.Unix(0, 0),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
err = client.Write(ctx, metrics)
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = client.Write(ctx, metrics)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTooLargeWriteRetry(t *testing.T) {
|
// goos: linux
|
||||||
ts := httptest.NewServer(
|
// goarch: amd64
|
||||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
// pkg: github.com/influxdata/telegraf/plugins/outputs/influxdb_v2
|
||||||
switch r.URL.Path {
|
// cpu: 11th Gen Intel(R) Core(TM) i7-11850H @ 2.50GHz
|
||||||
case "/api/v2/write":
|
// BenchmarkOldMakeWriteURL
|
||||||
err := r.ParseForm()
|
// BenchmarkOldMakeWriteURL-16 1556631 683.2 ns/op 424 B/op 14 allocs/op
|
||||||
require.NoError(t, err)
|
// PASS
|
||||||
|
// ok github.com/influxdata/telegraf/plugins/outputs/influxdb_v2 1.851s
|
||||||
|
func BenchmarkOldMakeWriteURL(b *testing.B) {
|
||||||
|
org := "org"
|
||||||
|
|
||||||
body, err := io.ReadAll(r.Body)
|
u, err := url.Parse("http://localhost:8086")
|
||||||
require.NoError(t, err)
|
require.NoError(b, err)
|
||||||
|
loc, _, err := prepareWriteURL(*u, org)
|
||||||
|
require.NoError(b, err)
|
||||||
|
|
||||||
// Ensure metric body size is small
|
b.ReportAllocs()
|
||||||
if len(body) > 16 {
|
for n := 0; n < b.N; n++ {
|
||||||
w.WriteHeader(http.StatusRequestEntityTooLarge)
|
//nolint:errcheck // Skip error for benchmarking
|
||||||
} else {
|
oldMakeWriteURL(*loc)
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
defer ts.Close()
|
|
||||||
|
|
||||||
addr := &url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
Host: ts.Listener.Addr().String(),
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
cfg := &influxdb.HTTPConfig{
|
|
||||||
URL: addr,
|
// goos: linux
|
||||||
Bucket: "telegraf",
|
// goarch: amd64
|
||||||
BucketTag: "bucket",
|
// pkg: github.com/influxdata/telegraf/plugins/outputs/influxdb_v2
|
||||||
ExcludeBucketTag: true,
|
// cpu: 11th Gen Intel(R) Core(TM) i7-11850H @ 2.50GHz
|
||||||
Log: testutil.Logger{},
|
// BenchmarkNewMakeWriteURL
|
||||||
}
|
// BenchmarkNewMakeWriteURL-16 2084415 496.5 ns/op 280 B/op 9 allocs/op
|
||||||
|
// PASS
|
||||||
client, err := influxdb.NewHTTPClient(cfg)
|
// ok github.com/influxdata/telegraf/plugins/outputs/influxdb_v2 1.626s
|
||||||
require.NoError(t, err)
|
func BenchmarkNewMakeWriteURL(b *testing.B) {
|
||||||
|
bucket := "bkt"
|
||||||
// Together the metric batch size is too big, split up, we get success
|
org := "org"
|
||||||
metrics := []telegraf.Metric{
|
|
||||||
testutil.MustMetric(
|
u, err := url.Parse("http://localhost:8086")
|
||||||
"cpu",
|
require.NoError(b, err)
|
||||||
map[string]string{
|
loc, params, err := prepareWriteURL(*u, org)
|
||||||
"bucket": "foo",
|
require.NoError(b, err)
|
||||||
},
|
|
||||||
map[string]interface{}{
|
b.ReportAllocs()
|
||||||
"value": 42.0,
|
for n := 0; n < b.N; n++ {
|
||||||
},
|
makeWriteURL(*loc, params, bucket)
|
||||||
time.Unix(0, 0),
|
}
|
||||||
),
|
}
|
||||||
testutil.MustMetric(
|
|
||||||
"cpu",
|
func oldMakeWriteURL(loc url.URL) (string, error) {
|
||||||
map[string]string{
|
params := url.Values{}
|
||||||
"bucket": "bar",
|
params.Set("bucket", "bkt")
|
||||||
},
|
params.Set("org", "org")
|
||||||
map[string]interface{}{
|
|
||||||
"value": 99.0,
|
switch loc.Scheme {
|
||||||
},
|
case "unix":
|
||||||
time.Unix(0, 0),
|
loc.Scheme = "http"
|
||||||
),
|
loc.Host = "127.0.0.1"
|
||||||
}
|
loc.Path = "/api/v2/write"
|
||||||
|
case "http", "https":
|
||||||
ctx := context.Background()
|
loc.Path = path.Join(loc.Path, "/api/v2/write")
|
||||||
err = client.Write(ctx, metrics)
|
default:
|
||||||
require.NoError(t, err)
|
return "", fmt.Errorf("unsupported scheme: %q", loc.Scheme)
|
||||||
|
}
|
||||||
// These metrics are too big, even after splitting in half, expect error
|
loc.RawQuery = params.Encode()
|
||||||
hugeMetrics := []telegraf.Metric{
|
return loc.String(), nil
|
||||||
testutil.MustMetric(
|
|
||||||
"reallyLargeMetric",
|
|
||||||
map[string]string{
|
|
||||||
"bucket": "foobar",
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"value": 123.456,
|
|
||||||
},
|
|
||||||
time.Unix(0, 0),
|
|
||||||
),
|
|
||||||
testutil.MustMetric(
|
|
||||||
"evenBiggerMetric",
|
|
||||||
map[string]string{
|
|
||||||
"bucket": "fizzbuzzbang",
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"value": 999.999,
|
|
||||||
},
|
|
||||||
time.Unix(0, 0),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
err = client.Write(ctx, hugeMetrics)
|
|
||||||
require.Error(t, err)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package influxdb_v2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -15,7 +16,8 @@ import (
|
||||||
|
|
||||||
"github.com/influxdata/telegraf"
|
"github.com/influxdata/telegraf"
|
||||||
"github.com/influxdata/telegraf/config"
|
"github.com/influxdata/telegraf/config"
|
||||||
"github.com/influxdata/telegraf/plugins/common/tls"
|
"github.com/influxdata/telegraf/internal"
|
||||||
|
commontls "github.com/influxdata/telegraf/plugins/common/tls"
|
||||||
"github.com/influxdata/telegraf/plugins/outputs"
|
"github.com/influxdata/telegraf/plugins/outputs"
|
||||||
"github.com/influxdata/telegraf/plugins/serializers/influx"
|
"github.com/influxdata/telegraf/plugins/serializers/influx"
|
||||||
)
|
)
|
||||||
|
|
@ -23,19 +25,6 @@ import (
|
||||||
//go:embed sample.conf
|
//go:embed sample.conf
|
||||||
var sampleConfig string
|
var sampleConfig string
|
||||||
|
|
||||||
var (
|
|
||||||
defaultURL = "http://localhost:8086"
|
|
||||||
|
|
||||||
ErrMissingURL = errors.New("missing URL")
|
|
||||||
)
|
|
||||||
|
|
||||||
type Client interface {
|
|
||||||
Write(context.Context, []telegraf.Metric) error
|
|
||||||
|
|
||||||
URL() string // for logging
|
|
||||||
Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
type InfluxDB struct {
|
type InfluxDB struct {
|
||||||
URLs []string `toml:"urls"`
|
URLs []string `toml:"urls"`
|
||||||
LocalAddr string `toml:"local_address"`
|
LocalAddr string `toml:"local_address"`
|
||||||
|
|
@ -53,22 +42,63 @@ type InfluxDB struct {
|
||||||
OmitTimestamp bool `toml:"influx_omit_timestamp"`
|
OmitTimestamp bool `toml:"influx_omit_timestamp"`
|
||||||
PingTimeout config.Duration `toml:"ping_timeout"`
|
PingTimeout config.Duration `toml:"ping_timeout"`
|
||||||
ReadIdleTimeout config.Duration `toml:"read_idle_timeout"`
|
ReadIdleTimeout config.Duration `toml:"read_idle_timeout"`
|
||||||
tls.ClientConfig
|
Log telegraf.Logger `toml:"-"`
|
||||||
|
commontls.ClientConfig
|
||||||
|
|
||||||
Log telegraf.Logger `toml:"-"`
|
clients []*httpClient
|
||||||
|
encoder internal.ContentEncoder
|
||||||
clients []Client
|
serializer *influx.Serializer
|
||||||
|
tlsCfg *tls.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*InfluxDB) SampleConfig() string {
|
func (*InfluxDB) SampleConfig() string {
|
||||||
return sampleConfig
|
return sampleConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *InfluxDB) Connect() error {
|
func (i *InfluxDB) Init() error {
|
||||||
if len(i.URLs) == 0 {
|
// Set defaults
|
||||||
i.URLs = append(i.URLs, defaultURL)
|
if i.UserAgent == "" {
|
||||||
|
i.UserAgent = internal.ProductToken()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(i.URLs) == 0 {
|
||||||
|
i.URLs = append(i.URLs, "http://localhost:8086")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check options
|
||||||
|
switch i.ContentEncoding {
|
||||||
|
case "", "gzip":
|
||||||
|
i.ContentEncoding = "gzip"
|
||||||
|
enc, err := internal.NewGzipEncoder()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("setting up gzip encoder failed: %w", err)
|
||||||
|
}
|
||||||
|
i.encoder = enc
|
||||||
|
case "identity":
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid content encoding %q", i.ContentEncoding)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the limited serializer
|
||||||
|
i.serializer = &influx.Serializer{
|
||||||
|
UintSupport: i.UintSupport,
|
||||||
|
OmitTimestamp: i.OmitTimestamp,
|
||||||
|
}
|
||||||
|
if err := i.serializer.Init(); err != nil {
|
||||||
|
return fmt.Errorf("setting up serializer failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the client config
|
||||||
|
tlsCfg, err := i.ClientConfig.TLSConfig()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("setting up TLS failed: %w", err)
|
||||||
|
}
|
||||||
|
i.tlsCfg = tlsCfg
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *InfluxDB) Connect() error {
|
||||||
for _, u := range i.URLs {
|
for _, u := range i.URLs {
|
||||||
parts, err := url.Parse(u)
|
parts, err := url.Parse(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -112,9 +142,29 @@ func (i *InfluxDB) Connect() error {
|
||||||
|
|
||||||
switch parts.Scheme {
|
switch parts.Scheme {
|
||||||
case "http", "https", "unix":
|
case "http", "https", "unix":
|
||||||
c, err := i.getHTTPClient(parts, localAddr, proxy)
|
c := &httpClient{
|
||||||
if err != nil {
|
url: parts,
|
||||||
return err
|
localAddr: localAddr,
|
||||||
|
token: i.Token,
|
||||||
|
organization: i.Organization,
|
||||||
|
bucket: i.Bucket,
|
||||||
|
bucketTag: i.BucketTag,
|
||||||
|
excludeBucketTag: i.ExcludeBucketTag,
|
||||||
|
timeout: time.Duration(i.Timeout),
|
||||||
|
headers: i.HTTPHeaders,
|
||||||
|
proxy: proxy,
|
||||||
|
userAgent: i.UserAgent,
|
||||||
|
contentEncoding: i.ContentEncoding,
|
||||||
|
tlsConfig: i.tlsCfg,
|
||||||
|
pingTimeout: i.PingTimeout,
|
||||||
|
readIdleTimeout: i.ReadIdleTimeout,
|
||||||
|
serializer: i.serializer,
|
||||||
|
encoder: i.encoder,
|
||||||
|
log: i.Log,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.Init(); err != nil {
|
||||||
|
return fmt.Errorf("error creating HTTP client [%s]: %w", parts, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
i.clients = append(i.clients, c)
|
i.clients = append(i.clients, c)
|
||||||
|
|
@ -138,68 +188,22 @@ func (i *InfluxDB) Close() error {
|
||||||
func (i *InfluxDB) Write(metrics []telegraf.Metric) error {
|
func (i *InfluxDB) Write(metrics []telegraf.Metric) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
var err error
|
for _, n := range rand.Perm(len(i.clients)) {
|
||||||
p := rand.Perm(len(i.clients))
|
|
||||||
for _, n := range p {
|
|
||||||
client := i.clients[n]
|
client := i.clients[n]
|
||||||
err = client.Write(ctx, metrics)
|
if err := client.Write(ctx, metrics); err != nil {
|
||||||
if err == nil {
|
i.Log.Errorf("When writing to [%s]: %v", client.url, err)
|
||||||
return nil
|
continue
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
i.Log.Errorf("When writing to [%s]: %v", client.URL(), err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.New("failed to send metrics to any configured server(s)")
|
return errors.New("failed to send metrics to any configured server(s)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *InfluxDB) getHTTPClient(address *url.URL, localAddr *net.TCPAddr, proxy *url.URL) (Client, error) {
|
|
||||||
tlsConfig, err := i.ClientConfig.TLSConfig()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
serializer := &influx.Serializer{
|
|
||||||
UintSupport: i.UintSupport,
|
|
||||||
OmitTimestamp: i.OmitTimestamp,
|
|
||||||
}
|
|
||||||
if err := serializer.Init(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
httpConfig := &HTTPConfig{
|
|
||||||
URL: address,
|
|
||||||
LocalAddr: localAddr,
|
|
||||||
Token: i.Token,
|
|
||||||
Organization: i.Organization,
|
|
||||||
Bucket: i.Bucket,
|
|
||||||
BucketTag: i.BucketTag,
|
|
||||||
ExcludeBucketTag: i.ExcludeBucketTag,
|
|
||||||
Timeout: time.Duration(i.Timeout),
|
|
||||||
Headers: i.HTTPHeaders,
|
|
||||||
Proxy: proxy,
|
|
||||||
UserAgent: i.UserAgent,
|
|
||||||
ContentEncoding: i.ContentEncoding,
|
|
||||||
TLSConfig: tlsConfig,
|
|
||||||
Serializer: serializer,
|
|
||||||
PingTimeout: i.PingTimeout,
|
|
||||||
ReadIdleTimeout: i.ReadIdleTimeout,
|
|
||||||
Log: i.Log,
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := NewHTTPClient(httpConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error creating HTTP client [%s]: %w", address, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
outputs.Add("influxdb_v2", func() telegraf.Output {
|
outputs.Add("influxdb_v2", func() telegraf.Output {
|
||||||
return &InfluxDB{
|
return &InfluxDB{
|
||||||
Timeout: config.Duration(time.Second * 5),
|
Timeout: config.Duration(time.Second * 5),
|
||||||
ContentEncoding: "gzip",
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,107 +1,126 @@
|
||||||
package influxdb_v2_test
|
package influxdb_v2_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf"
|
||||||
|
"github.com/influxdata/telegraf/config"
|
||||||
|
"github.com/influxdata/telegraf/metric"
|
||||||
"github.com/influxdata/telegraf/plugins/common/tls"
|
"github.com/influxdata/telegraf/plugins/common/tls"
|
||||||
"github.com/influxdata/telegraf/plugins/outputs"
|
"github.com/influxdata/telegraf/plugins/outputs"
|
||||||
influxdb "github.com/influxdata/telegraf/plugins/outputs/influxdb_v2"
|
influxdb "github.com/influxdata/telegraf/plugins/outputs/influxdb_v2"
|
||||||
|
"github.com/influxdata/telegraf/testutil"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestSampleConfig(t *testing.T) {
|
||||||
|
plugin := influxdb.InfluxDB{}
|
||||||
|
require.NotEmpty(t, plugin.SampleConfig())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPluginRegistered(t *testing.T) {
|
||||||
|
require.Contains(t, outputs.Outputs, "influxdb_v2")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCloseWithoutConnect(t *testing.T) {
|
||||||
|
plugin := influxdb.InfluxDB{}
|
||||||
|
require.NoError(t, plugin.Close())
|
||||||
|
}
|
||||||
|
|
||||||
func TestDefaultURL(t *testing.T) {
|
func TestDefaultURL(t *testing.T) {
|
||||||
output := influxdb.InfluxDB{}
|
plugin := influxdb.InfluxDB{}
|
||||||
err := output.Connect()
|
require.NoError(t, plugin.Init())
|
||||||
require.NoError(t, err)
|
require.Len(t, plugin.URLs, 1)
|
||||||
if len(output.URLs) < 1 {
|
require.Equal(t, "http://localhost:8086", plugin.URLs[0])
|
||||||
t.Fatal("Default URL failed to get set")
|
|
||||||
}
|
|
||||||
require.Equal(t, "http://localhost:8086", output.URLs[0])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInit(t *testing.T) {
|
||||||
|
tests := []*influxdb.InfluxDB{
|
||||||
|
{
|
||||||
|
URLs: []string{"https://localhost:8080"},
|
||||||
|
ClientConfig: tls.ClientConfig{
|
||||||
|
TLSCA: "thing",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, plugin := range tests {
|
||||||
|
t.Run(plugin.URLs[0], func(t *testing.T) {
|
||||||
|
require.Error(t, plugin.Init())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConnectFail(t *testing.T) {
|
||||||
|
tests := []*influxdb.InfluxDB{
|
||||||
|
{
|
||||||
|
URLs: []string{"!@#$qwert"},
|
||||||
|
HTTPProxy: "http://localhost:8086",
|
||||||
|
HTTPHeaders: map[string]string{
|
||||||
|
"x": "y",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
URLs: []string{"http://localhost:1234"},
|
||||||
|
HTTPProxy: "!@#$%^&*()_+",
|
||||||
|
HTTPHeaders: map[string]string{
|
||||||
|
"x": "y",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
URLs: []string{"!@#$%^&*()_+"},
|
||||||
|
HTTPProxy: "http://localhost:8086",
|
||||||
|
HTTPHeaders: map[string]string{
|
||||||
|
"x": "y",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
URLs: []string{":::@#$qwert"},
|
||||||
|
HTTPProxy: "http://localhost:8086",
|
||||||
|
HTTPHeaders: map[string]string{
|
||||||
|
"x": "y",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, plugin := range tests {
|
||||||
|
t.Run(plugin.URLs[0], func(t *testing.T) {
|
||||||
|
require.NoError(t, plugin.Init())
|
||||||
|
require.Error(t, plugin.Connect())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestConnect(t *testing.T) {
|
func TestConnect(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []*influxdb.InfluxDB{
|
||||||
err bool
|
|
||||||
out influxdb.InfluxDB
|
|
||||||
}{
|
|
||||||
{
|
{
|
||||||
out: influxdb.InfluxDB{
|
URLs: []string{"http://localhost:1234"},
|
||||||
URLs: []string{"http://localhost:1234"},
|
HTTPProxy: "http://localhost:8086",
|
||||||
HTTPProxy: "http://localhost:8086",
|
HTTPHeaders: map[string]string{
|
||||||
HTTPHeaders: map[string]string{
|
"x": "y",
|
||||||
"x": "y",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
err: true,
|
|
||||||
out: influxdb.InfluxDB{
|
|
||||||
URLs: []string{"!@#$qwert"},
|
|
||||||
HTTPProxy: "http://localhost:8086",
|
|
||||||
HTTPHeaders: map[string]string{
|
|
||||||
"x": "y",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
err: true,
|
|
||||||
out: influxdb.InfluxDB{
|
|
||||||
URLs: []string{"http://localhost:1234"},
|
|
||||||
HTTPProxy: "!@#$%^&*()_+",
|
|
||||||
HTTPHeaders: map[string]string{
|
|
||||||
"x": "y",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
err: true,
|
|
||||||
out: influxdb.InfluxDB{
|
|
||||||
URLs: []string{"!@#$%^&*()_+"},
|
|
||||||
HTTPProxy: "http://localhost:8086",
|
|
||||||
HTTPHeaders: map[string]string{
|
|
||||||
"x": "y",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
err: true,
|
|
||||||
out: influxdb.InfluxDB{
|
|
||||||
URLs: []string{":::@#$qwert"},
|
|
||||||
HTTPProxy: "http://localhost:8086",
|
|
||||||
HTTPHeaders: map[string]string{
|
|
||||||
"x": "y",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
err: true,
|
|
||||||
out: influxdb.InfluxDB{
|
|
||||||
URLs: []string{"https://localhost:8080"},
|
|
||||||
ClientConfig: tls.ClientConfig{
|
|
||||||
TLSCA: "thing",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range tests {
|
for _, plugin := range tests {
|
||||||
err := tests[i].out.Connect()
|
t.Run(plugin.URLs[0], func(t *testing.T) {
|
||||||
if !tests[i].err {
|
require.NoError(t, plugin.Init())
|
||||||
require.NoError(t, err)
|
require.NoError(t, plugin.Connect())
|
||||||
} else {
|
})
|
||||||
require.Error(t, err)
|
|
||||||
t.Log(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnused(_ *testing.T) {
|
|
||||||
thing := influxdb.InfluxDB{}
|
|
||||||
thing.Close()
|
|
||||||
thing.SampleConfig()
|
|
||||||
outputs.Outputs["influxdb_v2"]()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInfluxDBLocalAddress(t *testing.T) {
|
func TestInfluxDBLocalAddress(t *testing.T) {
|
||||||
t.Log("Starting server")
|
t.Log("Starting server")
|
||||||
server, err := net.Listen("tcp", "127.0.0.1:0")
|
server, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
|
@ -112,3 +131,202 @@ func TestInfluxDBLocalAddress(t *testing.T) {
|
||||||
require.NoError(t, output.Connect())
|
require.NoError(t, output.Connect())
|
||||||
require.NoError(t, output.Close())
|
require.NoError(t, output.Close())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWrite(t *testing.T) {
|
||||||
|
// Setup a test server
|
||||||
|
ts := httptest.NewServer(
|
||||||
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.URL.Path {
|
||||||
|
case "/api/v2/write":
|
||||||
|
require.NoError(t, r.ParseForm())
|
||||||
|
require.Equal(t, []string{"foobar"}, r.Form["bucket"])
|
||||||
|
|
||||||
|
body, err := io.ReadAll(r.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Contains(t, string(body), "cpu value=42.123")
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
// Setup plugin and connect
|
||||||
|
plugin := &influxdb.InfluxDB{
|
||||||
|
URLs: []string{"http://" + ts.Listener.Addr().String()},
|
||||||
|
Bucket: "telegraf",
|
||||||
|
BucketTag: "bucket",
|
||||||
|
ExcludeBucketTag: true,
|
||||||
|
ContentEncoding: "identity",
|
||||||
|
PingTimeout: config.Duration(15 * time.Second),
|
||||||
|
ReadIdleTimeout: config.Duration(30 * time.Second),
|
||||||
|
Log: &testutil.Logger{},
|
||||||
|
}
|
||||||
|
require.NoError(t, plugin.Init())
|
||||||
|
require.NoError(t, plugin.Connect())
|
||||||
|
defer plugin.Close()
|
||||||
|
|
||||||
|
// Test writing
|
||||||
|
metrics := []telegraf.Metric{
|
||||||
|
testutil.MustMetric(
|
||||||
|
"cpu",
|
||||||
|
map[string]string{
|
||||||
|
"bucket": "foobar",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"value": 42.123,
|
||||||
|
},
|
||||||
|
time.Unix(0, 0),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
require.NoError(t, plugin.Write(metrics))
|
||||||
|
require.NoError(t, plugin.Write(metrics))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriteBucketTagWorksOnRetry(t *testing.T) {
|
||||||
|
// Setup a test server
|
||||||
|
ts := httptest.NewServer(
|
||||||
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.URL.Path {
|
||||||
|
case "/api/v2/write":
|
||||||
|
require.NoError(t, r.ParseForm())
|
||||||
|
require.Equal(t, []string{"foo"}, r.Form["bucket"])
|
||||||
|
|
||||||
|
body, err := io.ReadAll(r.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Contains(t, string(body), "cpu value=42")
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
// Setup plugin and connect
|
||||||
|
plugin := &influxdb.InfluxDB{
|
||||||
|
URLs: []string{"http://" + ts.Listener.Addr().String()},
|
||||||
|
Bucket: "telegraf",
|
||||||
|
BucketTag: "bucket",
|
||||||
|
ExcludeBucketTag: true,
|
||||||
|
ContentEncoding: "identity",
|
||||||
|
Log: &testutil.Logger{},
|
||||||
|
}
|
||||||
|
require.NoError(t, plugin.Init())
|
||||||
|
require.NoError(t, plugin.Connect())
|
||||||
|
defer plugin.Close()
|
||||||
|
|
||||||
|
// Send the metrics which should be succeed if sent twice
|
||||||
|
metrics := []telegraf.Metric{
|
||||||
|
testutil.MustMetric(
|
||||||
|
"cpu",
|
||||||
|
map[string]string{
|
||||||
|
"bucket": "foo",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"value": 42.0,
|
||||||
|
},
|
||||||
|
time.Unix(0, 0),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
require.NoError(t, plugin.Write(metrics))
|
||||||
|
require.NoError(t, plugin.Write(metrics))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTooLargeWriteRetry(t *testing.T) {
|
||||||
|
// Setup a test server
|
||||||
|
ts := httptest.NewServer(
|
||||||
|
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.URL.Path {
|
||||||
|
case "/api/v2/write":
|
||||||
|
require.NoError(t, r.ParseForm())
|
||||||
|
|
||||||
|
body, err := io.ReadAll(r.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Ensure metric body size is small
|
||||||
|
if len(body) > 16 {
|
||||||
|
w.WriteHeader(http.StatusRequestEntityTooLarge)
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
// Setup plugin and connect
|
||||||
|
plugin := &influxdb.InfluxDB{
|
||||||
|
URLs: []string{"http://" + ts.Listener.Addr().String()},
|
||||||
|
Bucket: "telegraf",
|
||||||
|
BucketTag: "bucket",
|
||||||
|
ExcludeBucketTag: true,
|
||||||
|
ContentEncoding: "identity",
|
||||||
|
Log: &testutil.Logger{},
|
||||||
|
}
|
||||||
|
require.NoError(t, plugin.Init())
|
||||||
|
require.NoError(t, plugin.Connect())
|
||||||
|
defer plugin.Close()
|
||||||
|
|
||||||
|
// Together the metric batch size is too big, split up, we get success
|
||||||
|
metrics := []telegraf.Metric{
|
||||||
|
metric.New(
|
||||||
|
"cpu",
|
||||||
|
map[string]string{
|
||||||
|
"bucket": "foo",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"value": 42.0,
|
||||||
|
},
|
||||||
|
time.Unix(0, 0),
|
||||||
|
),
|
||||||
|
metric.New(
|
||||||
|
"cpu",
|
||||||
|
map[string]string{
|
||||||
|
"bucket": "bar",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"value": 99.0,
|
||||||
|
},
|
||||||
|
time.Unix(0, 0),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
require.NoError(t, plugin.Write(metrics))
|
||||||
|
|
||||||
|
// These metrics are too big, even after splitting in half, expect error
|
||||||
|
hugeMetrics := []telegraf.Metric{
|
||||||
|
metric.New(
|
||||||
|
"reallyLargeMetric",
|
||||||
|
map[string]string{
|
||||||
|
"bucket": "foobar",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"value": 123.456,
|
||||||
|
},
|
||||||
|
time.Unix(0, 0),
|
||||||
|
),
|
||||||
|
metric.New(
|
||||||
|
"evenBiggerMetric",
|
||||||
|
map[string]string{
|
||||||
|
"bucket": "fizzbuzzbang",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"value": 999.999,
|
||||||
|
},
|
||||||
|
time.Unix(0, 0),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
require.Error(t, plugin.Write(hugeMetrics))
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue