11package client
22
33import (
4+ "context"
5+ "fmt"
6+ "net/http"
7+ "net/url"
48 "strings"
59 "testing"
610 "time"
@@ -17,9 +21,148 @@ import (
1721 "github.com/stretchr/testify/require"
1822
1923 "github.com/grafana/alloy/internal/component/common/loki"
24+ "github.com/grafana/alloy/internal/component/common/loki/utils"
2025 "github.com/grafana/alloy/internal/loki/util"
2126)
2227
28+ func TestFanoutConsumer (t * testing.T ) {
29+ testClientConfig , rwReceivedReqs , closeServer := newServerAndClientConfig (t )
30+
31+ consumer , err := NewFanoutConsumer (log .NewNopLogger (), prometheus .NewRegistry (), testClientConfig )
32+ require .NoError (t , err )
33+
34+ receivedRequests := utils .NewSyncSlice [utils.RemoteWriteRequest ]()
35+ go func () {
36+ for req := range rwReceivedReqs {
37+ receivedRequests .Append (req )
38+ }
39+ }()
40+
41+ defer func () {
42+ consumer .Stop ()
43+ closeServer ()
44+ }()
45+
46+ var testLabels = model.LabelSet {
47+ "pizza-flavour" : "fugazzeta" ,
48+ }
49+ var totalLines = 100
50+ for i := range totalLines {
51+ consumer .Chan () <- loki.Entry {
52+ Labels : testLabels ,
53+ Entry : push.Entry {
54+ Timestamp : time .Now (),
55+ Line : fmt .Sprintf ("line%d" , i ),
56+ },
57+ }
58+ }
59+
60+ require .Eventually (t , func () bool {
61+ return receivedRequests .Length () == totalLines
62+ }, 5 * time .Second , time .Second , "timed out waiting for requests to be received" )
63+
64+ var seenEntries = map [string ]struct {}{}
65+ // assert over rw client received entries
66+ defer receivedRequests .DoneIterate ()
67+ for _ , req := range receivedRequests .StartIterate () {
68+ require .Len (t , req .Request .Streams , 1 , "expected 1 stream requests to be received" )
69+ require .Len (t , req .Request .Streams [0 ].Entries , 1 , "expected 1 entry in the only stream received per request" )
70+ require .Equal (t , `{pizza-flavour="fugazzeta"}` , req .Request .Streams [0 ].Labels )
71+ seenEntries [req .Request .Streams [0 ].Entries [0 ].Line ] = struct {}{}
72+ }
73+ require .Len (t , seenEntries , totalLines )
74+ }
75+
76+ func TestFanoutConsumer_MultipleConfigs (t * testing.T ) {
77+ testClientConfig , rwReceivedReqs , closeServer := newServerAndClientConfig (t )
78+ testClientConfig2 , rwReceivedReqs2 , closeServer2 := newServerAndClientConfig (t )
79+ testClientConfig2 .Name = "test-client-2"
80+
81+ // start writer and consumer
82+ consumer , err := NewFanoutConsumer (log .NewNopLogger (), prometheus .NewRegistry (), testClientConfig , testClientConfig2 )
83+ require .NoError (t , err )
84+
85+ receivedRequests := utils .NewSyncSlice [utils.RemoteWriteRequest ]()
86+ ctx , cancel := context .WithCancel (t .Context ())
87+ go func (ctx context.Context ) {
88+ for {
89+ select {
90+ case req := <- rwReceivedReqs :
91+ receivedRequests .Append (req )
92+ case req := <- rwReceivedReqs2 :
93+ receivedRequests .Append (req )
94+ case <- ctx .Done ():
95+ return
96+ }
97+ }
98+ }(ctx )
99+
100+ defer func () {
101+ consumer .Stop ()
102+ closeServer ()
103+ closeServer2 ()
104+ cancel ()
105+ }()
106+
107+ var testLabels = model.LabelSet {
108+ "pizza-flavour" : "fugazzeta" ,
109+ }
110+ var totalLines = 100
111+ for i := range totalLines {
112+ consumer .Chan () <- loki.Entry {
113+ Labels : testLabels ,
114+ Entry : push.Entry {
115+ Timestamp : time .Now (),
116+ Line : fmt .Sprintf ("line%d" , i ),
117+ },
118+ }
119+ }
120+
121+ // times 2 due to clients being run
122+ expectedTotalLines := totalLines * 2
123+ require .Eventually (t , func () bool {
124+ return receivedRequests .Length () == expectedTotalLines
125+ }, 5 * time .Second , time .Second , "timed out waiting for requests to be received" )
126+
127+ var seenEntries int
128+ // assert over rw client received entries
129+ defer receivedRequests .DoneIterate ()
130+ for _ , req := range receivedRequests .StartIterate () {
131+ require .Len (t , req .Request .Streams , 1 , "expected 1 stream requests to be received" )
132+ require .Len (t , req .Request .Streams [0 ].Entries , 1 , "expected 1 entry in the only stream received per request" )
133+ seenEntries += 1
134+ }
135+ require .Equal (t , seenEntries , expectedTotalLines )
136+ }
137+
138+ func TestFanoutConsumer_InvalidConfig (t * testing.T ) {
139+ t .Run ("no clients" , func (t * testing.T ) {
140+ _ , err := NewFanoutConsumer (log .NewNopLogger (), prometheus .NewRegistry ())
141+ require .Error (t , err )
142+ })
143+
144+ t .Run ("repeated client" , func (t * testing.T ) {
145+ host , _ := url .Parse ("http://localhost:3100" )
146+ config := Config {URL : flagext.URLValue {URL : host }}
147+ _ , err := NewFanoutConsumer (log .NewNopLogger (), prometheus .NewRegistry (), config , config )
148+ require .Error (t , err )
149+ })
150+ }
151+
152+ func TestFanoutConsumer_NoDuplicateMetricsPanic (t * testing.T ) {
153+ var (
154+ host , _ = url .Parse ("http://localhost:3100" )
155+ reg = prometheus .NewRegistry ()
156+ )
157+
158+ require .NotPanics (t , func () {
159+ for range 2 {
160+ _ , err := NewFanoutConsumer (log .NewNopLogger (), reg , Config {URL : flagext.URLValue {URL : host }})
161+ require .NoError (t , err )
162+ }
163+ })
164+ }
165+
23166var logEntries = []loki.Entry {
24167 {Labels : model.LabelSet {}, Entry : push.Entry {Timestamp : time .Unix (1 , 0 ).UTC (), Line : "line1" }},
25168 {Labels : model.LabelSet {}, Entry : push.Entry {Timestamp : time .Unix (2 , 0 ).UTC (), Line : "line2" }},
@@ -435,7 +578,7 @@ func TestClient_Handle(t *testing.T) {
435578 }
436579
437580 m := NewMetrics (reg )
438- c , err := New (m , cfg , log .NewNopLogger ())
581+ c , err := newClient (m , cfg , log .NewNopLogger ())
439582 require .NoError (t , err )
440583
441584 // Send all the input log entries
@@ -471,3 +614,30 @@ func TestClient_Handle(t *testing.T) {
471614 })
472615 }
473616}
617+
618+ func newServerAndClientConfig (t * testing.T ) (Config , chan utils.RemoteWriteRequest , func ()) {
619+ receivedReqsChan := make (chan utils.RemoteWriteRequest , 10 )
620+
621+ // Start a local HTTP server
622+ server := utils .NewRemoteWriteServer (receivedReqsChan , http .StatusOK )
623+ require .NotNil (t , server )
624+
625+ testClientURL , _ := url .Parse (server .URL )
626+ testClientConfig := Config {
627+ Name : "test-client" ,
628+ URL : flagext.URLValue {URL : testClientURL },
629+ Timeout : time .Second * 2 ,
630+ BatchSize : 1 ,
631+ BackoffConfig : backoff.Config {
632+ MaxRetries : 0 ,
633+ },
634+ Queue : QueueConfig {
635+ Capacity : 10 , // buffered channel of size 10
636+ DrainTimeout : time .Second * 10 ,
637+ },
638+ }
639+ return testClientConfig , receivedReqsChan , func () {
640+ server .Close ()
641+ close (receivedReqsChan )
642+ }
643+ }
0 commit comments