1
0
Fork 0
telegraf/plugins/inputs/haproxy/haproxy_test.go
Daniel Baumann 4978089aab
Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-05-24 07:26:29 +02:00

344 lines
9 KiB
Go

package haproxy
import (
"crypto/rand"
"encoding/binary"
"fmt"
"net"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf/testutil"
)
func serverSocket(l net.Listener) {
for {
conn, err := l.Accept()
if err != nil {
return
}
go func(c net.Conn) {
defer c.Close()
buf := make([]byte, 1024)
n, err := c.Read(buf)
if err != nil {
return
}
data := buf[:n]
if string(data) == "show stat\n" {
c.Write(csvOutputSample) //nolint:errcheck // we return anyway
}
}(conn)
}
}
func TestHaproxyGeneratesMetricsWithAuthentication(t *testing.T) {
// We create a fake server to return test data
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth()
if !ok {
w.WriteHeader(http.StatusNotFound)
if _, err := fmt.Fprint(w, "Unauthorized"); err != nil {
w.WriteHeader(http.StatusInternalServerError)
t.Error(err)
return
}
return
}
if username == "user" && password == "password" {
if _, err := fmt.Fprint(w, string(csvOutputSample)); err != nil {
w.WriteHeader(http.StatusInternalServerError)
t.Error(err)
return
}
} else {
w.WriteHeader(http.StatusNotFound)
if _, err := fmt.Fprint(w, "Unauthorized"); err != nil {
w.WriteHeader(http.StatusInternalServerError)
t.Error(err)
return
}
}
}))
defer ts.Close()
// Now we tested again above server, with our authentication data
r := &HAProxy{
Servers: []string{strings.Replace(ts.URL, "http://", "http://user:password@", 1)},
}
var acc testutil.Accumulator
err := r.Gather(&acc)
require.NoError(t, err)
tags := map[string]string{
"server": ts.Listener.Addr().String(),
"proxy": "git",
"sv": "www",
"type": "server",
}
fields := haproxyGetFieldValues()
acc.AssertContainsTaggedFields(t, "haproxy", fields, tags)
// Here, we should get error because we don't pass authentication data
r = &HAProxy{
Servers: []string{ts.URL},
}
require.NoError(t, r.Gather(&acc))
require.NotEmpty(t, acc.Errors)
}
func TestHaproxyGeneratesMetricsWithoutAuthentication(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
if _, err := fmt.Fprint(w, string(csvOutputSample)); err != nil {
w.WriteHeader(http.StatusInternalServerError)
t.Error(err)
return
}
}))
defer ts.Close()
r := &HAProxy{
Servers: []string{ts.URL},
}
var acc testutil.Accumulator
require.NoError(t, r.Gather(&acc))
tags := map[string]string{
"server": ts.Listener.Addr().String(),
"proxy": "git",
"sv": "www",
"type": "server",
}
fields := haproxyGetFieldValues()
acc.AssertContainsTaggedFields(t, "haproxy", fields, tags)
}
func TestHaproxyGeneratesMetricsUsingSocket(t *testing.T) {
var randomNumber int64
var sockets [5]net.Listener
// The Maximum length of the socket path is 104/108 characters, path created with t.TempDir() is too long for some cases
// (it combines test name with subtest name and some random numbers in the path). Therefore, in this case, it is safer to stick with `os.MkdirTemp()`.
//nolint:usetesting // Ignore "os.TempDir() could be replaced by t.TempDir() in TestHaproxyGeneratesMetricsUsingSocket" finding.
tempDir := os.TempDir()
_globmask := filepath.Join(tempDir, "test-haproxy*.sock")
_badmask := filepath.Join(tempDir, "test-fail-haproxy*.sock")
for i := 0; i < 5; i++ {
require.NoError(t, binary.Read(rand.Reader, binary.LittleEndian, &randomNumber))
sockname := filepath.Join(tempDir, fmt.Sprintf("test-haproxy%d.sock", randomNumber))
sock, err := net.Listen("unix", sockname)
require.NoError(t, err, "Cannot initialize socket")
sockets[i] = sock
defer sock.Close() //nolint:revive,gocritic // done on purpose, closing will be executed properly
go serverSocket(sock)
}
r := &HAProxy{
Servers: []string{_globmask},
}
var acc testutil.Accumulator
err := r.Gather(&acc)
require.NoError(t, err)
fields := haproxyGetFieldValues()
for _, sock := range sockets {
tags := map[string]string{
"server": getSocketAddr(sock.Addr().String()),
"proxy": "git",
"sv": "www",
"type": "server",
}
acc.AssertContainsTaggedFields(t, "haproxy", fields, tags)
}
// This mask should not match any socket
r.Servers = []string{_badmask}
require.NoError(t, r.Gather(&acc))
require.NotEmpty(t, acc.Errors)
}
func TestHaproxyGeneratesMetricsUsingTcp(t *testing.T) {
l, err := net.Listen("tcp", "localhost:8192")
if err != nil {
t.Fatal(err)
}
defer l.Close()
go serverSocket(l)
r := &HAProxy{
Servers: []string{"tcp://" + l.Addr().String()},
}
var acc testutil.Accumulator
require.NoError(t, r.Gather(&acc))
fields := haproxyGetFieldValues()
tags := map[string]string{
"server": l.Addr().String(),
"proxy": "git",
"sv": "www",
"type": "server",
}
acc.AssertContainsTaggedFields(t, "haproxy", fields, tags)
require.NoError(t, r.Gather(&acc))
}
// When not passing server config, we default to localhost
// We just want to make sure we did request stat from localhost
func TestHaproxyDefaultGetFromLocalhost(t *testing.T) {
r := &HAProxy{}
var acc testutil.Accumulator
err := r.Gather(&acc)
require.Error(t, err)
require.Contains(t, err.Error(), "127.0.0.1:1936/haproxy?stats/;csv")
}
func TestHaproxyKeepFieldNames(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
if _, err := fmt.Fprint(w, string(csvOutputSample)); err != nil {
w.WriteHeader(http.StatusInternalServerError)
t.Error(err)
return
}
}))
defer ts.Close()
r := &HAProxy{
Servers: []string{ts.URL},
KeepFieldNames: true,
}
var acc testutil.Accumulator
require.NoError(t, r.Gather(&acc))
tags := map[string]string{
"server": ts.Listener.Addr().String(),
"pxname": "git",
"svname": "www",
"type": "server",
}
fields := haproxyGetFieldValues()
fields["act"] = fields["active_servers"]
delete(fields, "active_servers")
fields["bck"] = fields["backup_servers"]
delete(fields, "backup_servers")
fields["cli_abrt"] = fields["cli_abort"]
delete(fields, "cli_abort")
fields["srv_abrt"] = fields["srv_abort"]
delete(fields, "srv_abort")
fields["hrsp_1xx"] = fields["http_response.1xx"]
delete(fields, "http_response.1xx")
fields["hrsp_2xx"] = fields["http_response.2xx"]
delete(fields, "http_response.2xx")
fields["hrsp_3xx"] = fields["http_response.3xx"]
delete(fields, "http_response.3xx")
fields["hrsp_4xx"] = fields["http_response.4xx"]
delete(fields, "http_response.4xx")
fields["hrsp_5xx"] = fields["http_response.5xx"]
delete(fields, "http_response.5xx")
fields["hrsp_other"] = fields["http_response.other"]
delete(fields, "http_response.other")
acc.AssertContainsTaggedFields(t, "haproxy", fields, tags)
}
func mustReadSampleOutput() []byte {
filePath := "testdata/sample_output.csv"
data, err := os.ReadFile(filePath)
if err != nil {
panic(fmt.Errorf("could not read from file %s: %w", filePath, err))
}
return data
}
func haproxyGetFieldValues() map[string]interface{} {
fields := map[string]interface{}{
"active_servers": uint64(1),
"backup_servers": uint64(0),
"bin": uint64(5228218),
"bout": uint64(303747244),
"check_code": uint64(200),
"check_duration": uint64(3),
"check_fall": uint64(3),
"check_health": uint64(4),
"check_rise": uint64(2),
"check_status": "L7OK",
"chkdown": uint64(84),
"chkfail": uint64(559),
"cli_abort": uint64(690),
"ctime": uint64(1),
"downtime": uint64(3352),
"dresp": uint64(0),
"econ": uint64(0),
"eresp": uint64(21),
"http_response.1xx": uint64(0),
"http_response.2xx": uint64(5668),
"http_response.3xx": uint64(8710),
"http_response.4xx": uint64(140),
"http_response.5xx": uint64(0),
"http_response.other": uint64(0),
"iid": uint64(4),
"last_chk": "OK",
"lastchg": uint64(1036557),
"lastsess": int64(1342),
"lbtot": uint64(9481),
"mode": "http",
"pid": uint64(1),
"qcur": uint64(0),
"qmax": uint64(0),
"qtime": uint64(1268),
"rate": uint64(0),
"rate_max": uint64(2),
"rtime": uint64(2908),
"sid": uint64(1),
"scur": uint64(0),
"slim": uint64(2),
"smax": uint64(2),
"srv_abort": uint64(0),
"status": "UP",
"stot": uint64(14539),
"ttime": uint64(4500),
"weight": uint64(1),
"wredis": uint64(0),
"wretr": uint64(0),
}
return fields
}
// Can obtain from official haproxy demo: 'http://demo.haproxy.org/;csv'
var csvOutputSample = mustReadSampleOutput()