Skip to content

Commit 48aa0dd

Browse files
authored
[chore][receiver/statsd] Refactor of hardcoded tests (#28896)
**Description:** This is the first step to adding UDS support to the StatsD receiver. As I started to develop it, I saw that all tests were hardcoded to UDP and using networking and there was no possibility to add a socket communication. PR started to get huge so I split it into two, one to refactor tests and another one that adds UDS support: * Removed all unused references. * Made a `Transport` helper that allows centralizing all supported protocols and constants in its package. * Removed all hardcoded UDP protocols and generalized testing so new protocols are easy to add. If you need a rationale about why the changes are like this, this is the next PR I am going to submit after this one is merged: kilokang#2 That is the PR that is going to add UDS support properly. **Link to tracking Issue:** - #21385 **Previous closed PR:** - #24832
1 parent 3e81d24 commit 48aa0dd

File tree

9 files changed

+206
-143
lines changed

9 files changed

+206
-143
lines changed

receiver/statsdreceiver/config_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ func TestValidate(t *testing.T) {
9393
noObjectNameErr = "must specify object id for all TimerHistogramMappings"
9494
statsdTypeNotSupportErr = "statsd_type is not a supported mapping for histogram and timing metrics: %s"
9595
observerTypeNotSupportErr = "observer_type is not supported for histogram and timing metrics: %s"
96+
invalidHistogramErr = "histogram configuration requires observer_type: histogram"
9697
)
9798

9899
tests := []test{
@@ -160,7 +161,7 @@ func TestValidate(t *testing.T) {
160161
},
161162
},
162163
},
163-
expectedErr: "histogram configuration requires observer_type: histogram",
164+
expectedErr: invalidHistogramErr,
164165
},
165166
{
166167
name: "negativeAggregationInterval",
@@ -170,7 +171,7 @@ func TestValidate(t *testing.T) {
170171
{StatsdType: "timing", ObserverType: "gauge"},
171172
},
172173
},
173-
expectedErr: "aggregation_interval must be a positive duration",
174+
expectedErr: negativeAggregationIntervalErr,
174175
},
175176
}
176177

receiver/statsdreceiver/internal/transport/client/client.go

Lines changed: 25 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -7,90 +7,72 @@ import (
77
"fmt"
88
"io"
99
"net"
10+
"strings"
1011
)
1112

1213
// StatsD defines the properties of a StatsD connection.
1314
type StatsD struct {
14-
Host string
15-
Port int
16-
Conn io.Writer
15+
transport string
16+
address string
17+
conn io.Writer
1718
}
1819

