mirror of
https://github.com/hanzoai/traces.git
synced 2026-10-30 18:20:41 +00:00
313 lines
7.0 KiB
Go
313 lines
7.0 KiB
Go
package otlpgrpc
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"testing"
|
|
|
|
"github.com/go-kit/log"
|
|
"github.com/grafana/dskit/user"
|
|
"github.com/stretchr/testify/require"
|
|
"go.opentelemetry.io/collector/pdata/ptrace"
|
|
"go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp"
|
|
"go.uber.org/atomic"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/test/bufconn"
|
|
)
|
|
|
|
type mockGRPCServer struct {
|
|
ptraceotlp.UnimplementedGRPCServer
|
|
req ptraceotlp.ExportRequest
|
|
}
|
|
|
|
func (m *mockGRPCServer) Export(_ context.Context, req ptraceotlp.ExportRequest) (ptraceotlp.ExportResponse, error) {
|
|
m.req = req
|
|
return ptraceotlp.NewExportResponse(), nil
|
|
}
|
|
|
|
func newForwarder(t *testing.T, cfg Config, logger log.Logger) *Forwarder {
|
|
t.Helper()
|
|
|
|
f, err := NewForwarder(cfg, logger)
|
|
require.NoError(t, err)
|
|
|
|
t.Cleanup(func() {
|
|
require.NoError(t, f.Shutdown(context.Background()))
|
|
})
|
|
|
|
return f
|
|
}
|
|
|
|
func newListener(t *testing.T, srv ptraceotlp.GRPCServer) *bufconn.Listener {
|
|
t.Helper()
|
|
|
|
const size = 1024 * 1024
|
|
l := bufconn.Listen(size)
|
|
t.Cleanup(func() {
|
|
err := l.Close()
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
s := grpc.NewServer()
|
|
t.Cleanup(func() {
|
|
s.GracefulStop()
|
|
})
|
|
|
|
ptraceotlp.RegisterGRPCServer(s, srv)
|
|
go func() {
|
|
require.NoError(t, s.Serve(l))
|
|
}()
|
|
|
|
return l
|
|
}
|
|
|
|
type contextDialer func(context.Context, string) (net.Conn, error)
|
|
|
|
func newContextDialer(l *bufconn.Listener) contextDialer {
|
|
return func(ctx context.Context, _ string) (net.Conn, error) {
|
|
return l.DialContext(ctx)
|
|
}
|
|
}
|
|
|
|
type countingConn struct {
|
|
net.Conn
|
|
closeCount atomic.Int32
|
|
waitClose chan struct{}
|
|
}
|
|
|
|
func (c *countingConn) Close() error {
|
|
defer func() {
|
|
select {
|
|
case <-c.waitClose:
|
|
return
|
|
default:
|
|
}
|
|
|
|
close(c.waitClose)
|
|
}()
|
|
|
|
c.closeCount.Add(1)
|
|
return c.Conn.Close()
|
|
}
|
|
|
|
func newContextDialerWithCountingConn(l *bufconn.Listener) (contextDialer, *countingConn) {
|
|
countingConn := &countingConn{
|
|
waitClose: make(chan struct{}),
|
|
}
|
|
|
|
return func(ctx context.Context, _ string) (net.Conn, error) {
|
|
conn, err := l.DialContext(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
countingConn.Conn = conn
|
|
|
|
return countingConn, nil
|
|
}, countingConn
|
|
}
|
|
|
|
type fatalError struct{}
|
|
|
|
func (fatalError) Error() string { return "context dialer error" }
|
|
func (fatalError) Temporary() bool { return false }
|
|
|
|
func newFailingContextDialer() contextDialer {
|
|
return func(ctx context.Context, s string) (net.Conn, error) {
|
|
return nil, fatalError{}
|
|
}
|
|
}
|
|
|
|
func TestNewForwarder_ReturnsNoErrorAndNonNilForwarderWithValidConfig(t *testing.T) {
|
|
// Given
|
|
cfg := Config{
|
|
Endpoints: nil,
|
|
TLS: TLSConfig{Insecure: true},
|
|
}
|
|
logger := log.NewNopLogger()
|
|
|
|
// When
|
|
got, err := NewForwarder(cfg, logger)
|
|
|
|
// Then
|
|
require.NoError(t, err)
|
|
require.NotNil(t, got)
|
|
}
|
|
|
|
func TestNewForwarder_ReturnsErrorAndNilForwarderWithInvalidConfig(t *testing.T) {
|
|
// Given
|
|
cfg := Config{
|
|
Endpoints: nil,
|
|
TLS: TLSConfig{Insecure: false},
|
|
}
|
|
logger := log.NewNopLogger()
|
|
|
|
// When
|
|
got, err := NewForwarder(cfg, logger)
|
|
|
|
// Then
|
|
require.Error(t, err)
|
|
require.Nil(t, got)
|
|
}
|
|
|
|
func Test_Forwarder_Dial_ReturnsNoErrorWithWorkingDialer(t *testing.T) {
|
|
// Given
|
|
cfg := Config{
|
|
Endpoints: []string{"test:1234"},
|
|
TLS: TLSConfig{Insecure: true},
|
|
}
|
|
logger := log.NewNopLogger()
|
|
f := newForwarder(t, cfg, logger)
|
|
l := newListener(t, nil)
|
|
d := newContextDialer(l)
|
|
|
|
// When
|
|
err := f.Dial(context.Background(), grpc.WithContextDialer(d), grpc.WithBlock())
|
|
|
|
// Then
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func Test_Forwarder_Dial_ReturnsErrorWithFailingContextDialer(t *testing.T) {
|
|
// Given
|
|
cfg := Config{
|
|
Endpoints: []string{"test:1234"},
|
|
TLS: TLSConfig{Insecure: true},
|
|
}
|
|
logger := log.NewNopLogger()
|
|
f := newForwarder(t, cfg, logger)
|
|
d := newFailingContextDialer()
|
|
|
|
// When
|
|
err := f.Dial(
|
|
context.Background(),
|
|
grpc.WithContextDialer(d),
|
|
grpc.WithBlock(),
|
|
grpc.FailOnNonTempDialError(true),
|
|
)
|
|
|
|
// Then
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func Test_Forwarder_Dial_ReturnsErrorWithCancelledContext(t *testing.T) {
|
|
// Given
|
|
cfg := Config{
|
|
Endpoints: []string{"test:1234"},
|
|
TLS: TLSConfig{Insecure: true},
|
|
}
|
|
logger := log.NewNopLogger()
|
|
f := newForwarder(t, cfg, logger)
|
|
l := newListener(t, nil)
|
|
d := newContextDialer(l)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
// Ensure the server is ready
|
|
err := f.Dial(ctx, grpc.WithContextDialer(d), grpc.WithBlock(), grpc.FailOnNonTempDialError(true))
|
|
require.NoError(t, err)
|
|
|
|
cancel()
|
|
|
|
// When
|
|
err = f.Dial(ctx, grpc.WithContextDialer(d), grpc.WithBlock(), grpc.FailOnNonTempDialError(true))
|
|
|
|
// Then
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func Test_Forwarder_Dial_ReturnsErrorWhenCalledSecondTime(t *testing.T) {
|
|
// Given
|
|
cfg := Config{
|
|
Endpoints: []string{"test:1234"},
|
|
TLS: TLSConfig{Insecure: true},
|
|
}
|
|
logger := log.NewNopLogger()
|
|
f := newForwarder(t, cfg, logger)
|
|
l := newListener(t, nil)
|
|
d := newContextDialer(l)
|
|
|
|
// When
|
|
err := f.Dial(context.Background(), grpc.WithContextDialer(d), grpc.WithBlock())
|
|
|
|
// Then
|
|
require.NoError(t, err)
|
|
|
|
// When
|
|
err = f.Dial(context.Background(), grpc.WithContextDialer(d), grpc.WithBlock())
|
|
|
|
// Then
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func Test_Forwarder_ForwardTraces_ReturnsNoErrorAndSentTracesMatchReceivedTraces(t *testing.T) {
|
|
// Given
|
|
cfg := Config{
|
|
Endpoints: []string{"test:1234"},
|
|
TLS: TLSConfig{Insecure: true},
|
|
}
|
|
logger := log.NewNopLogger()
|
|
f := newForwarder(t, cfg, logger)
|
|
srv := &mockGRPCServer{}
|
|
l := newListener(t, srv)
|
|
d := newContextDialer(l)
|
|
err := f.Dial(context.Background(), grpc.WithContextDialer(d), grpc.WithBlock())
|
|
require.NoError(t, err)
|
|
traces := ptrace.NewTraces()
|
|
traces.ResourceSpans().AppendEmpty().SetSchemaUrl("testURL")
|
|
ctx := user.InjectOrgID(context.Background(), "123")
|
|
|
|
// When
|
|
err = f.ForwardTraces(ctx, traces)
|
|
|
|
// Then
|
|
require.NoError(t, err)
|
|
require.Equal(t, traces, srv.req.Traces())
|
|
}
|
|
|
|
func Test_Forwarder_ForwardTraces_ReturnsErrorWithNoOrgIDInContext(t *testing.T) {
|
|
// Given
|
|
cfg := Config{
|
|
Endpoints: []string{"test:1234"},
|
|
TLS: TLSConfig{Insecure: true},
|
|
}
|
|
logger := log.NewNopLogger()
|
|
f := newForwarder(t, cfg, logger)
|
|
srv := &mockGRPCServer{}
|
|
l := newListener(t, srv)
|
|
d := newContextDialer(l)
|
|
err := f.Dial(context.Background(), grpc.WithContextDialer(d), grpc.WithBlock())
|
|
require.NoError(t, err)
|
|
traces := ptrace.NewTraces()
|
|
traces.ResourceSpans().AppendEmpty().SetSchemaUrl("testURL")
|
|
|
|
// When
|
|
err = f.ForwardTraces(context.Background(), traces)
|
|
|
|
// Then
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func Test_Forwarder_Shutdown_CallsCloseOnConnection(t *testing.T) {
|
|
// Given
|
|
cfg := Config{
|
|
Endpoints: []string{"test:1234"},
|
|
TLS: TLSConfig{Insecure: true},
|
|
}
|
|
logger := log.NewNopLogger()
|
|
f, err := NewForwarder(cfg, logger)
|
|
require.NoError(t, err)
|
|
srv := &mockGRPCServer{}
|
|
l := newListener(t, srv)
|
|
d, conn := newContextDialerWithCountingConn(l)
|
|
err = f.Dial(context.Background(), grpc.WithContextDialer(d), grpc.WithBlock())
|
|
require.NoError(t, err)
|
|
|
|
// When
|
|
err = f.Shutdown(context.Background())
|
|
|
|
// Then
|
|
<-conn.waitClose
|
|
require.NoError(t, err)
|
|
require.GreaterOrEqual(t, conn.closeCount.Load(), int32(1))
|
|
}
|