Skip to content

Commit c3fe350

Browse files
authored
Merge pull request #166 from moov-io/include-network-errors-in-upload-test
Include network errors in upload test
2 parents 6c6eeb0 + ad02b82 commit c3fe350

6 files changed

Lines changed: 73 additions & 6 deletions

File tree

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ require (
3333
gocloud.dev/pubsub/kafkapubsub v0.26.0
3434
goftp.io/server v0.4.1
3535
golang.org/x/crypto v0.7.0
36+
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0
3637
golang.org/x/text v0.8.0
3738
)
3839

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1656,6 +1656,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
16561656
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
16571657
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
16581658
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
1659+
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 h1:pVgRXcIictcr+lBQIFeiwuwtDIs4eL21OuM9nyAADmo=
1660+
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
16591661
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
16601662
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
16611663
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=

internal/incoming/stream/publisher.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ package stream
1515

1616
import (
1717
"context"
18+
"fmt"
19+
"net/url"
1820

1921
"github.com/moov-io/achgateway/internal/kafka"
2022
"github.com/moov-io/achgateway/internal/service"
@@ -26,7 +28,14 @@ import (
2628

2729
func Topic(logger log.Logger, cfg *service.Config) (*pubsub.Topic, error) {
2830
if cfg.Inbound.InMem != nil {
29-
return pubsub.OpenTopic(context.Background(), cfg.Inbound.InMem.URL)
31+
// Strip away any query params. They're only supported by subscriptions
32+
u, err := url.Parse(cfg.Inbound.InMem.URL)
33+
if err != nil {
34+
return nil, fmt.Errorf("parsing inbound in-mem url: %v", err)
35+
}
36+
37+
addr := fmt.Sprintf("%s://%s", u.Scheme, u.Host)
38+
return pubsub.OpenTopic(context.Background(), addr)
3039
}
3140
if cfg.Inbound.Kafka != nil {
3241
return kafka.OpenTopic(logger, cfg.Inbound.Kafka)

internal/pipeline/file_receiver.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"errors"
2323
"fmt"
2424
"strings"
25+
"sync"
2526

2627
"github.com/moov-io/achgateway/internal/incoming"
2728
"github.com/moov-io/achgateway/internal/incoming/stream"
@@ -41,6 +42,7 @@ import (
4142
type FileReceiver struct {
4243
logger log.Logger
4344
cfg *service.Config
45+
mu sync.RWMutex
4446

4547
defaultShardName string
4648

@@ -79,6 +81,9 @@ func newFileReceiver(
7981
}
8082

8183
func (fr *FileReceiver) reconnect() error {
84+
fr.mu.Lock()
85+
defer fr.mu.Unlock()
86+
8287
// Close any existing subscription
8388
if fr.streamFiles != nil {
8489
fr.streamFiles.Shutdown(context.Background())
@@ -94,6 +99,9 @@ func (fr *FileReceiver) reconnect() error {
9499
}
95100

96101
func (fr *FileReceiver) ReplaceStreamFiles(sub stream.Subscription) {
102+
fr.mu.Lock()
103+
defer fr.mu.Unlock()
104+
97105
// Close an existing stream subscription
98106
if fr.streamFiles != nil {
99107
fr.streamFiles.Shutdown(context.Background())
@@ -121,7 +129,7 @@ func (fr *FileReceiver) Start(ctx context.Context) {
121129
fr.logger.LogErrorf("error handling stream file: %v", err)
122130

123131
// Attempt to reconnect under some conditions
124-
if contains(err, "write: broken pipe") {
132+
if isNetworkError(err) {
125133
fr.logger.Info().Log("attempt to reconnect to stream subscription")
126134
if err := fr.reconnect(); err != nil {
127135
fr.logger.LogErrorf("unable to reconnect stream subscription: %v", err)
@@ -172,6 +180,9 @@ func (fr *FileReceiver) RegisterAdminRoutes(r *admin.Server) {
172180
// handleMessage will listen for an incoming.ACHFile to pass off to an aggregator for the shard
173181
// responsible. It does so with a database lookup and the fixed set of Shards from the file config.
174182
func (fr *FileReceiver) handleMessage(ctx context.Context, sub stream.Subscription) chan error {
183+
fr.mu.RLock()
184+
defer fr.mu.RUnlock()
185+
175186
out := make(chan error, 1)
176187
if sub == nil {
177188
return out
@@ -191,8 +202,9 @@ func (fr *FileReceiver) handleMessage(ctx context.Context, sub stream.Subscripti
191202
return
192203
}
193204
// Bubble up some errors to alerting
194-
if contains(err, "connect: ", "write:", "broken pipe", "pubsub", "EOF") {
205+
if isNetworkError(err) {
195206
out <- err
207+
return
196208
}
197209
fr.logger.LogErrorf("ERROR receiving message: %v", err)
198210
}
@@ -217,6 +229,10 @@ func (fr *FileReceiver) handleMessage(ctx context.Context, sub stream.Subscripti
217229
return out
218230
}
219231

232+
func isNetworkError(err error) bool {
233+
return contains(err, "connect: ", "write:", "broken pipe", "pubsub", "EOF")
234+
}
235+
220236
func contains(err error, options ...string) bool {
221237
es := err.Error()
222238
for i := range options {

internal/pipeline/merging.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ import (
3636
"github.com/moov-io/base"
3737
"github.com/moov-io/base/log"
3838
"github.com/moov-io/base/strx"
39+
40+
"golang.org/x/exp/maps"
3941
)
4042

4143
// XferMerging represents logic for accepting ACH files to be merged together.
@@ -226,7 +228,9 @@ func (m *filesystemMerging) WithEachMerged(f func(int, upload.Agent, *ach.File)
226228
}
227229

228230
logger := m.logger.Set("shardName", log.String(m.shard.Name))
229-
logger.Logf("found %d matching ACH files: %#v", len(matches), matches)
231+
232+
dirNames := strings.Join(directoryNames(matches), ", ")
233+
logger.Logf("found %d matching ACH files in %v", len(matches), dirNames)
230234

231235
var files []*ach.File
232236
var el base.ErrorList
@@ -328,6 +332,15 @@ func (m *filesystemMerging) WithEachMerged(f func(int, upload.Agent, *ach.File)
328332
return newProcessedFiles(m.shard.Name, matches), nil
329333
}
330334

335+
func directoryNames(matches []string) []string {
336+
out := make(map[string]int)
337+
for i := range matches {
338+
dir, _ := filepath.Split(matches[i])
339+
out[dir] += 1
340+
}
341+
return maps.Keys(out)
342+
}
343+
331344
func (m *filesystemMerging) saveMergedFile(dir string, file *ach.File) error {
332345
var buf bytes.Buffer
333346
if err := ach.NewWriter(&buf).Write(file); err != nil {

internal/test/upload_test.go

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"encoding/json"
2525
"errors"
2626
"fmt"
27+
"io"
2728
"math/rand"
2829
"net/http"
2930
"net/http/httptest"
@@ -70,7 +71,7 @@ var (
7071
},
7172
Inbound: service.Inbound{
7273
InMem: &service.InMemory{
73-
URL: "mem://upload-test",
74+
URL: "mem://upload-test?ackdeadline=1s",
7475
},
7576
},
7677
Sharding: service.Sharding{
@@ -182,6 +183,7 @@ func TestUploads(t *testing.T) {
182183
// Upload our files
183184
createdEntries := 0
184185
canceledEntries := 0
186+
erroredSubscriptions := 0
185187
for i := 0; i < 1000; i++ {
186188
shardKey := shardKeys[i%10]
187189
fileID := base.ID()
@@ -191,6 +193,13 @@ func TestUploads(t *testing.T) {
191193
require.Equal(t, http.StatusOK, w.Code)
192194

193195
canceledEntries += maybeCancelFile(t, r, shardKey, fileID, file)
196+
197+
// Force the subscription to fail sometimes
198+
if err := causeSubscriptionFailure(t); err != nil {
199+
flakeySub := streamtest.FailingSubscription(err)
200+
fileReceiver.ReplaceStreamFiles(flakeySub)
201+
erroredSubscriptions += 1
202+
}
194203
}
195204

196205
t.Logf("created %d entries and canceled %d entries", createdEntries, canceledEntries)
@@ -215,7 +224,7 @@ func TestUploads(t *testing.T) {
215224

216225
expected := createdEntries - canceledEntries
217226
found := countAllEntries(createdFiles)
218-
t.Logf("found %d entries of %d expected (%d canceled)", found, expected, canceledEntries)
227+
t.Logf("found %d entries of %d expected (%d canceled) (%d errored)", found, expected, canceledEntries, erroredSubscriptions)
219228
require.Equal(t, found, expected)
220229
}
221230

@@ -381,3 +390,20 @@ func cancelFile(t *testing.T, r *mux.Router, shardKey, fileID string) {
381390

382391
require.Equal(t, http.StatusOK, w.Code)
383392
}
393+
394+
var subscriptionFailures = []error{
395+
io.EOF,
396+
errors.New("write: broken pipe"),
397+
errors.New("contains: pubsub error"),
398+
}
399+
400+
func causeSubscriptionFailure(t *testing.T) error {
401+
t.Helper()
402+
403+
n := rand.Int63n(100) //nolint:gosec
404+
if n%25 == 0 {
405+
idx := (len(subscriptionFailures) - 1) % (int(n) + 1)
406+
return subscriptionFailures[idx]
407+
}
408+
return nil
409+
}

0 commit comments

Comments
 (0)