19-
// Transport is an enum to select the type of transport.
20-
type Transport int
21-
22-
const (
23-
// TCP Transport
24-
TCP Transport = iota
25-
// UDP Transport
26-
UDP
27-
)
28-
2920
// NewStatsD creates a new StatsD instance to support the need for testing
3021
// the statsdreceiver package and is not intended/tested to be used in production.
31-
func NewStatsD(transport Transport, host string, port int) (*StatsD, error) {
22+
func NewStatsD(transport string, address string) (*StatsD, error) {
3223
statsd := &StatsD{
33-
Host: host,
34-
Port: port,
24+
transport: transport,
25+
address: address,
3526
}
36-
err := statsd.connect(transport)
27+
28+
err := statsd.connect()
3729
if err != nil {
3830
return nil, err
3931
}
4032

4133
return statsd, nil
4234
}
4335

44-
// connect populates the StatsD.Conn
45-
func (s *StatsD) connect(transport Transport) error {
46-
if cl, ok := s.Conn.(io.Closer); ok {
47-
err := cl.Close()
36+
// connect populates the StatsD.conn
37+
func (s *StatsD) connect() error {
38+
switch s.transport {
39+
case "udp":
40+
udpAddr, err := net.ResolveUDPAddr(s.transport, s.address)
4841
if err != nil {
4942
return err
5043
}
51-
}
52-
53-
address := fmt.Sprintf("%s:%d", s.Host, s.Port)
54-
55-
var err error
56-
switch transport {
57-
case TCP:
58-
s.Conn, err = net.Dial("tcp", address)
59-
if err != nil {
60-
return err
61-
}
62-
case UDP:
63-
var udpAddr *net.UDPAddr
64-
udpAddr, err = net.ResolveUDPAddr("udp", address)
44+
s.conn, err = net.DialUDP(s.transport, nil, udpAddr)
6545
if err != nil {
6646
return err
6747
}
68-
s.Conn, err = net.DialUDP("udp", nil, udpAddr)
48+
case "tcp":
49+
var err error
50+
s.conn, err = net.Dial(s.transport, s.address)
6951
if err != nil {
7052
return err
7153
}
7254
default:
73-
return fmt.Errorf("unknown transport: %d", transport)
55+
return fmt.Errorf("unknown/unsupported transport: %s", s.transport)
7456
}
7557

76-
return err
58+
return nil
7759
}
7860

79-
// Disconnect closes the StatsD.Conn.
61+
// Disconnect closes the StatsD.conn.
8062
func (s *StatsD) Disconnect() error {
8163
var err error
82-
if cl, ok := s.Conn.(io.Closer); ok {
64+
if cl, ok := s.conn.(io.Closer); ok {
8365
err = cl.Close()
8466
}
85-
s.Conn = nil
67+
s.conn = nil
8668
return err
8769
}
8870

8971
// SendMetric sends the input metric to the StatsD connection.
9072
func (s *StatsD) SendMetric(metric Metric) error {
91-
_, err := fmt.Fprint(s.Conn, metric.String())
73+
_, err := io.Copy(s.conn, strings.NewReader(metric.String()))
9274
if err != nil {
93-
return err
75+
return fmt.Errorf("send metric on test client: %w", err)
9476
}
9577
return nil
9678
}

receiver/statsdreceiver/internal/transport/server.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ import (
88
"net"
99

1010
"go.opentelemetry.io/collector/consumer"
11-
12-
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/statsdreceiver/internal/protocol"
1311
)
1412

1513
var errNilListenAndServeParameters = errors.New("no parameter of ListenAndServe can be nil")
@@ -21,7 +19,6 @@ type Server interface {
2119
// on the specific transport, and prepares the message to be processed by
2220
// the Parser and passed to the next consumer.
2321
ListenAndServe(
24-
p protocol.Parser,
2522
mc consumer.Metrics,
2623
r Reporter,
2724
transferChan chan<- Metric,

receiver/statsdreceiver/internal/transport/server_test.go

Lines changed: 55 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
package transport
55

66
import (
7+
"io"
78
"net"
89
"runtime"
9-
"strconv"
1010
"sync"
1111
"testing"
1212
"time"
@@ -16,65 +16,43 @@ import (
1616
"go.opentelemetry.io/collector/consumer/consumertest"
1717

1818
"github.com/open-telemetry/opentelemetry-collector-contrib/internal/common/testutil"
19-
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/statsdreceiver/internal/protocol"
2019
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/statsdreceiver/internal/transport/client"
2120
)
2221

2322
func Test_Server_ListenAndServe(t *testing.T) {
2423
tests := []struct {
25-
name string
26-
transport string
27-
buildServerFn func(addr string) (Server, error)
28-
buildClientFn func(host string, port int) (*client.StatsD, error)
24+
name string
25+
transport Transport
26+
buildServerFn func(transport Transport, addr string) (Server, error)
27+
getFreeEndpointFn func(t testing.TB, transport string) string
28+
buildClientFn func(transport string, address string) (*client.StatsD, error)
2929
}{
3030
{
31-
name: "udp",
32-
transport: "udp",
33-
buildServerFn: NewUDPServer,
34-
buildClientFn: func(host string, port int) (*client.StatsD, error) {
35-
return client.NewStatsD(client.UDP, host, port)
36-
},
31+
name: "udp",
32+
transport: UDP,
33+
getFreeEndpointFn: testutil.GetAvailableLocalNetworkAddress,
34+
buildServerFn: NewUDPServer,
35+
buildClientFn: client.NewStatsD,
3736
},
3837
{
39-
name: "tcp",
40-
transport: "tcp",
41-
buildServerFn: NewTCPServer,
42-
buildClientFn: func(host string, port int) (*client.StatsD, error) {
43-
return client.NewStatsD(client.TCP, host, port)
44-
},
38+
name: "tcp",
39+
transport: TCP,
40+
getFreeEndpointFn: testutil.GetAvailableLocalNetworkAddress,
41+
buildServerFn: NewTCPServer,
42+
buildClientFn: client.NewStatsD,
4543
},
4644
}
45+
4746
for _, tt := range tests {
4847
t.Run(tt.name, func(t *testing.T) {
49-
addr := testutil.GetAvailableLocalNetworkAddress(t, tt.transport)
50-
51-
if tt.transport == "udp" {
52-
// Endpoint should be free.
53-
ln0, err := net.ListenPacket("udp", addr)
54-
require.NoError(t, err)
55-
require.NotNil(t, ln0)
56-
57-
// Ensure that the endpoint wasn't something like ":0" by checking that a second listener will fail.
58-
ln1, err := net.ListenPacket("udp", addr)
59-
require.Error(t, err)
60-
require.Nil(t, ln1)
48+
addr := tt.getFreeEndpointFn(t, tt.name)
49+
testFreeEndpoint(t, tt.name, addr)
6150

62-
// Unbind the local address so the mock UDP service can use it
63-
err = ln0.Close()
64-
require.NoError(t, err)
65-
}
66-
67-
srv, err := tt.buildServerFn(addr)
51+
srv, err := tt.buildServerFn(tt.transport, addr)
6852
require.NoError(t, err)
6953
require.NotNil(t, srv)
7054

71-
host, portStr, err := net.SplitHostPort(addr)
72-
require.NoError(t, err)
73-
port, err := strconv.Atoi(portStr)
74-
require.NoError(t, err)
75-
7655
mc := new(consumertest.MetricsSink)
77-
p := &protocol.StatsDParser{}
7856
require.NoError(t, err)
7957
mr := NewMockReporter(1)
8058
transferChan := make(chan Metric, 10)
@@ -83,12 +61,12 @@ func Test_Server_ListenAndServe(t *testing.T) {
8361
wgListenAndServe.Add(1)
8462
go func() {
8563
defer wgListenAndServe.Done()
86-
assert.Error(t, srv.ListenAndServe(p, mc, mr, transferChan))
64+
assert.Error(t, srv.ListenAndServe(mc, mr, transferChan))
8765
}()
8866

8967
runtime.Gosched()
9068

91-
gc, err := tt.buildClientFn(host, port)
69+
gc, err := tt.buildClientFn(tt.transport.String(), addr)
9270
require.NoError(t, err)
9371
require.NotNil(t, gc)
9472
err = gc.SendMetric(client.Metric{
@@ -115,3 +93,36 @@ func Test_Server_ListenAndServe(t *testing.T) {
11593
})
11694
}
11795
}
96+
97+
func testFreeEndpoint(t *testing.T, transport string, address string) {
98+
t.Helper()
99+
100+
var ln0, ln1 io.Closer
101+
var err0, err1 error
102+
103+
trans := NewTransport(transport)
104+
require.NotEqual(t, trans, Transport(""))
105+
106+
if trans.IsPacketTransport() {
107+
// Endpoint should be free.
108+
ln0, err0 = net.ListenPacket(transport, address)
109+
ln1, err1 = net.ListenPacket(transport, address)
110+
}
111+
112+
if trans.IsStreamTransport() {
113+
// Endpoint should be free.
114+
ln0, err0 = net.Listen(transport, address)
115+
ln1, err1 = net.Listen(transport, address)
116+
}
117+
118+
// Endpoint should be free.
119+
require.NoError(t, err0)
120+
require.NotNil(t, ln0)
121+
122+
// Ensure that the endpoint wasn't something like ":0" by checking that a second listener will fail.
123+
require.Error(t, err1)
124+
require.Nil(t, ln1)
125+
126+
// Unbind the local address so the mock UDP service can use it
127+
require.NoError(t, ln0.Close())
128+
}

receiver/statsdreceiver/internal/transport/tcp_server.go

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,43 +6,50 @@ package transport // import "github.com/open-telemetry/opentelemetry-collector-c
66
import (
77
"bytes"
88
"errors"
9+
"fmt"
910
"io"
1011
"net"
1112
"strings"
1213
"sync"
1314

1415
"go.opentelemetry.io/collector/consumer"
15-
16-
"github.com/open-telemetry/opentelemetry-collector-contrib/receiver/statsdreceiver/internal/protocol"
1716
)
1817

1918
var errTCPServerDone = errors.New("server stopped")
2019

2120
type tcpServer struct {
22-
listener net.Listener
23-
reporter Reporter
24-
wg sync.WaitGroup
25-
stopChan chan struct{}
21+
listener net.Listener
22+
reporter Reporter
23+
wg sync.WaitGroup
24+
transport Transport
25+
stopChan chan struct{}
2626
}
2727

28+
// Ensure that Server is implemented on TCP Server.
2829
var _ Server = (*tcpServer)(nil)
2930

3031
// NewTCPServer creates a transport.Server using TCP as its transport.
31-
func NewTCPServer(addr string) (Server, error) {
32-
l, err := net.Listen("tcp", addr)
33-
if err != nil {
34-
return nil, err
32+
func NewTCPServer(transport Transport, address string) (Server, error) {
33+
var tsrv tcpServer
34+
var err error
35+
36+
if !transport.IsStreamTransport() {
37+
return nil, fmt.Errorf("NewTCPServer with %s: %w", transport.String(), ErrUnsupportedStreamTransport)
3538
}
3639

37-
t := tcpServer{
38-
listener: l,
39-
stopChan: make(chan struct{}),
40+
tsrv.transport = transport
41+
tsrv.listener, err = net.Listen(transport.String(), address)
42+
if err != nil {
43+
return nil, fmt.Errorf("starting to listen %s socket: %w", transport.String(), err)
4044
}
41-
return &t, nil
45+
46+
tsrv.stopChan = make(chan struct{})
47+
return &tsrv, nil
4248
}
4349

44-
func (t *tcpServer) ListenAndServe(parser protocol.Parser, nextConsumer consumer.Metrics, reporter Reporter, transferChan chan<- Metric) error {
45-
if parser == nil || nextConsumer == nil || reporter == nil {
50+
// ListenAndServe starts the server ready to receive metrics.
51+
func (t *tcpServer) ListenAndServe(nextConsumer consumer.Metrics, reporter Reporter, transferChan chan<- Metric) error {
52+
if nextConsumer == nil || reporter == nil {
4653
return errNilListenAndServeParameters
4754
}
4855

@@ -71,6 +78,7 @@ LOOP:
7178
return errTCPServerDone
7279
}
7380

81+
// handleConn is helper that parses the buffer and split it line by line to be parsed upstream.
7482
func (t *tcpServer) handleConn(c net.Conn, transferChan chan<- Metric) {
7583
payload := make([]byte, 4096)
7684
var remainder []byte
@@ -98,6 +106,7 @@ func (t *tcpServer) handleConn(c net.Conn, transferChan chan<- Metric) {
98106
}
99107
}
100108

109+
// Close closes the server.
101110
func (t *tcpServer) Close() error {
102111
close(t.stopChan)
103112
t.wg.Wait()

0 commit comments

Comments
 (0)