Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
e393c3af3f
commit
4978089aab
4963 changed files with 677545 additions and 0 deletions
167
plugins/outputs/http/README.md
Normal file
167
plugins/outputs/http/README.md
Normal file
|
@ -0,0 +1,167 @@
|
|||
# HTTP Output Plugin
|
||||
|
||||
This plugin writes metrics to a HTTP endpoint using one of the supported
|
||||
[data formats][data_formats]. For data formats supporting batching, metrics are
|
||||
sent in batches by default.
|
||||
|
||||
⭐ Telegraf v1.7.0
|
||||
🏷️ applications
|
||||
💻 all
|
||||
|
||||
[data_formats]: /docs/DATA_FORMATS_OUTPUT.md
|
||||
|
||||
## Global configuration options <!-- @/docs/includes/plugin_config.md -->
|
||||
|
||||
In addition to the plugin-specific configuration settings, plugins support
|
||||
additional global and plugin configuration settings. These settings are used to
|
||||
modify metrics, tags, and field or create aliases and configure ordering, etc.
|
||||
See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
|
||||
|
||||
[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins
|
||||
|
||||
## Secret-store support
|
||||
|
||||
This plugin supports secrets from secret-stores for the `username`, `password`
|
||||
`headers`, and `cookie_auth_headers` option.
|
||||
See the [secret-store documentation][SECRETSTORE] for more details on how
|
||||
to use them.
|
||||
|
||||
[SECRETSTORE]: ../../../docs/CONFIGURATION.md#secret-store-secrets
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml @sample.conf
|
||||
# A plugin that can transmit metrics over HTTP
|
||||
[[outputs.http]]
|
||||
## URL is the address to send metrics to
|
||||
url = "http://127.0.0.1:8080/telegraf"
|
||||
|
||||
## Timeout for HTTP message
|
||||
# timeout = "5s"
|
||||
|
||||
## HTTP method, one of: "POST" or "PUT" or "PATCH"
|
||||
# method = "POST"
|
||||
|
||||
## HTTP Basic Auth credentials
|
||||
# username = "username"
|
||||
# password = "pa$$word"
|
||||
|
||||
## OAuth2 Client Credentials Grant
|
||||
# client_id = "clientid"
|
||||
# client_secret = "secret"
|
||||
# token_url = "https://indentityprovider/oauth2/v1/token"
|
||||
# audience = ""
|
||||
# scopes = ["urn:opc:idm:__myscopes__"]
|
||||
|
||||
## Goole API Auth
|
||||
# google_application_credentials = "/etc/telegraf/example_secret.json"
|
||||
|
||||
## HTTP Proxy support
|
||||
# use_system_proxy = false
|
||||
# http_proxy_url = ""
|
||||
|
||||
## Optional TLS Config
|
||||
# tls_ca = "/etc/telegraf/ca.pem"
|
||||
# tls_cert = "/etc/telegraf/cert.pem"
|
||||
# tls_key = "/etc/telegraf/key.pem"
|
||||
## Use TLS but skip chain & host verification
|
||||
# insecure_skip_verify = false
|
||||
|
||||
## Optional Cookie authentication
|
||||
# cookie_auth_url = "https://localhost/authMe"
|
||||
# cookie_auth_method = "POST"
|
||||
# cookie_auth_username = "username"
|
||||
# cookie_auth_password = "pa$$word"
|
||||
# cookie_auth_headers = '{"Content-Type": "application/json", "X-MY-HEADER":"hello"}'
|
||||
# cookie_auth_body = '{"username": "user", "password": "pa$$word", "authenticate": "me"}'
|
||||
## cookie_auth_renewal not set or set to "0" will auth once and never renew the cookie
|
||||
# cookie_auth_renewal = "5m"
|
||||
|
||||
## Data format to output.
|
||||
## Each data format has it's own unique set of configuration options, read
|
||||
## more about them here:
|
||||
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md
|
||||
# data_format = "influx"
|
||||
|
||||
## Use batch serialization format (default) instead of line based format.
|
||||
## Batch format is more efficient and should be used unless line based
|
||||
## format is really needed.
|
||||
# use_batch_format = true
|
||||
|
||||
## HTTP Content-Encoding for write request body, can be set to "gzip" to
|
||||
## compress body or "identity" to apply no encoding.
|
||||
# content_encoding = "identity"
|
||||
|
||||
## MaxIdleConns controls the maximum number of idle (keep-alive)
|
||||
## connections across all hosts. Zero means no limit.
|
||||
# max_idle_conn = 0
|
||||
|
||||
## MaxIdleConnsPerHost, if non-zero, controls the maximum idle
|
||||
## (keep-alive) connections to keep per-host. If zero,
|
||||
## DefaultMaxIdleConnsPerHost is used(2).
|
||||
# max_idle_conn_per_host = 2
|
||||
|
||||
## Idle (keep-alive) connection timeout.
|
||||
## Maximum amount of time before idle connection is closed.
|
||||
## Zero means no limit.
|
||||
# idle_conn_timeout = 0
|
||||
|
||||
## Amazon Region
|
||||
#region = "us-east-1"
|
||||
|
||||
## Amazon Credentials
|
||||
## Amazon Credentials are not built unless the following aws_service
|
||||
## setting is set to a non-empty string. It may need to match the name of
|
||||
## the service output to as well
|
||||
#aws_service = "execute-api"
|
||||
|
||||
## Credentials are loaded in the following order
|
||||
## 1) Web identity provider credentials via STS if role_arn and web_identity_token_file are specified
|
||||
## 2) Assumed credentials via STS if role_arn is specified
|
||||
## 3) explicit credentials from 'access_key' and 'secret_key'
|
||||
## 4) shared profile from 'profile'
|
||||
## 5) environment variables
|
||||
## 6) shared credentials file
|
||||
## 7) EC2 Instance Profile
|
||||
#access_key = ""
|
||||
#secret_key = ""
|
||||
#token = ""
|
||||
#role_arn = ""
|
||||
#web_identity_token_file = ""
|
||||
#role_session_name = ""
|
||||
#profile = ""
|
||||
#shared_credential_file = ""
|
||||
|
||||
## Optional list of statuscodes (<200 or >300) upon which requests should not be retried
|
||||
# non_retryable_statuscodes = [409, 413]
|
||||
|
||||
## NOTE: Due to the way TOML is parsed, tables must be at the END of the
|
||||
## plugin definition, otherwise additional config options are read as part of
|
||||
## the table
|
||||
|
||||
## Additional HTTP headers
|
||||
# [outputs.http.headers]
|
||||
# ## Should be set manually to "application/json" for json data_format
|
||||
# Content-Type = "text/plain; charset=utf-8"
|
||||
```
|
||||
|
||||
### Google API Auth
|
||||
|
||||
The `google_application_credentials` setting is used with Google Cloud APIs.
|
||||
It specifies the json key file. To learn about creating Google service accounts,
|
||||
consult Google's [oauth2 service account documentation][create_service_account].
|
||||
An example use case is a metrics proxy deployed to Cloud Run. In this example,
|
||||
the service account must have the "run.routes.invoke" permission.
|
||||
|
||||
[create_service_account]: https://cloud.google.com/docs/authentication/production#create_service_account
|
||||
|
||||
### Optional Cookie Authentication Settings
|
||||
|
||||
The optional Cookie Authentication Settings will retrieve a cookie from the
|
||||
given authorization endpoint, and use it in subsequent API requests. This is
|
||||
useful for services that do not provide OAuth or Basic Auth authentication,
|
||||
e.g. the [Tesla Powerwall API][powerwall], which uses a Cookie Auth Body to
|
||||
retrieve an authorization cookie. The Cookie Auth Renewal interval will renew
|
||||
the authorization by retrieving a new cookie at the given interval.
|
||||
|
||||
[powerwall]: https://www.tesla.com/support/energy/powerwall/own/monitoring-from-home-network
|
285
plugins/outputs/http/http.go
Normal file
285
plugins/outputs/http/http.go
Normal file
|
@ -0,0 +1,285 @@
|
|||
//go:generate ../../../tools/readme_config_includer/generator
|
||||
package http
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
_ "embed"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
aws_signer "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
|
||||
"golang.org/x/oauth2"
|
||||
"google.golang.org/api/idtoken"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/config"
|
||||
"github.com/influxdata/telegraf/internal"
|
||||
common_aws "github.com/influxdata/telegraf/plugins/common/aws"
|
||||
common_http "github.com/influxdata/telegraf/plugins/common/http"
|
||||
"github.com/influxdata/telegraf/plugins/outputs"
|
||||
)
|
||||
|
||||
//go:embed sample.conf
|
||||
var sampleConfig string
|
||||
|
||||
const (
|
||||
maxErrMsgLen = 1024
|
||||
defaultURL = "http://127.0.0.1:8080/telegraf"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultContentType = "text/plain; charset=utf-8"
|
||||
defaultMethod = http.MethodPost
|
||||
defaultUseBatchFormat = true
|
||||
)
|
||||
|
||||
type HTTP struct {
|
||||
URL string `toml:"url"`
|
||||
Method string `toml:"method"`
|
||||
Username config.Secret `toml:"username"`
|
||||
Password config.Secret `toml:"password"`
|
||||
Headers map[string]*config.Secret `toml:"headers"`
|
||||
ContentEncoding string `toml:"content_encoding"`
|
||||
UseBatchFormat bool `toml:"use_batch_format"`
|
||||
AwsService string `toml:"aws_service"`
|
||||
NonRetryableStatusCodes []int `toml:"non_retryable_statuscodes"`
|
||||
common_http.HTTPClientConfig
|
||||
Log telegraf.Logger `toml:"-"`
|
||||
|
||||
client *http.Client
|
||||
serializer telegraf.Serializer
|
||||
|
||||
awsCfg *aws.Config
|
||||
common_aws.CredentialConfig
|
||||
|
||||
// Google API Auth
|
||||
CredentialsFile string `toml:"google_application_credentials"`
|
||||
oauth2Token *oauth2.Token
|
||||
}
|
||||
|
||||
func (*HTTP) SampleConfig() string {
|
||||
return sampleConfig
|
||||
}
|
||||
|
||||
func (h *HTTP) SetSerializer(serializer telegraf.Serializer) {
|
||||
h.serializer = serializer
|
||||
}
|
||||
|
||||
func (h *HTTP) Connect() error {
|
||||
if h.AwsService != "" {
|
||||
cfg, err := h.CredentialConfig.Credentials()
|
||||
if err == nil {
|
||||
h.awsCfg = &cfg
|
||||
}
|
||||
}
|
||||
|
||||
if h.Method == "" {
|
||||
h.Method = http.MethodPost
|
||||
}
|
||||
h.Method = strings.ToUpper(h.Method)
|
||||
if h.Method != http.MethodPost && h.Method != http.MethodPut && h.Method != http.MethodPatch {
|
||||
return fmt.Errorf("invalid method [%s] %s", h.URL, h.Method)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
client, err := h.HTTPClientConfig.CreateClient(ctx, h.Log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h.client = client
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HTTP) Close() error {
|
||||
if h.client != nil {
|
||||
h.client.CloseIdleConnections()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HTTP) Write(metrics []telegraf.Metric) error {
|
||||
if h.UseBatchFormat {
|
||||
reqBody, err := h.serializer.SerializeBatch(metrics)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return h.writeMetric(reqBody)
|
||||
}
|
||||
|
||||
for _, metric := range metrics {
|
||||
reqBody, err := h.serializer.Serialize(metric)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := h.writeMetric(reqBody); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HTTP) writeMetric(reqBody []byte) error {
|
||||
var reqBodyBuffer io.Reader = bytes.NewBuffer(reqBody)
|
||||
|
||||
var err error
|
||||
if h.ContentEncoding == "gzip" {
|
||||
rc := internal.CompressWithGzip(reqBodyBuffer)
|
||||
defer rc.Close()
|
||||
reqBodyBuffer = rc
|
||||
}
|
||||
|
||||
var payloadHash *string
|
||||
if h.awsCfg != nil {
|
||||
// We need a local copy of the full buffer, the signature scheme requires a sha256 of the request body.
|
||||
buf := new(bytes.Buffer)
|
||||
_, err = io.Copy(buf, reqBodyBuffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sum := sha256.Sum256(buf.Bytes())
|
||||
reqBodyBuffer = buf
|
||||
|
||||
// sha256 is hex encoded
|
||||
hash := hex.EncodeToString(sum[:])
|
||||
payloadHash = &hash
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(h.Method, h.URL, reqBodyBuffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if h.awsCfg != nil {
|
||||
signer := aws_signer.NewSigner()
|
||||
ctx := context.Background()
|
||||
|
||||
credentials, err := h.awsCfg.Credentials.Retrieve(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = signer.SignHTTP(ctx, credentials, req, *payloadHash, h.AwsService, h.Region, time.Now().UTC())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !h.Username.Empty() || !h.Password.Empty() {
|
||||
username, err := h.Username.Get()
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting username failed: %w", err)
|
||||
}
|
||||
password, err := h.Password.Get()
|
||||
if err != nil {
|
||||
username.Destroy()
|
||||
return fmt.Errorf("getting password failed: %w", err)
|
||||
}
|
||||
req.SetBasicAuth(username.String(), password.String())
|
||||
username.Destroy()
|
||||
password.Destroy()
|
||||
}
|
||||
|
||||
// google api auth
|
||||
if h.CredentialsFile != "" {
|
||||
token, err := h.getAccessToken(context.Background(), h.URL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
token.SetAuthHeader(req)
|
||||
}
|
||||
|
||||
req.Header.Set("User-Agent", internal.ProductToken())
|
||||
req.Header.Set("Content-Type", defaultContentType)
|
||||
if h.ContentEncoding == "gzip" {
|
||||
req.Header.Set("Content-Encoding", "gzip")
|
||||
}
|
||||
|
||||
for k, v := range h.Headers {
|
||||
secret, err := v.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
headerVal := secret.String()
|
||||
if strings.EqualFold(k, "host") {
|
||||
req.Host = headerVal
|
||||
}
|
||||
req.Header.Set(k, headerVal)
|
||||
|
||||
secret.Destroy()
|
||||
}
|
||||
|
||||
resp, err := h.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
for _, nonRetryableStatusCode := range h.NonRetryableStatusCodes {
|
||||
if resp.StatusCode == nonRetryableStatusCode {
|
||||
h.Log.Errorf("Received non-retryable status %v. Metrics are lost.", resp.StatusCode)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
errorLine := ""
|
||||
scanner := bufio.NewScanner(io.LimitReader(resp.Body, maxErrMsgLen))
|
||||
if scanner.Scan() {
|
||||
errorLine = scanner.Text()
|
||||
}
|
||||
|
||||
return fmt.Errorf("when writing to [%s] received status code: %d. body: %s", h.URL, resp.StatusCode, errorLine)
|
||||
}
|
||||
|
||||
_, err = io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("when writing to [%s] received error: %w", h.URL, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
outputs.Add("http", func() telegraf.Output {
|
||||
return &HTTP{
|
||||
Method: defaultMethod,
|
||||
URL: defaultURL,
|
||||
UseBatchFormat: defaultUseBatchFormat,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (h *HTTP) getAccessToken(ctx context.Context, audience string) (*oauth2.Token, error) {
|
||||
if h.oauth2Token.Valid() {
|
||||
return h.oauth2Token, nil
|
||||
}
|
||||
|
||||
ts, err := idtoken.NewTokenSource(ctx, audience, idtoken.WithCredentialsFile(h.CredentialsFile))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating oauth2 token source: %w", err)
|
||||
}
|
||||
|
||||
token, err := ts.Token()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error fetching oauth2 token: %w", err)
|
||||
}
|
||||
|
||||
h.oauth2Token = token
|
||||
|
||||
return token, nil
|
||||
}
|
819
plugins/outputs/http/http_test.go
Normal file
819
plugins/outputs/http/http_test.go
Normal file
|
@ -0,0 +1,819 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/influxdata/telegraf"
|
||||
"github.com/influxdata/telegraf/config"
|
||||
"github.com/influxdata/telegraf/internal"
|
||||
"github.com/influxdata/telegraf/metric"
|
||||
common_aws "github.com/influxdata/telegraf/plugins/common/aws"
|
||||
common_http "github.com/influxdata/telegraf/plugins/common/http"
|
||||
"github.com/influxdata/telegraf/plugins/common/oauth"
|
||||
"github.com/influxdata/telegraf/plugins/serializers/influx"
|
||||
"github.com/influxdata/telegraf/plugins/serializers/json"
|
||||
"github.com/influxdata/telegraf/testutil"
|
||||
)
|
||||
|
||||
func getMetric() telegraf.Metric {
|
||||
m := metric.New(
|
||||
"cpu",
|
||||
map[string]string{},
|
||||
map[string]interface{}{
|
||||
"value": 42.0,
|
||||
},
|
||||
time.Unix(0, 0),
|
||||
)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func getMetrics(n int) []telegraf.Metric {
|
||||
m := make([]telegraf.Metric, n)
|
||||
for n > 0 {
|
||||
n--
|
||||
m[n] = getMetric()
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func TestInvalidMethod(t *testing.T) {
|
||||
plugin := &HTTP{
|
||||
URL: "",
|
||||
Method: http.MethodGet,
|
||||
}
|
||||
|
||||
err := plugin.Connect()
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMethod(t *testing.T) {
|
||||
ts := httptest.NewServer(http.NotFoundHandler())
|
||||
defer ts.Close()
|
||||
|
||||
u, err := url.Parse("http://" + ts.Listener.Addr().String())
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
plugin *HTTP
|
||||
expectedMethod string
|
||||
connectError bool
|
||||
}{
|
||||
{
|
||||
name: "default method is POST",
|
||||
plugin: &HTTP{
|
||||
URL: u.String(),
|
||||
Method: defaultMethod,
|
||||
},
|
||||
expectedMethod: http.MethodPost,
|
||||
},
|
||||
{
|
||||
name: "put is okay",
|
||||
plugin: &HTTP{
|
||||
URL: u.String(),
|
||||
Method: http.MethodPut,
|
||||
},
|
||||
expectedMethod: http.MethodPut,
|
||||
},
|
||||
{
|
||||
name: "get is invalid",
|
||||
plugin: &HTTP{
|
||||
URL: u.String(),
|
||||
Method: http.MethodGet,
|
||||
},
|
||||
connectError: true,
|
||||
},
|
||||
{
|
||||
name: "method is case insensitive",
|
||||
plugin: &HTTP{
|
||||
URL: u.String(),
|
||||
Method: "poST",
|
||||
},
|
||||
expectedMethod: http.MethodPost,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != tt.expectedMethod {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
t.Errorf("Not equal, expected: %q, actual: %q", tt.expectedMethod, r.Method)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
serializer := &influx.Serializer{}
|
||||
require.NoError(t, serializer.Init())
|
||||
tt.plugin.SetSerializer(serializer)
|
||||
err = tt.plugin.Connect()
|
||||
if tt.connectError {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
err = tt.plugin.Write([]telegraf.Metric{getMetric()})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPClientConfig(t *testing.T) {
|
||||
ts := httptest.NewServer(http.NotFoundHandler())
|
||||
defer ts.Close()
|
||||
|
||||
u, err := url.Parse("http://" + ts.Listener.Addr().String())
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
plugin *HTTP
|
||||
connectError bool
|
||||
expectedMaxIdleConns int
|
||||
expectedMaxIdleConnsPerHost int
|
||||
}{
|
||||
{
|
||||
name: "With default client Config",
|
||||
plugin: &HTTP{
|
||||
URL: u.String(),
|
||||
Method: defaultMethod,
|
||||
HTTPClientConfig: common_http.HTTPClientConfig{
|
||||
IdleConnTimeout: config.Duration(5 * time.Second),
|
||||
},
|
||||
},
|
||||
expectedMaxIdleConns: 0,
|
||||
expectedMaxIdleConnsPerHost: 0,
|
||||
},
|
||||
{
|
||||
name: "With MaxIdleConns client Config",
|
||||
plugin: &HTTP{
|
||||
URL: u.String(),
|
||||
Method: defaultMethod,
|
||||
HTTPClientConfig: common_http.HTTPClientConfig{
|
||||
MaxIdleConns: 100,
|
||||
MaxIdleConnsPerHost: 100,
|
||||
IdleConnTimeout: config.Duration(5 * time.Second),
|
||||
},
|
||||
},
|
||||
expectedMaxIdleConns: 100,
|
||||
expectedMaxIdleConnsPerHost: 100,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
serializer := &influx.Serializer{}
|
||||
require.NoError(t, serializer.Init())
|
||||
tt.plugin.SetSerializer(serializer)
|
||||
err = tt.plugin.Connect()
|
||||
if tt.connectError {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
tr := tt.plugin.client.Transport.(*http.Transport)
|
||||
maxIdleConns, maxIdleConnsPerHost := tr.MaxIdleConns, tr.MaxIdleConnsPerHost
|
||||
require.Equal(t, tt.expectedMaxIdleConns, maxIdleConns)
|
||||
require.Equal(t, tt.expectedMaxIdleConnsPerHost, maxIdleConnsPerHost)
|
||||
|
||||
err = tt.plugin.Write([]telegraf.Metric{getMetric()})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatusCode(t *testing.T) {
|
||||
ts := httptest.NewServer(http.NotFoundHandler())
|
||||
defer ts.Close()
|
||||
|
||||
u, err := url.Parse("http://" + ts.Listener.Addr().String())
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
plugin *HTTP
|
||||
statusCode int
|
||||
errFunc func(t *testing.T, err error)
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
plugin: &HTTP{
|
||||
URL: u.String(),
|
||||
},
|
||||
statusCode: http.StatusOK,
|
||||
errFunc: func(t *testing.T, err error) {
|
||||
require.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "1xx status is an error",
|
||||
plugin: &HTTP{
|
||||
URL: u.String(),
|
||||
},
|
||||
statusCode: http.StatusSwitchingProtocols,
|
||||
errFunc: func(t *testing.T, err error) {
|
||||
require.Error(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "3xx status is an error",
|
||||
plugin: &HTTP{
|
||||
URL: u.String(),
|
||||
},
|
||||
statusCode: http.StatusMultipleChoices,
|
||||
errFunc: func(t *testing.T, err error) {
|
||||
require.Error(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "4xx status is an error",
|
||||
plugin: &HTTP{
|
||||
URL: u.String(),
|
||||
},
|
||||
statusCode: http.StatusBadRequest,
|
||||
errFunc: func(t *testing.T, err error) {
|
||||
require.Error(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Do not retry on configured non-retryable statuscode",
|
||||
plugin: &HTTP{
|
||||
URL: u.String(),
|
||||
NonRetryableStatusCodes: []int{409},
|
||||
},
|
||||
statusCode: http.StatusConflict,
|
||||
errFunc: func(t *testing.T, err error) {
|
||||
require.NoError(t, err)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(tt.statusCode)
|
||||
})
|
||||
|
||||
serializer := &influx.Serializer{}
|
||||
require.NoError(t, serializer.Init())
|
||||
tt.plugin.SetSerializer(serializer)
|
||||
err = tt.plugin.Connect()
|
||||
require.NoError(t, err)
|
||||
|
||||
tt.plugin.Log = testutil.Logger{}
|
||||
|
||||
err = tt.plugin.Write([]telegraf.Metric{getMetric()})
|
||||
tt.errFunc(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestContentType(t *testing.T) {
|
||||
ts := httptest.NewServer(http.NotFoundHandler())
|
||||
defer ts.Close()
|
||||
|
||||
u, err := url.Parse("http://" + ts.Listener.Addr().String())
|
||||
require.NoError(t, err)
|
||||
|
||||
headerSecret := config.NewSecret([]byte("application/json"))
|
||||
tests := []struct {
|
||||
name string
|
||||
plugin *HTTP
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "default is text plain",
|
||||
plugin: &HTTP{
|
||||
URL: u.String(),
|
||||
},
|
||||
expected: defaultContentType,
|
||||
},
|
||||
{
|
||||
name: "overwrite content_type",
|
||||
plugin: &HTTP{
|
||||
URL: u.String(),
|
||||
Headers: map[string]*config.Secret{"Content-Type": &headerSecret},
|
||||
},
|
||||
expected: "application/json",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if contentHeader := r.Header.Get("Content-Type"); contentHeader != tt.expected {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
t.Errorf("Not equal, expected: %q, actual: %q", tt.expected, contentHeader)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
serializer := &influx.Serializer{}
|
||||
require.NoError(t, serializer.Init())
|
||||
tt.plugin.SetSerializer(serializer)
|
||||
err = tt.plugin.Connect()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = tt.plugin.Write([]telegraf.Metric{getMetric()})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestContentEncodingGzip(t *testing.T) {
|
||||
ts := httptest.NewServer(http.NotFoundHandler())
|
||||
defer ts.Close()
|
||||
|
||||
u, err := url.Parse("http://" + ts.Listener.Addr().String())
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
plugin *HTTP
|
||||
payload string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "default is no content encoding",
|
||||
plugin: &HTTP{
|
||||
URL: u.String(),
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "overwrite content_encoding",
|
||||
plugin: &HTTP{
|
||||
URL: u.String(),
|
||||
ContentEncoding: "gzip",
|
||||
},
|
||||
expected: "gzip",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if contentHeader := r.Header.Get("Content-Encoding"); contentHeader != tt.expected {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
t.Errorf("Not equal, expected: %q, actual: %q", tt.expected, contentHeader)
|
||||
return
|
||||
}
|
||||
|
||||
body := r.Body
|
||||
var err error
|
||||
if r.Header.Get("Content-Encoding") == "gzip" {
|
||||
body, err = gzip.NewReader(r.Body)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
payload, err := io.ReadAll(body)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if !strings.Contains(string(payload), "cpu value=42") {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
t.Errorf("'payload' should contain %q", "cpu value=42")
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
})
|
||||
|
||||
serializer := &influx.Serializer{}
|
||||
require.NoError(t, serializer.Init())
|
||||
tt.plugin.SetSerializer(serializer)
|
||||
err = tt.plugin.Connect()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = tt.plugin.Write([]telegraf.Metric{getMetric()})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicAuth(t *testing.T) {
|
||||
ts := httptest.NewServer(http.NotFoundHandler())
|
||||
defer ts.Close()
|
||||
|
||||
u, err := url.Parse("http://" + ts.Listener.Addr().String())
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
username string
|
||||
password string
|
||||
}{
|
||||
{
|
||||
name: "default",
|
||||
},
|
||||
{
|
||||
name: "username only",
|
||||
username: "username",
|
||||
},
|
||||
{
|
||||
name: "password only",
|
||||
password: "pa$$word",
|
||||
},
|
||||
{
|
||||
name: "username and password",
|
||||
username: "username",
|
||||
password: "pa$$word",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
plugin := &HTTP{
|
||||
URL: u.String(),
|
||||
Username: config.NewSecret([]byte(tt.username)),
|
||||
Password: config.NewSecret([]byte(tt.password)),
|
||||
}
|
||||
ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
username, password, _ := r.BasicAuth()
|
||||
if username != tt.username {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
t.Errorf("Not equal, expected: %q, actual: %q", tt.username, username)
|
||||
return
|
||||
}
|
||||
if password != tt.password {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
t.Errorf("Not equal, expected: %q, actual: %q", tt.password, password)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
serializer := &influx.Serializer{}
|
||||
require.NoError(t, serializer.Init())
|
||||
plugin.SetSerializer(serializer)
|
||||
require.NoError(t, plugin.Connect())
|
||||
require.NoError(t, plugin.Write([]telegraf.Metric{getMetric()}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type TestHandlerFunc func(t *testing.T, w http.ResponseWriter, r *http.Request)
|
||||
|
||||
func TestOAuthClientCredentialsGrant(t *testing.T) {
|
||||
ts := httptest.NewServer(http.NotFoundHandler())
|
||||
defer ts.Close()
|
||||
|
||||
var token = "2YotnFZFEjr1zCsicMWpAA"
|
||||
|
||||
u, err := url.Parse("http://" + ts.Listener.Addr().String())
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
plugin *HTTP
|
||||
tokenHandler TestHandlerFunc
|
||||
handler TestHandlerFunc
|
||||
}{
|
||||
{
|
||||
name: "no credentials",
|
||||
plugin: &HTTP{
|
||||
URL: u.String(),
|
||||
},
|
||||
handler: func(t *testing.T, w http.ResponseWriter, r *http.Request) {
|
||||
require.Empty(t, r.Header["Authorization"])
|
||||
w.WriteHeader(http.StatusOK)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "success",
|
||||
plugin: &HTTP{
|
||||
URL: u.String() + "/write",
|
||||
HTTPClientConfig: common_http.HTTPClientConfig{
|
||||
OAuth2Config: oauth.OAuth2Config{
|
||||
ClientID: "howdy",
|
||||
ClientSecret: "secret",
|
||||
TokenURL: u.String() + "/token",
|
||||
Scopes: []string{"urn:opc:idm:__myscopes__"},
|
||||
},
|
||||
},
|
||||
},
|
||||
tokenHandler: func(t *testing.T, w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
values := url.Values{}
|
||||
values.Add("access_token", token)
|
||||
values.Add("token_type", "bearer")
|
||||
values.Add("expires_in", "3600")
|
||||
_, err = w.Write([]byte(values.Encode()))
|
||||
require.NoError(t, err)
|
||||
},
|
||||
handler: func(t *testing.T, w http.ResponseWriter, r *http.Request) {
|
||||
require.Equal(t, []string{"Bearer " + token}, r.Header["Authorization"])
|
||||
w.WriteHeader(http.StatusOK)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "audience",
|
||||
plugin: &HTTP{
|
||||
URL: u.String() + "/write",
|
||||
HTTPClientConfig: common_http.HTTPClientConfig{
|
||||
OAuth2Config: oauth.OAuth2Config{
|
||||
ClientID: "howdy",
|
||||
ClientSecret: "secret",
|
||||
TokenURL: u.String() + "/token",
|
||||
Scopes: []string{"urn:opc:idm:__myscopes__"},
|
||||
Audience: "audience",
|
||||
},
|
||||
},
|
||||
},
|
||||
tokenHandler: func(t *testing.T, w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
values := url.Values{}
|
||||
values.Add("access_token", token)
|
||||
values.Add("token_type", "bearer")
|
||||
values.Add("expires_in", "3600")
|
||||
_, err = w.Write([]byte(values.Encode()))
|
||||
require.NoError(t, err)
|
||||
},
|
||||
handler: func(t *testing.T, w http.ResponseWriter, r *http.Request) {
|
||||
require.Equal(t, []string{"Bearer " + token}, r.Header["Authorization"])
|
||||
w.WriteHeader(http.StatusOK)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/write":
|
||||
tt.handler(t, w, r)
|
||||
case "/token":
|
||||
tt.tokenHandler(t, w, r)
|
||||
}
|
||||
})
|
||||
|
||||
serializer := &influx.Serializer{}
|
||||
require.NoError(t, serializer.Init())
|
||||
tt.plugin.SetSerializer(serializer)
|
||||
err = tt.plugin.Connect()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = tt.plugin.Write([]telegraf.Metric{getMetric()})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOAuthAuthorizationCodeGrant(t *testing.T) {
|
||||
ts := httptest.NewServer(http.NotFoundHandler())
|
||||
defer ts.Close()
|
||||
|
||||
u, err := url.Parse("http://" + ts.Listener.Addr().String())
|
||||
require.NoError(t, err)
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
tmpFile, err := os.CreateTemp(tmpDir, "test_key_file")
|
||||
require.NoError(t, err)
|
||||
|
||||
tmpTokenURI := u.String() + "/token"
|
||||
data := []byte(
|
||||
fmt.Sprintf(
|
||||
"{\n \"type\": \"service_account\",\n \"project_id\": \"my-project\",\n "+
|
||||
"\"private_key_id\": \"223423436436453645363456\",\n \"private_key\": "+
|
||||
"\"-----BEGIN PRIVATE KEY-----\\nMIICXAIBAAKBgQDX7Plvu0MJtA9TrusYtQnAogsdiYJZd9wfFIjH5FxE3SWJ4KAIE+yRWRqcqX8XnpieQLaNsfXhDPWLkWngTDydk4NO/"+
|
||||
"jlAQk0e6+9+NeiZ2ViIHmtXERb9CyiiWUmo+YCd69lhzSEIMK9EPBSDHQTgQMtEfGak03G5rx3MCakE1QIDAQABAoGAOjRU4Lt3zKvO3d3u3ZAfet+zY1jn3DolCfO9EzUJcj6ymc"+
|
||||
"IFIWhNgrikJcrCyZkkxrPnAbcQ8oNNxTuDcMTcKZbnyUnlQj5NtVuty5Q+zgf3/Q2pRhaE+TwrpOJ+ETtVp9R/PrPN2NC5wPo289fPNWFYkd4DPbdWZp5AJHz1XYECQQD3kKpinJx"+
|
||||
"MYp9FQ1Qj1OkxGln0KPgdqRYjjW/rXI4/hUodfg+xXWHPFSGj3AgEjQIvuengbOAeH3qowF1uxVTlAkEA30hXM3EbboMCDQzNRNkkV9EiZ0MZXhj1aIGl+sQZOmOeFdcdjGkDdsA4"+
|
||||
"2nmaYqXCD9KAvc+S/tGJaa0Qg0VhMQJAb2+TAqh0Qn3yK39PFIH2JcAy1ZDLfq5p5L75rfwPm9AnuHbSIYhjSo+8gMG+ai3+2fTZrcfUajrJP8S3SfFRcQJBANQQPOHatxcKzlPeq"+
|
||||
"MaPBXlyY553mAxK4CnVmPLGdL+EBYzwtlu5EVUj09uMSxkOHXYxk5yzHQVvtXbsrBZBOsECQBJLlkMjJmXrIIdLPmHQWL3bm9MMg1PqzupSEwz6cyrGuIIm/X91pDyxCHaKYWp38F"+
|
||||
"XBkYAgohI8ow5/sgRvU5w=\\n-----END PRIVATE KEY-----\\n\",\n "+
|
||||
"\"client_email\": \"test-service-account-email@example.iam.gserviceaccount.com\",\n \"client_id\": \"110300009813738675309\",\n "+
|
||||
"\"auth_uri\": \"https://accounts.google.com/o/oauth2/auth\",\n \"token_uri\": \"%s\",\n "+
|
||||
"\"auth_provider_x509_cert_url\": \"https://www.googleapis.com/oauth2/v1/certs\",\n "+
|
||||
"\"client_x509_cert_url\": \"https://www.googleapis.com/robot/v1/metadata/x509/test-service-account-email@example.iam.gserviceaccount.com\"\n}",
|
||||
tmpTokenURI,
|
||||
),
|
||||
)
|
||||
_, err = tmpFile.Write(data)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, tmpFile.Close())
|
||||
|
||||
const token = "eyJhbGciOiJSUzI1NiIsImtpZCI6Ijg2NzUzMDliMjJiMDFiZTU2YzIxM2M5ODU0MGFiNTYzYmZmNWE1OGMiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwOi8vMTI3LjAuMC4x" +
|
||||
"OjU4MDI1LyIsImF6cCI6InRlc3Qtc2VydmljZS1hY2NvdW50LWVtYWlsQGV4YW1wbGUuY29tIiwiZW1haWwiOiJ0ZXN0LXNlcnZpY2UtYWNjb3VudC1lbWFpbEBleGFtcGxlLmNvbSIsImVtY" +
|
||||
"WlsX3ZlcmlmaWVkIjp0cnVlLCJleHAiOjk0NjY4NDgwMCwiaWF0Ijo5NDY2ODEyMDAsImlzcyI6Imh0dHBzOi8vYWNjb3VudHMudGVzdC5jb20iLCJzdWIiOiIxMTAzMDAwMDk4MTM3Mzg2Nz" +
|
||||
"UzMDkifQ.qi2LsXP2o6nl-rbYKUlHAgTBY0QoU7Nhty5NGR4GMdc8OoGEPW-vlD0WBSaKSr11vyFcIO4ftFDWXElo9Ut-AIQPKVxinsjHIU2-LoIATgI1kyifFLyU_pBecwcI4CIXEcDK5wEk" +
|
||||
"fonWFSkyDZHBeZFKbJXlQXtxj0OHvQ-DEEepXLuKY6v3s4U6GyD9_ppYUy6gzDZPYUbfPfgxCj_Jbv6qkLU0DiZ7F5-do6X6n-qkpgCRLTGHcY__rn8oe8_pSimsyJEeY49ZQ5lj4mXkVCwgL" +
|
||||
"9bvL1_eW1p6sgbHaBnPKVPbM7S1_cBmzgSonm__qWyZUxfDgNdigtNsvzBQTg"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
plugin *HTTP
|
||||
handler TestHandlerFunc
|
||||
tokenHandler TestHandlerFunc
|
||||
}{
|
||||
{
|
||||
name: "no credentials file",
|
||||
plugin: &HTTP{
|
||||
URL: u.String(),
|
||||
},
|
||||
handler: func(t *testing.T, w http.ResponseWriter, r *http.Request) {
|
||||
require.Empty(t, r.Header["Authorization"])
|
||||
w.WriteHeader(http.StatusOK)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "success",
|
||||
plugin: &HTTP{
|
||||
URL: u.String() + "/write",
|
||||
CredentialsFile: tmpFile.Name(),
|
||||
},
|
||||
tokenHandler: func(t *testing.T, w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
authHeader := fmt.Sprintf(`{"id_token":%q}`, token)
|
||||
_, err = w.Write([]byte(authHeader))
|
||||
require.NoError(t, err)
|
||||
},
|
||||
handler: func(t *testing.T, w http.ResponseWriter, r *http.Request) {
|
||||
require.Equal(t, []string{"Bearer " + token}, r.Header["Authorization"])
|
||||
w.WriteHeader(http.StatusOK)
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/write":
|
||||
tt.handler(t, w, r)
|
||||
case "/token":
|
||||
tt.tokenHandler(t, w, r)
|
||||
}
|
||||
})
|
||||
|
||||
serializer := &influx.Serializer{}
|
||||
require.NoError(t, serializer.Init())
|
||||
tt.plugin.SetSerializer(serializer)
|
||||
|
||||
require.NoError(t, tt.plugin.Connect())
|
||||
require.NoError(t, tt.plugin.Write([]telegraf.Metric{getMetric()}))
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultUserAgent(t *testing.T) {
|
||||
ts := httptest.NewServer(http.NotFoundHandler())
|
||||
defer ts.Close()
|
||||
|
||||
u, err := url.Parse("http://" + ts.Listener.Addr().String())
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("default-user-agent", func(t *testing.T) {
|
||||
ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if userHeader := r.Header.Get("User-Agent"); userHeader != internal.ProductToken() {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
t.Errorf("Not equal, expected: %q, actual: %q", internal.ProductToken(), userHeader)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
client := &HTTP{
|
||||
URL: u.String(),
|
||||
Method: defaultMethod,
|
||||
}
|
||||
|
||||
serializer := &influx.Serializer{}
|
||||
require.NoError(t, serializer.Init())
|
||||
client.SetSerializer(serializer)
|
||||
err = client.Connect()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = client.Write([]telegraf.Metric{getMetric()})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBatchedUnbatched(t *testing.T) {
|
||||
ts := httptest.NewServer(http.NotFoundHandler())
|
||||
defer ts.Close()
|
||||
|
||||
u, err := url.Parse("http://" + ts.Listener.Addr().String())
|
||||
require.NoError(t, err)
|
||||
|
||||
client := &HTTP{
|
||||
URL: u.String(),
|
||||
Method: defaultMethod,
|
||||
}
|
||||
|
||||
influxSerializer := &influx.Serializer{}
|
||||
require.NoError(t, influxSerializer.Init())
|
||||
|
||||
jsonSerializer := &json.Serializer{}
|
||||
require.NoError(t, jsonSerializer.Init())
|
||||
|
||||
s := map[string]telegraf.Serializer{
|
||||
"influx": influxSerializer,
|
||||
"json": jsonSerializer,
|
||||
}
|
||||
|
||||
for name, serializer := range s {
|
||||
var requests int
|
||||
ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
requests++
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
t.Run(name, func(t *testing.T) {
|
||||
for _, mode := range [...]bool{false, true} {
|
||||
requests = 0
|
||||
client.UseBatchFormat = mode
|
||||
client.SetSerializer(serializer)
|
||||
|
||||
err = client.Connect()
|
||||
require.NoError(t, err)
|
||||
err = client.Write(getMetrics(3))
|
||||
require.NoError(t, err)
|
||||
|
||||
if client.UseBatchFormat {
|
||||
require.Equal(t, 1, requests, "batched")
|
||||
} else {
|
||||
require.Equal(t, 3, requests, "unbatched")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAwsCredentials(t *testing.T) {
|
||||
ts := httptest.NewServer(http.NotFoundHandler())
|
||||
defer ts.Close()
|
||||
|
||||
u, err := url.Parse("http://" + ts.Listener.Addr().String())
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
plugin *HTTP
|
||||
tokenHandler TestHandlerFunc
|
||||
handler TestHandlerFunc
|
||||
}{
|
||||
{
|
||||
name: "simple credentials",
|
||||
plugin: &HTTP{
|
||||
URL: u.String(),
|
||||
AwsService: "aps",
|
||||
CredentialConfig: common_aws.CredentialConfig{
|
||||
Region: "us-east-1",
|
||||
AccessKey: "dummy",
|
||||
SecretKey: "dummy",
|
||||
},
|
||||
},
|
||||
handler: func(t *testing.T, w http.ResponseWriter, r *http.Request) {
|
||||
require.Contains(t, r.Header["Authorization"][0], "AWS4-HMAC-SHA256")
|
||||
require.Contains(t, r.Header["Authorization"][0], "=dummy/")
|
||||
require.Contains(t, r.Header["Authorization"][0], "/us-east-1/")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ts.Config.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
tt.handler(t, w, r)
|
||||
})
|
||||
|
||||
serializer := &influx.Serializer{}
|
||||
require.NoError(t, serializer.Init())
|
||||
tt.plugin.SetSerializer(serializer)
|
||||
err = tt.plugin.Connect()
|
||||
require.NoError(t, err)
|
||||
|
||||
err = tt.plugin.Write([]telegraf.Metric{getMetric()})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
112
plugins/outputs/http/sample.conf
Normal file
112
plugins/outputs/http/sample.conf
Normal file
|
@ -0,0 +1,112 @@
|
|||
# A plugin that can transmit metrics over HTTP
|
||||
[[outputs.http]]
|
||||
## URL is the address to send metrics to
|
||||
url = "http://127.0.0.1:8080/telegraf"
|
||||
|
||||
## Timeout for HTTP message
|
||||
# timeout = "5s"
|
||||
|
||||
## HTTP method, one of: "POST" or "PUT" or "PATCH"
|
||||
# method = "POST"
|
||||
|
||||
## HTTP Basic Auth credentials
|
||||
# username = "username"
|
||||
# password = "pa$$word"
|
||||
|
||||
## OAuth2 Client Credentials Grant
|
||||
# client_id = "clientid"
|
||||
# client_secret = "secret"
|
||||
# token_url = "https://indentityprovider/oauth2/v1/token"
|
||||
# audience = ""
|
||||
# scopes = ["urn:opc:idm:__myscopes__"]
|
||||
|
||||
## Goole API Auth
|
||||
# google_application_credentials = "/etc/telegraf/example_secret.json"
|
||||
|
||||
## HTTP Proxy support
|
||||
# use_system_proxy = false
|
||||
# http_proxy_url = ""
|
||||
|
||||
## Optional TLS Config
|
||||
# tls_ca = "/etc/telegraf/ca.pem"
|
||||
# tls_cert = "/etc/telegraf/cert.pem"
|
||||
# tls_key = "/etc/telegraf/key.pem"
|
||||
## Use TLS but skip chain & host verification
|
||||
# insecure_skip_verify = false
|
||||
|
||||
## Optional Cookie authentication
|
||||
# cookie_auth_url = "https://localhost/authMe"
|
||||
# cookie_auth_method = "POST"
|
||||
# cookie_auth_username = "username"
|
||||
# cookie_auth_password = "pa$$word"
|
||||
# cookie_auth_headers = '{"Content-Type": "application/json", "X-MY-HEADER":"hello"}'
|
||||
# cookie_auth_body = '{"username": "user", "password": "pa$$word", "authenticate": "me"}'
|
||||
## cookie_auth_renewal not set or set to "0" will auth once and never renew the cookie
|
||||
# cookie_auth_renewal = "5m"
|
||||
|
||||
## Data format to output.
|
||||
## Each data format has it's own unique set of configuration options, read
|
||||
## more about them here:
|
||||
## https://github.com/influxdata/telegraf/blob/master/docs/DATA_FORMATS_OUTPUT.md
|
||||
# data_format = "influx"
|
||||
|
||||
## Use batch serialization format (default) instead of line based format.
|
||||
## Batch format is more efficient and should be used unless line based
|
||||
## format is really needed.
|
||||
# use_batch_format = true
|
||||
|
||||
## HTTP Content-Encoding for write request body, can be set to "gzip" to
|
||||
## compress body or "identity" to apply no encoding.
|
||||
# content_encoding = "identity"
|
||||
|
||||
## MaxIdleConns controls the maximum number of idle (keep-alive)
|
||||
## connections across all hosts. Zero means no limit.
|
||||
# max_idle_conn = 0
|
||||
|
||||
## MaxIdleConnsPerHost, if non-zero, controls the maximum idle
|
||||
## (keep-alive) connections to keep per-host. If zero,
|
||||
## DefaultMaxIdleConnsPerHost is used(2).
|
||||
# max_idle_conn_per_host = 2
|
||||
|
||||
## Idle (keep-alive) connection timeout.
|
||||
## Maximum amount of time before idle connection is closed.
|
||||
## Zero means no limit.
|
||||
# idle_conn_timeout = 0
|
||||
|
||||
## Amazon Region
|
||||
#region = "us-east-1"
|
||||
|
||||
## Amazon Credentials
|
||||
## Amazon Credentials are not built unless the following aws_service
|
||||
## setting is set to a non-empty string. It may need to match the name of
|
||||
## the service output to as well
|
||||
#aws_service = "execute-api"
|
||||
|
||||
## Credentials are loaded in the following order
|
||||
## 1) Web identity provider credentials via STS if role_arn and web_identity_token_file are specified
|
||||
## 2) Assumed credentials via STS if role_arn is specified
|
||||
## 3) explicit credentials from 'access_key' and 'secret_key'
|
||||
## 4) shared profile from 'profile'
|
||||
## 5) environment variables
|
||||
## 6) shared credentials file
|
||||
## 7) EC2 Instance Profile
|
||||
#access_key = ""
|
||||
#secret_key = ""
|
||||
#token = ""
|
||||
#role_arn = ""
|
||||
#web_identity_token_file = ""
|
||||
#role_session_name = ""
|
||||
#profile = ""
|
||||
#shared_credential_file = ""
|
||||
|
||||
## Optional list of statuscodes (<200 or >300) upon which requests should not be retried
|
||||
# non_retryable_statuscodes = [409, 413]
|
||||
|
||||
## NOTE: Due to the way TOML is parsed, tables must be at the END of the
|
||||
## plugin definition, otherwise additional config options are read as part of
|
||||
## the table
|
||||
|
||||
## Additional HTTP headers
|
||||
# [outputs.http.headers]
|
||||
# ## Should be set manually to "application/json" for json data_format
|
||||
# Content-Type = "text/plain; charset=utf-8"
|
Loading…
Add table
Add a link
Reference in a new issue