846 lines
21 KiB
Go
846 lines
21 KiB
Go
|
package socket
|
||
|
|
||
|
import (
|
||
|
"crypto/tls"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"net"
|
||
|
"os"
|
||
|
"runtime"
|
||
|
"strings"
|
||
|
"sync"
|
||
|
"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"
|
||
|
_ "github.com/influxdata/telegraf/plugins/parsers/all"
|
||
|
"github.com/influxdata/telegraf/plugins/parsers/influx"
|
||
|
"github.com/influxdata/telegraf/testutil"
|
||
|
)
|
||
|
|
||
|
var pki = testutil.NewPKI("../../../testutil/pki")
|
||
|
|
||
|
func TestListenData(t *testing.T) {
|
||
|
messages := [][]byte{
|
||
|
[]byte("test,foo=bar v=1i 123456789\ntest,foo=baz v=2i 123456790\n"),
|
||
|
[]byte("test,foo=zab v=3i 123456791\n"),
|
||
|
}
|
||
|
expectedTemplates := []telegraf.Metric{
|
||
|
metric.New(
|
||
|
"test",
|
||
|
map[string]string{"foo": "bar"},
|
||
|
map[string]interface{}{"v": int64(1)},
|
||
|
time.Unix(0, 123456789),
|
||
|
),
|
||
|
metric.New(
|
||
|
"test",
|
||
|
map[string]string{"foo": "baz"},
|
||
|
map[string]interface{}{"v": int64(2)},
|
||
|
time.Unix(0, 123456790),
|
||
|
),
|
||
|
metric.New(
|
||
|
"test",
|
||
|
map[string]string{"foo": "zab"},
|
||
|
map[string]interface{}{"v": int64(3)},
|
||
|
time.Unix(0, 123456791),
|
||
|
),
|
||
|
}
|
||
|
|
||
|
tests := []struct {
|
||
|
name string
|
||
|
schema string
|
||
|
buffersize config.Size
|
||
|
encoding string
|
||
|
}{
|
||
|
{
|
||
|
name: "TCP",
|
||
|
schema: "tcp",
|
||
|
buffersize: config.Size(1024),
|
||
|
},
|
||
|
{
|
||
|
name: "TCP with TLS",
|
||
|
schema: "tcp+tls",
|
||
|
},
|
||
|
{
|
||
|
name: "TCP with gzip encoding",
|
||
|
schema: "tcp",
|
||
|
buffersize: config.Size(1024),
|
||
|
encoding: "gzip",
|
||
|
},
|
||
|
{
|
||
|
name: "UDP",
|
||
|
schema: "udp",
|
||
|
buffersize: config.Size(1024),
|
||
|
},
|
||
|
{
|
||
|
name: "UDP with gzip encoding",
|
||
|
schema: "udp",
|
||
|
buffersize: config.Size(1024),
|
||
|
encoding: "gzip",
|
||
|
},
|
||
|
{
|
||
|
name: "unix socket",
|
||
|
schema: "unix",
|
||
|
buffersize: config.Size(1024),
|
||
|
},
|
||
|
{
|
||
|
name: "unix socket with TLS",
|
||
|
schema: "unix+tls",
|
||
|
},
|
||
|
{
|
||
|
name: "unix socket with gzip encoding",
|
||
|
schema: "unix",
|
||
|
encoding: "gzip",
|
||
|
},
|
||
|
{
|
||
|
name: "unixgram socket",
|
||
|
schema: "unixgram",
|
||
|
buffersize: config.Size(1024),
|
||
|
},
|
||
|
}
|
||
|
|
||
|
serverTLS := pki.TLSServerConfig()
|
||
|
clientTLS := pki.TLSClientConfig()
|
||
|
|
||
|
for _, tt := range tests {
|
||
|
t.Run(tt.name, func(t *testing.T) {
|
||
|
proto := strings.TrimSuffix(tt.schema, "+tls")
|
||
|
|
||
|
// Prepare the address and socket if needed
|
||
|
var sockPath string
|
||
|
var serviceAddress string
|
||
|
var tlsCfg *tls.Config
|
||
|
switch proto {
|
||
|
case "tcp", "udp":
|
||
|
serviceAddress = proto + "://" + "127.0.0.1:0"
|
||
|
case "unix", "unixgram":
|
||
|
if runtime.GOOS == "windows" {
|
||
|
t.Skip("Skipping on Windows, as unixgram sockets are not supported")
|
||
|
}
|
||
|
|
||
|
// Create a socket
|
||
|
sockPath = testutil.TempSocket(t)
|
||
|
f, err := os.Create(sockPath)
|
||
|
require.NoError(t, err)
|
||
|
defer f.Close()
|
||
|
serviceAddress = proto + "://" + sockPath
|
||
|
}
|
||
|
|
||
|
// Setup the configuration according to test specification
|
||
|
cfg := &Config{
|
||
|
ContentEncoding: tt.encoding,
|
||
|
ReadBufferSize: tt.buffersize,
|
||
|
}
|
||
|
if strings.HasSuffix(tt.schema, "tls") {
|
||
|
cfg.ServerConfig = *serverTLS
|
||
|
var err error
|
||
|
tlsCfg, err = clientTLS.TLSConfig()
|
||
|
require.NoError(t, err)
|
||
|
}
|
||
|
|
||
|
// Create the socket
|
||
|
sock, err := cfg.NewSocket(serviceAddress, &SplitConfig{}, &testutil.Logger{})
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
// Create callbacks
|
||
|
parser := &influx.Parser{}
|
||
|
require.NoError(t, parser.Init())
|
||
|
|
||
|
var acc testutil.Accumulator
|
||
|
onData := func(remote net.Addr, data []byte, _ time.Time) {
|
||
|
m, err := parser.Parse(data)
|
||
|
require.NoError(t, err)
|
||
|
addr, _, err := net.SplitHostPort(remote.String())
|
||
|
if err != nil {
|
||
|
addr = remote.String()
|
||
|
}
|
||
|
for i := range m {
|
||
|
m[i].AddTag("source", addr)
|
||
|
}
|
||
|
acc.AddMetrics(m)
|
||
|
}
|
||
|
onError := func(err error) {
|
||
|
acc.AddError(err)
|
||
|
}
|
||
|
|
||
|
// Start the listener
|
||
|
require.NoError(t, sock.Setup())
|
||
|
sock.Listen(onData, onError)
|
||
|
defer sock.Close()
|
||
|
|
||
|
addr := sock.Address()
|
||
|
|
||
|
// Create a noop client
|
||
|
// Server is async, so verify no errors at the end.
|
||
|
client, err := createClient(serviceAddress, addr, tlsCfg)
|
||
|
require.NoError(t, err)
|
||
|
require.NoError(t, client.Close())
|
||
|
|
||
|
// Setup the client for submitting data
|
||
|
client, err = createClient(serviceAddress, addr, tlsCfg)
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
// Conditionally add the source address to the expectation
|
||
|
expected := make([]telegraf.Metric, 0, len(expectedTemplates))
|
||
|
for _, tmpl := range expectedTemplates {
|
||
|
m := tmpl.Copy()
|
||
|
switch proto {
|
||
|
case "tcp", "udp":
|
||
|
laddr := client.LocalAddr().String()
|
||
|
addr, _, err := net.SplitHostPort(laddr)
|
||
|
if err != nil {
|
||
|
addr = laddr
|
||
|
}
|
||
|
m.AddTag("source", addr)
|
||
|
case "unix", "unixgram":
|
||
|
m.AddTag("source", sockPath)
|
||
|
}
|
||
|
expected = append(expected, m)
|
||
|
}
|
||
|
|
||
|
// Send the data with the correct encoding
|
||
|
encoder, err := internal.NewContentEncoder(tt.encoding)
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
for i, msg := range messages {
|
||
|
m, err := encoder.Encode(msg)
|
||
|
require.NoErrorf(t, err, "encoding failed for msg %d", i)
|
||
|
_, err = client.Write(m)
|
||
|
require.NoErrorf(t, err, "sending msg %d failed", i)
|
||
|
}
|
||
|
|
||
|
// Test the resulting metrics and compare against expected results
|
||
|
require.Eventuallyf(t, func() bool {
|
||
|
acc.Lock()
|
||
|
defer acc.Unlock()
|
||
|
return acc.NMetrics() >= uint64(len(expected))
|
||
|
}, time.Second, 100*time.Millisecond, "did not receive metrics (%d)", acc.NMetrics())
|
||
|
|
||
|
actual := acc.GetTelegrafMetrics()
|
||
|
testutil.RequireMetricsEqual(t, expected, actual, testutil.SortMetrics())
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestListenConnection(t *testing.T) {
|
||
|
messages := [][]byte{
|
||
|
[]byte("test,foo=bar v=1i 123456789\ntest,foo=baz v=2i 123456790\n"),
|
||
|
[]byte("test,foo=zab v=3i 123456791\n"),
|
||
|
}
|
||
|
expectedTemplates := []telegraf.Metric{
|
||
|
metric.New(
|
||
|
"test",
|
||
|
map[string]string{"foo": "bar"},
|
||
|
map[string]interface{}{"v": int64(1)},
|
||
|
time.Unix(0, 123456789),
|
||
|
),
|
||
|
metric.New(
|
||
|
"test",
|
||
|
map[string]string{"foo": "baz"},
|
||
|
map[string]interface{}{"v": int64(2)},
|
||
|
time.Unix(0, 123456790),
|
||
|
),
|
||
|
metric.New(
|
||
|
"test",
|
||
|
map[string]string{"foo": "zab"},
|
||
|
map[string]interface{}{"v": int64(3)},
|
||
|
time.Unix(0, 123456791),
|
||
|
),
|
||
|
}
|
||
|
|
||
|
tests := []struct {
|
||
|
name string
|
||
|
schema string
|
||
|
buffersize config.Size
|
||
|
encoding string
|
||
|
}{
|
||
|
{
|
||
|
name: "TCP",
|
||
|
schema: "tcp",
|
||
|
buffersize: config.Size(1024),
|
||
|
},
|
||
|
{
|
||
|
name: "TCP with TLS",
|
||
|
schema: "tcp+tls",
|
||
|
},
|
||
|
{
|
||
|
name: "TCP with gzip encoding",
|
||
|
schema: "tcp",
|
||
|
buffersize: config.Size(1024),
|
||
|
encoding: "gzip",
|
||
|
},
|
||
|
{
|
||
|
name: "UDP",
|
||
|
schema: "udp",
|
||
|
buffersize: config.Size(1024),
|
||
|
},
|
||
|
{
|
||
|
name: "UDP with gzip encoding",
|
||
|
schema: "udp",
|
||
|
buffersize: config.Size(1024),
|
||
|
encoding: "gzip",
|
||
|
},
|
||
|
{
|
||
|
name: "unix socket",
|
||
|
schema: "unix",
|
||
|
buffersize: config.Size(1024),
|
||
|
},
|
||
|
{
|
||
|
name: "unix socket with TLS",
|
||
|
schema: "unix+tls",
|
||
|
},
|
||
|
{
|
||
|
name: "unix socket with gzip encoding",
|
||
|
schema: "unix",
|
||
|
encoding: "gzip",
|
||
|
},
|
||
|
{
|
||
|
name: "unixgram socket",
|
||
|
schema: "unixgram",
|
||
|
buffersize: config.Size(1024),
|
||
|
},
|
||
|
}
|
||
|
|
||
|
serverTLS := pki.TLSServerConfig()
|
||
|
clientTLS := pki.TLSClientConfig()
|
||
|
|
||
|
for _, tt := range tests {
|
||
|
t.Run(tt.name, func(t *testing.T) {
|
||
|
proto := strings.TrimSuffix(tt.schema, "+tls")
|
||
|
|
||
|
// Prepare the address and socket if needed
|
||
|
var sockPath string
|
||
|
var serviceAddress string
|
||
|
var tlsCfg *tls.Config
|
||
|
switch proto {
|
||
|
case "tcp", "udp":
|
||
|
serviceAddress = proto + "://" + "127.0.0.1:0"
|
||
|
case "unix", "unixgram":
|
||
|
if runtime.GOOS == "windows" {
|
||
|
t.Skip("Skipping on Windows, as unixgram sockets are not supported")
|
||
|
}
|
||
|
|
||
|
// Create a socket
|
||
|
sockPath = testutil.TempSocket(t)
|
||
|
f, err := os.Create(sockPath)
|
||
|
require.NoError(t, err)
|
||
|
defer f.Close()
|
||
|
serviceAddress = proto + "://" + sockPath
|
||
|
}
|
||
|
|
||
|
// Setup the configuration according to test specification
|
||
|
cfg := &Config{
|
||
|
ContentEncoding: tt.encoding,
|
||
|
ReadBufferSize: tt.buffersize,
|
||
|
}
|
||
|
if strings.HasSuffix(tt.schema, "tls") {
|
||
|
cfg.ServerConfig = *serverTLS
|
||
|
var err error
|
||
|
tlsCfg, err = clientTLS.TLSConfig()
|
||
|
require.NoError(t, err)
|
||
|
}
|
||
|
|
||
|
// Create the socket
|
||
|
sock, err := cfg.NewSocket(serviceAddress, &SplitConfig{}, &testutil.Logger{})
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
// Create callbacks
|
||
|
parser := &influx.Parser{}
|
||
|
require.NoError(t, parser.Init())
|
||
|
|
||
|
var acc testutil.Accumulator
|
||
|
onConnection := func(remote net.Addr, reader io.ReadCloser) {
|
||
|
data, err := io.ReadAll(reader)
|
||
|
require.NoError(t, err)
|
||
|
m, err := parser.Parse(data)
|
||
|
require.NoError(t, err)
|
||
|
addr, _, err := net.SplitHostPort(remote.String())
|
||
|
if err != nil {
|
||
|
addr = remote.String()
|
||
|
}
|
||
|
for i := range m {
|
||
|
m[i].AddTag("source", addr)
|
||
|
}
|
||
|
acc.AddMetrics(m)
|
||
|
}
|
||
|
onError := func(err error) {
|
||
|
acc.AddError(err)
|
||
|
}
|
||
|
|
||
|
// Start the listener
|
||
|
require.NoError(t, sock.Setup())
|
||
|
sock.ListenConnection(onConnection, onError)
|
||
|
defer sock.Close()
|
||
|
|
||
|
addr := sock.Address()
|
||
|
|
||
|
// Create a noop client
|
||
|
// Server is async, so verify no errors at the end.
|
||
|
client, err := createClient(serviceAddress, addr, tlsCfg)
|
||
|
require.NoError(t, err)
|
||
|
require.NoError(t, client.Close())
|
||
|
|
||
|
// Setup the client for submitting data
|
||
|
client, err = createClient(serviceAddress, addr, tlsCfg)
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
// Conditionally add the source address to the expectation
|
||
|
expected := make([]telegraf.Metric, 0, len(expectedTemplates))
|
||
|
for _, tmpl := range expectedTemplates {
|
||
|
m := tmpl.Copy()
|
||
|
switch proto {
|
||
|
case "tcp", "udp":
|
||
|
laddr := client.LocalAddr().String()
|
||
|
addr, _, err := net.SplitHostPort(laddr)
|
||
|
if err != nil {
|
||
|
addr = laddr
|
||
|
}
|
||
|
m.AddTag("source", addr)
|
||
|
case "unix", "unixgram":
|
||
|
m.AddTag("source", sockPath)
|
||
|
}
|
||
|
expected = append(expected, m)
|
||
|
}
|
||
|
|
||
|
// Send the data with the correct encoding
|
||
|
encoder, err := internal.NewContentEncoder(tt.encoding)
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
for i, msg := range messages {
|
||
|
m, err := encoder.Encode(msg)
|
||
|
require.NoErrorf(t, err, "encoding failed for msg %d", i)
|
||
|
_, err = client.Write(m)
|
||
|
require.NoErrorf(t, err, "sending msg %d failed", i)
|
||
|
}
|
||
|
client.Close()
|
||
|
|
||
|
// Test the resulting metrics and compare against expected results
|
||
|
require.Eventuallyf(t, func() bool {
|
||
|
acc.Lock()
|
||
|
defer acc.Unlock()
|
||
|
return acc.NMetrics() >= uint64(len(expected))
|
||
|
}, time.Second, 100*time.Millisecond, "did not receive metrics (%d)", acc.NMetrics())
|
||
|
|
||
|
actual := acc.GetTelegrafMetrics()
|
||
|
testutil.RequireMetricsEqual(t, expected, actual, testutil.SortMetrics())
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestClosingConnections(t *testing.T) {
|
||
|
// Setup the configuration
|
||
|
cfg := &Config{
|
||
|
ReadBufferSize: 1024,
|
||
|
}
|
||
|
|
||
|
// Create the socket
|
||
|
serviceAddress := "tcp://127.0.0.1:0"
|
||
|
logger := &testutil.CaptureLogger{}
|
||
|
sock, err := cfg.NewSocket(serviceAddress, &SplitConfig{}, logger)
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
// Create callbacks
|
||
|
parser := &influx.Parser{}
|
||
|
require.NoError(t, parser.Init())
|
||
|
|
||
|
var acc testutil.Accumulator
|
||
|
onData := func(_ net.Addr, data []byte, _ time.Time) {
|
||
|
m, err := parser.Parse(data)
|
||
|
require.NoError(t, err)
|
||
|
acc.AddMetrics(m)
|
||
|
}
|
||
|
onError := func(err error) {
|
||
|
acc.AddError(err)
|
||
|
}
|
||
|
|
||
|
// Start the listener
|
||
|
require.NoError(t, sock.Setup())
|
||
|
sock.Listen(onData, onError)
|
||
|
defer sock.Close()
|
||
|
|
||
|
addr := sock.Address()
|
||
|
|
||
|
// Create a noop client
|
||
|
client, err := createClient(serviceAddress, addr, nil)
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
_, err = client.Write([]byte("test value=42i\n"))
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
require.Eventually(t, func() bool {
|
||
|
acc.Lock()
|
||
|
defer acc.Unlock()
|
||
|
return acc.NMetrics() >= 1
|
||
|
}, time.Second, 100*time.Millisecond, "did not receive metric")
|
||
|
|
||
|
// This has to be a stream-listener...
|
||
|
listener, ok := sock.listener.(*streamListener)
|
||
|
require.True(t, ok)
|
||
|
listener.Lock()
|
||
|
conns := listener.connections
|
||
|
listener.Unlock()
|
||
|
require.NotZero(t, conns)
|
||
|
|
||
|
sock.Close()
|
||
|
|
||
|
// Verify that plugin.Stop() closed the client's connection
|
||
|
require.NoError(t, client.SetReadDeadline(time.Now().Add(time.Second)))
|
||
|
buf := []byte{1}
|
||
|
_, err = client.Read(buf)
|
||
|
require.Equal(t, err, io.EOF)
|
||
|
|
||
|
require.Empty(t, logger.Errors())
|
||
|
require.Empty(t, logger.Warnings())
|
||
|
}
|
||
|
func TestMaxConnections(t *testing.T) {
|
||
|
if runtime.GOOS == "darwin" {
|
||
|
t.Skip("Skipping on darwin due to missing socket options")
|
||
|
}
|
||
|
|
||
|
// Setup the configuration
|
||
|
period := config.Duration(10 * time.Millisecond)
|
||
|
cfg := &Config{
|
||
|
MaxConnections: 5,
|
||
|
KeepAlivePeriod: &period,
|
||
|
}
|
||
|
|
||
|
// Create the socket
|
||
|
serviceAddress := "tcp://127.0.0.1:0"
|
||
|
sock, err := cfg.NewSocket(serviceAddress, nil, &testutil.Logger{})
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
// Create callback
|
||
|
var errs []error
|
||
|
var mu sync.Mutex
|
||
|
onData := func(_ net.Addr, _ []byte, _ time.Time) {}
|
||
|
onError := func(err error) {
|
||
|
mu.Lock()
|
||
|
errs = append(errs, err)
|
||
|
mu.Unlock()
|
||
|
}
|
||
|
|
||
|
// Start the listener
|
||
|
require.NoError(t, sock.Setup())
|
||
|
sock.Listen(onData, onError)
|
||
|
defer sock.Close()
|
||
|
|
||
|
addr := sock.Address()
|
||
|
|
||
|
// Create maximum number of connections and write some data. All of this
|
||
|
// should succeed...
|
||
|
clients := make([]*net.TCPConn, 0, cfg.MaxConnections)
|
||
|
for i := 0; i < int(cfg.MaxConnections); i++ {
|
||
|
c, err := net.DialTCP("tcp", nil, addr.(*net.TCPAddr))
|
||
|
require.NoError(t, err)
|
||
|
require.NoError(t, c.SetWriteBuffer(0))
|
||
|
require.NoError(t, c.SetNoDelay(true))
|
||
|
clients = append(clients, c)
|
||
|
|
||
|
_, err = c.Write([]byte("test value=42i\n"))
|
||
|
require.NoError(t, err)
|
||
|
}
|
||
|
|
||
|
func() {
|
||
|
mu.Lock()
|
||
|
defer mu.Unlock()
|
||
|
require.Empty(t, errs)
|
||
|
}()
|
||
|
|
||
|
// Create another client. This should fail because we already reached the
|
||
|
// connection limit and the connection should be closed...
|
||
|
client, err := net.DialTCP("tcp", nil, addr.(*net.TCPAddr))
|
||
|
require.NoError(t, err)
|
||
|
require.NoError(t, client.SetWriteBuffer(0))
|
||
|
require.NoError(t, client.SetNoDelay(true))
|
||
|
|
||
|
require.Eventually(t, func() bool {
|
||
|
mu.Lock()
|
||
|
defer mu.Unlock()
|
||
|
return len(errs) > 0
|
||
|
}, 3*time.Second, 100*time.Millisecond)
|
||
|
func() {
|
||
|
mu.Lock()
|
||
|
defer mu.Unlock()
|
||
|
require.Len(t, errs, 1)
|
||
|
require.ErrorContains(t, errs[0], "too many connections")
|
||
|
errs = make([]error, 0)
|
||
|
}()
|
||
|
|
||
|
require.Eventually(t, func() bool {
|
||
|
_, err := client.Write([]byte("fail\n"))
|
||
|
return err != nil
|
||
|
}, 3*time.Second, 100*time.Millisecond)
|
||
|
_, err = client.Write([]byte("test\n"))
|
||
|
require.Error(t, err)
|
||
|
|
||
|
// Check other connections are still good
|
||
|
for _, c := range clients {
|
||
|
_, err := c.Write([]byte("test\n"))
|
||
|
require.NoError(t, err)
|
||
|
}
|
||
|
func() {
|
||
|
mu.Lock()
|
||
|
defer mu.Unlock()
|
||
|
require.Empty(t, errs)
|
||
|
}()
|
||
|
|
||
|
// Close the first client and check if we can connect now
|
||
|
require.NoError(t, clients[0].Close())
|
||
|
client, err = net.DialTCP("tcp", nil, addr.(*net.TCPAddr))
|
||
|
require.NoError(t, err)
|
||
|
require.NoError(t, client.SetWriteBuffer(0))
|
||
|
require.NoError(t, client.SetNoDelay(true))
|
||
|
_, err = client.Write([]byte("success\n"))
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
// Close all connections
|
||
|
require.NoError(t, client.Close())
|
||
|
for _, c := range clients[1:] {
|
||
|
require.NoError(t, c.Close())
|
||
|
}
|
||
|
|
||
|
// Close the clients and check the connection counter
|
||
|
listener, ok := sock.listener.(*streamListener)
|
||
|
require.True(t, ok)
|
||
|
require.Eventually(t, func() bool {
|
||
|
listener.Lock()
|
||
|
conns := listener.connections
|
||
|
listener.Unlock()
|
||
|
return conns == 0
|
||
|
}, 3*time.Second, 100*time.Millisecond)
|
||
|
|
||
|
// Close the socket and check again...
|
||
|
sock.Close()
|
||
|
listener.Lock()
|
||
|
conns := listener.connections
|
||
|
listener.Unlock()
|
||
|
require.Zero(t, conns)
|
||
|
}
|
||
|
|
||
|
func TestNoSplitter(t *testing.T) {
|
||
|
messages := [][]byte{
|
||
|
[]byte("test,foo=bar v"),
|
||
|
[]byte("=1i 123456789\ntest,foo=baz v=2i 123456790\ntest,foo=zab v=3i 123456791\n"),
|
||
|
}
|
||
|
expectedTemplates := []telegraf.Metric{
|
||
|
metric.New(
|
||
|
"test",
|
||
|
map[string]string{"foo": "bar"},
|
||
|
map[string]interface{}{"v": int64(1)},
|
||
|
time.Unix(0, 123456789),
|
||
|
),
|
||
|
metric.New(
|
||
|
"test",
|
||
|
map[string]string{"foo": "baz"},
|
||
|
map[string]interface{}{"v": int64(2)},
|
||
|
time.Unix(0, 123456790),
|
||
|
),
|
||
|
metric.New(
|
||
|
"test",
|
||
|
map[string]string{"foo": "zab"},
|
||
|
map[string]interface{}{"v": int64(3)},
|
||
|
time.Unix(0, 123456791),
|
||
|
),
|
||
|
}
|
||
|
|
||
|
// Prepare the address and socket if needed
|
||
|
serviceAddress := "tcp://127.0.0.1:0"
|
||
|
|
||
|
// Setup the configuration according to test specification
|
||
|
cfg := &Config{}
|
||
|
|
||
|
// Create the socket
|
||
|
sock, err := cfg.NewSocket(serviceAddress, nil, &testutil.Logger{})
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
// Create callbacks
|
||
|
parser := &influx.Parser{}
|
||
|
require.NoError(t, parser.Init())
|
||
|
|
||
|
var acc testutil.Accumulator
|
||
|
onConnection := func(remote net.Addr, reader io.ReadCloser) {
|
||
|
data, err := io.ReadAll(reader)
|
||
|
require.NoError(t, err)
|
||
|
m, err := parser.Parse(data)
|
||
|
require.NoError(t, err)
|
||
|
addr, _, err := net.SplitHostPort(remote.String())
|
||
|
if err != nil {
|
||
|
addr = remote.String()
|
||
|
}
|
||
|
for i := range m {
|
||
|
m[i].AddTag("source", addr)
|
||
|
}
|
||
|
acc.AddMetrics(m)
|
||
|
}
|
||
|
onError := func(err error) {
|
||
|
acc.AddError(err)
|
||
|
}
|
||
|
|
||
|
// Start the listener
|
||
|
require.NoError(t, sock.Setup())
|
||
|
sock.ListenConnection(onConnection, onError)
|
||
|
defer sock.Close()
|
||
|
|
||
|
addr := sock.Address()
|
||
|
|
||
|
// Create a noop client
|
||
|
// Server is async, so verify no errors at the end.
|
||
|
client, err := createClient(serviceAddress, addr, nil)
|
||
|
require.NoError(t, err)
|
||
|
require.NoError(t, client.Close())
|
||
|
|
||
|
// Setup the client for submitting data
|
||
|
client, err = createClient(serviceAddress, addr, nil)
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
// Conditionally add the source address to the expectation
|
||
|
expected := make([]telegraf.Metric, 0, len(expectedTemplates))
|
||
|
for _, tmpl := range expectedTemplates {
|
||
|
m := tmpl.Copy()
|
||
|
laddr := client.LocalAddr().String()
|
||
|
addr, _, err := net.SplitHostPort(laddr)
|
||
|
if err != nil {
|
||
|
addr = laddr
|
||
|
}
|
||
|
m.AddTag("source", addr)
|
||
|
expected = append(expected, m)
|
||
|
}
|
||
|
|
||
|
// Send the data
|
||
|
for i, msg := range messages {
|
||
|
_, err = client.Write(msg)
|
||
|
time.Sleep(100 * time.Millisecond)
|
||
|
require.NoErrorf(t, err, "sending msg %d failed", i)
|
||
|
}
|
||
|
client.Close()
|
||
|
|
||
|
// Test the resulting metrics and compare against expected results
|
||
|
require.Eventuallyf(t, func() bool {
|
||
|
acc.Lock()
|
||
|
defer acc.Unlock()
|
||
|
return acc.NMetrics() >= uint64(len(expected))
|
||
|
}, time.Second, 100*time.Millisecond, "did not receive metrics (%d)", acc.NMetrics())
|
||
|
|
||
|
actual := acc.GetTelegrafMetrics()
|
||
|
testutil.RequireMetricsEqual(t, expected, actual, testutil.SortMetrics())
|
||
|
}
|
||
|
|
||
|
func TestTLSMemLeak(t *testing.T) {
|
||
|
// For issue https://github.com/influxdata/telegraf/issues/15509
|
||
|
|
||
|
// Prepare the address and socket if needed
|
||
|
serviceAddress := "tcp://127.0.0.1:0"
|
||
|
|
||
|
// Setup a TLS socket to trigger the issue
|
||
|
cfg := &Config{
|
||
|
ServerConfig: *pki.TLSServerConfig(),
|
||
|
}
|
||
|
|
||
|
// Create the socket
|
||
|
sock, err := cfg.NewSocket(serviceAddress, nil, &testutil.Logger{})
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
// Create callbacks
|
||
|
onConnection := func(_ net.Addr, reader io.ReadCloser) {
|
||
|
//nolint:errcheck // We are not interested in the data so ignore all errors
|
||
|
io.Copy(io.Discard, reader)
|
||
|
}
|
||
|
|
||
|
// Start the listener
|
||
|
require.NoError(t, sock.Setup())
|
||
|
sock.ListenConnection(onConnection, nil)
|
||
|
defer sock.Close()
|
||
|
|
||
|
addr := sock.Address()
|
||
|
|
||
|
// Setup the client side TLS
|
||
|
tlsCfg, err := pki.TLSClientConfig().TLSConfig()
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
// Define a single client write sequence
|
||
|
data := []byte("test value=42i")
|
||
|
write := func() error {
|
||
|
conn, err := tls.Dial("tcp", addr.String(), tlsCfg)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
defer conn.Close()
|
||
|
|
||
|
_, err = conn.Write(data)
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Define a test with the given number of connections
|
||
|
maxConcurrency := runtime.GOMAXPROCS(0)
|
||
|
testCycle := func(connections int) (uint64, error) {
|
||
|
var mu sync.Mutex
|
||
|
var errs []error
|
||
|
var wg sync.WaitGroup
|
||
|
for count := 1; count < connections; count++ {
|
||
|
wg.Add(1)
|
||
|
go func() {
|
||
|
defer wg.Done()
|
||
|
if err := write(); err != nil {
|
||
|
mu.Lock()
|
||
|
errs = append(errs, err)
|
||
|
mu.Unlock()
|
||
|
}
|
||
|
}()
|
||
|
if count%maxConcurrency == 0 {
|
||
|
wg.Wait()
|
||
|
mu.Lock()
|
||
|
if len(errs) > 0 {
|
||
|
mu.Unlock()
|
||
|
return 0, errors.Join(errs...)
|
||
|
}
|
||
|
mu.Unlock()
|
||
|
}
|
||
|
}
|
||
|
//nolint:revive // We need to actively run the garbage collector to get reliable measurements
|
||
|
runtime.GC()
|
||
|
|
||
|
var stats runtime.MemStats
|
||
|
runtime.ReadMemStats(&stats)
|
||
|
return stats.HeapObjects, nil
|
||
|
}
|
||
|
|
||
|
// Measure the memory usage after a short warmup and after some time.
|
||
|
// The final number of heap objects should not exceed the number of
|
||
|
// runs by a save margin
|
||
|
|
||
|
// Warmup, do a low number of runs to initialize all data structures
|
||
|
// taking them out of the equation.
|
||
|
initial, err := testCycle(100)
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
// Do some more runs and make sure the memory growth is bound
|
||
|
final, err := testCycle(2000)
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
require.Less(t, final, 3*initial)
|
||
|
}
|
||
|
|
||
|
func createClient(endpoint string, addr net.Addr, tlsCfg *tls.Config) (net.Conn, error) {
|
||
|
// Determine the protocol in a crude fashion
|
||
|
parts := strings.SplitN(endpoint, "://", 2)
|
||
|
if len(parts) != 2 {
|
||
|
return nil, fmt.Errorf("invalid endpoint %q", endpoint)
|
||
|
}
|
||
|
protocol := parts[0]
|
||
|
|
||
|
if tlsCfg == nil {
|
||
|
return net.Dial(protocol, addr.String())
|
||
|
}
|
||
|
|
||
|
if protocol == "unix" {
|
||
|
tlsCfg.InsecureSkipVerify = true
|
||
|
}
|
||
|
return tls.Dial(protocol, addr.String(), tlsCfg)
|
||
|
}
|