Skip to content

Commit b021bdb

Browse files
committed
Add functionality to return an internally managed error channel when creating a Sender
1 parent a802799 commit b021bdb

File tree

4 files changed

+242
-29
lines changed

4 files changed

+242
-29
lines changed

config.go

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,21 @@ import (
1818

1919
// Config describes the libkflow configuration.
2020
type Config struct {
21-
email string
22-
token string
23-
capture Capture
24-
proxy *url.URL
25-
api *url.URL
26-
flow *url.URL
27-
metrics *url.URL
28-
sample int
29-
timeout time.Duration
30-
retries int
31-
logger interface{}
32-
program string
33-
version string
34-
registry go_metrics.Registry
21+
email string
22+
token string
23+
capture Capture
24+
proxy *url.URL
25+
api *url.URL
26+
flow *url.URL
27+
metrics *url.URL
28+
sample int
29+
timeout time.Duration
30+
retries int
31+
logger interface{}
32+
program string
33+
version string
34+
registry go_metrics.Registry
35+
useInternalErrors bool
3536

3637
metricsPrefix string
3738
metricsInterval time.Duration
@@ -154,6 +155,10 @@ func (c *Config) OverrideRegistry(registry go_metrics.Registry) {
154155
c.registry = registry
155156
}
156157

158+
func (c *Config) WithInternalErrors() {
159+
c.useInternalErrors = true
160+
}
161+
157162
func (c *Config) client() *api.Client {
158163
return api.NewClient(api.ClientConfig{
159164
Email: c.email,
@@ -167,6 +172,21 @@ func (c *Config) client() *api.Client {
167172
})
168173
}
169174

175+
func (c *Config) startWithInternalErrors(client *api.Client, dev *api.Device) (*Sender, <-chan error, error) {
176+
errChan := make(chan error)
177+
sender, err := c.start(client, dev, errChan)
178+
if err != nil {
179+
close(errChan)
180+
return nil, nil, err
181+
}
182+
183+
if c.useInternalErrors {
184+
sender.useInternalErrors = true
185+
}
186+
187+
return sender, errChan, nil
188+
}
189+
170190
func (c *Config) start(client *api.Client, dev *api.Device, errors chan<- error) (*Sender, error) {
171191
if c.metricsInterval == 0 {
172192
c.metricsInterval = 60 * time.Second

lib.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,19 @@ func NewSenderWithDeviceName(name string, errors chan<- error, cfg *Config) (*Se
6666
return cfg.start(client, d, errors)
6767
}
6868

69+
// NewSenderWithDeviceNameWithErrors creates a new flow Sender given a device name address and Config.
70+
// The returned error channel is managed internally and will be closed after Sender.Stop() is called.
71+
// If the timeout of Sender.Stop() is reached, it will return before the internal error channel is closed
72+
func NewSenderWithDeviceNameWithErrors(name string, cfg *Config) (*Sender, <-chan error, error) {
73+
client := cfg.client()
74+
d, err := lookupdev(client.GetDeviceByName(name))
75+
if err != nil {
76+
return nil, nil, err
77+
}
78+
79+
return cfg.startWithInternalErrors(client, d)
80+
}
81+
6982
// NewSenderWithNewDevice creates a new device given device creation parameters,
7083
// and then creates a new flow Sender with that device, the error channel, and
7184
// the Config.
@@ -80,6 +93,16 @@ func NewSenderWithNewDevice(dev *api.DeviceCreate, errors chan<- error, cfg *Con
8093
return cfg.start(client, d, errors)
8194
}
8295

96+
func NewSenderWithNewDeviceWithErrors(dev *api.DeviceCreate, cfg *Config) (*Sender, <-chan error, error) {
97+
client := cfg.client()
98+
d, err := client.CreateDevice(dev)
99+
if err != nil {
100+
return nil, nil, err
101+
}
102+
103+
return cfg.startWithInternalErrors(client, d)
104+
}
105+
83106
func NewSenderWithNewSiteAndDevice(siteAndDevice *api.SiteAndDeviceCreate, errors chan<- error, cfg *Config) (*Sender, error) {
84107
client := cfg.client()
85108
d, err := client.CreateDeviceAndSite(siteAndDevice)

lib_test.go

Lines changed: 167 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package libkflow_test
22

33
import (
4+
"net/http"
5+
"net/http/httptest"
46
"net/url"
7+
"sync"
58
"sync/atomic"
69
"testing"
710
"time"
@@ -10,6 +13,7 @@ import (
1013
"github.com/kentik/libkflow"
1114
"github.com/kentik/libkflow/api"
1215
"github.com/kentik/libkflow/api/test"
16+
"github.com/kentik/libkflow/flow"
1317
metrics2 "github.com/kentik/libkflow/metrics"
1418
"github.com/stretchr/testify/assert"
1519
"go.uber.org/goleak"
@@ -54,8 +58,170 @@ func TestNewSenderWithDeviceName(t *testing.T) {
5458
assert.Nil(err)
5559
}
5660

57-
func TestNewSenderWithDeviceNameLeaks(t *testing.T) {
61+
func TestNewSenderWithDeviceNameWithErrors_NoErrs(t *testing.T) {
62+
client, server, device, err := test.NewClientServer()
63+
if err != nil {
64+
t.Fatal(err)
65+
}
66+
67+
apiurl = server.URL(test.API)
68+
flowurl = server.URL(test.FLOW)
69+
metricsurl = server.URL(test.TSDB)
70+
71+
email = client.Email
72+
token = client.Token
73+
74+
config := libkflow.NewConfig(email, token, "test", "0.0.1")
75+
config.OverrideURLs(apiurl, flowurl, metricsurl)
76+
77+
l := stubLeveledLogger{}
78+
79+
registry := metrics.NewRegistry()
80+
metrics2.StartWithSetConf(registry, &l, metricsurl.String(), email, token, "chf")
81+
config.OverrideRegistry(registry)
82+
config.WithInternalErrors()
83+
84+
s, errors, err := libkflow.NewSenderWithDeviceNameWithErrors(device.Name, config)
85+
assert.NoError(t, err)
86+
87+
errorsFromChan := make([]error, 0)
88+
89+
wg := sync.WaitGroup{}
90+
wg.Add(1)
91+
go func() {
92+
for err := range errors {
93+
errorsFromChan = append(errorsFromChan, err)
94+
}
95+
wg.Done()
96+
}()
97+
98+
for i := 0; i < 5; i++ {
99+
s.Send(&flow.Flow{
100+
TimestampNano: time.Now().UnixNano(),
101+
})
102+
}
103+
104+
s.Stop(time.Second)
105+
106+
wg.Wait()
107+
108+
assert.Len(t, errorsFromChan, 0)
109+
}
110+
111+
func TestNewSenderWithDeviceNameWithErrors_WithErrs(t *testing.T) {
112+
client, server, device, err := test.NewClientServer()
113+
if err != nil {
114+
t.Fatal(err)
115+
}
116+
117+
flowServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
118+
w.WriteHeader(400)
119+
}))
120+
121+
apiurl = server.URL(test.API)
122+
flowurl = server.URL(flowServer.URL)
123+
metricsurl = server.URL(test.TSDB)
124+
125+
email = client.Email
126+
token = client.Token
127+
128+
config := libkflow.NewConfig(email, token, "test", "0.0.1")
129+
config.OverrideURLs(apiurl, flowurl, metricsurl)
130+
131+
l := stubLeveledLogger{}
58132

133+
registry := metrics.NewRegistry()
134+
metrics2.StartWithSetConf(registry, &l, metricsurl.String(), email, token, "chf")
135+
config.OverrideRegistry(registry)
136+
config.WithInternalErrors()
137+
138+
s, errors, err := libkflow.NewSenderWithDeviceNameWithErrors(device.Name, config)
139+
assert.NoError(t, err)
140+
141+
errorsFromChan := make([]error, 0)
142+
143+
wg := sync.WaitGroup{}
144+
wg.Add(1)
145+
go func() {
146+
for err := range errors {
147+
errorsFromChan = append(errorsFromChan, err)
148+
}
149+
wg.Done()
150+
}()
151+
152+
s.Send(&flow.Flow{
153+
TimestampNano: time.Now().UnixNano(),
154+
})
155+
156+
s.Stop(time.Second)
157+
158+
wg.Wait()
159+
160+
assert.Len(t, errorsFromChan, 1)
161+
}
162+
163+
func TestNewSenderWithDeviceName_WithErrs_NoPanic(t *testing.T) {
164+
client, server, device, err := test.NewClientServer()
165+
if err != nil {
166+
t.Fatal(err)
167+
}
168+
169+
flowServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
170+
w.WriteHeader(400)
171+
time.Sleep(time.Second)
172+
}))
173+
174+
apiurl = server.URL(test.API)
175+
flowurl = server.URL(flowServer.URL)
176+
metricsurl = server.URL(test.TSDB)
177+
178+
email = client.Email
179+
token = client.Token
180+
181+
config := libkflow.NewConfig(email, token, "test", "0.0.1")
182+
config.OverrideURLs(apiurl, flowurl, metricsurl)
183+
184+
l := stubLeveledLogger{}
185+
186+
registry := metrics.NewRegistry()
187+
metrics2.StartWithSetConf(registry, &l, metricsurl.String(), email, token, "chf")
188+
config.OverrideRegistry(registry)
189+
190+
errors := make(chan error)
191+
192+
s, err := libkflow.NewSenderWithDeviceName(device.Name, errors, config)
193+
assert.NoError(t, err)
194+
195+
errorsFromChan := make([]error, 0)
196+
197+
wg := sync.WaitGroup{}
198+
wg.Add(1)
199+
go func() {
200+
defer wg.Done()
201+
for {
202+
select {
203+
case <-time.After(time.Second):
204+
return
205+
case err := <-errors:
206+
errorsFromChan = append(errorsFromChan, err)
207+
}
208+
}
209+
}()
210+
211+
for i := 0; i < 100000; i++ {
212+
s.Send(&flow.Flow{
213+
TimestampNano: time.Now().UnixNano(),
214+
})
215+
}
216+
217+
s.Stop(time.Second * 0)
218+
219+
wg.Wait()
220+
221+
assert.Len(t, errorsFromChan, 1)
222+
}
223+
224+
func TestNewSenderWithDeviceNameLeaks(t *testing.T) {
59225
client, server, device, err := test.NewClientServer()
60226
if err != nil {
61227
t.Fatal(err)

send.go

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,21 @@ import (
2020

2121
// A Sender aggregates and transmits flow information to Kentik.
2222
type Sender struct {
23-
agg *agg.Agg
24-
exit chan struct{}
25-
url *url.URL
26-
timeout time.Duration
27-
client *api.Client
28-
sample int
29-
ticker *time.Ticker
30-
tickerCtx context.Context
31-
tickerCancelFunc context.CancelFunc
32-
workers sync.WaitGroup
33-
dns chan []byte
34-
Device *api.Device
35-
Errors chan<- error
36-
Metrics *metrics.Metrics
23+
agg *agg.Agg
24+
exit chan struct{}
25+
url *url.URL
26+
timeout time.Duration
27+
client *api.Client
28+
sample int
29+
ticker *time.Ticker
30+
tickerCtx context.Context
31+
tickerCancelFunc context.CancelFunc
32+
workers sync.WaitGroup
33+
dns chan []byte
34+
Device *api.Device
35+
Errors chan<- error
36+
useInternalErrors bool
37+
Metrics *metrics.Metrics
3738
}
3839

3940
func newSender(url *url.URL, timeout time.Duration) *Sender {
@@ -196,6 +197,9 @@ func (s *Sender) monitor() {
196197
s.ticker.Stop()
197198
s.tickerCancelFunc()
198199
s.Metrics.Unregister()
200+
if s.useInternalErrors {
201+
close(s.Errors)
202+
}
199203
s.exit <- struct{}{}
200204
log.Debugf("sender stopped")
201205
return

0 commit comments

Comments
 (0)