github.com/Jeffail/benthos/v3@v3.65.0/lib/output/writer/kinesis_firehose_test.go (about) 1 package writer 2 3 import ( 4 "errors" 5 "fmt" 6 "testing" 7 8 "github.com/Jeffail/benthos/v3/lib/log" 9 "github.com/Jeffail/benthos/v3/lib/message" 10 "github.com/aws/aws-sdk-go/aws" 11 "github.com/aws/aws-sdk-go/aws/credentials" 12 "github.com/aws/aws-sdk-go/aws/session" 13 "github.com/aws/aws-sdk-go/service/firehose" 14 "github.com/aws/aws-sdk-go/service/firehose/firehoseiface" 15 "github.com/cenkalti/backoff/v4" 16 ) 17 18 var ( 19 firehoseMThrottled = mockStats.GetCounter("send.throttled") 20 firehoseMThrottledF = mockStats.GetCounter("send.throttled") 21 firehoseMPartsThrottled = mockStats.GetCounter("parts.send.throttled") 22 firehoseMPartsThrottledF = mockStats.GetCounter("parts.send.throttled") 23 ) 24 25 type mockKinesisFirehose struct { 26 firehoseiface.FirehoseAPI 27 fn func(input *firehose.PutRecordBatchInput) (*firehose.PutRecordBatchOutput, error) 28 } 29 30 func (m *mockKinesisFirehose) PutRecordBatch(input *firehose.PutRecordBatchInput) (*firehose.PutRecordBatchOutput, error) { 31 return m.fn(input) 32 } 33 34 func TestKinesisFirehoseWriteSinglePartMessage(t *testing.T) { 35 k := KinesisFirehose{ 36 backoffCtor: func() backoff.BackOff { 37 return backoff.NewExponentialBackOff() 38 }, 39 session: session.Must(session.NewSession(&aws.Config{ 40 Credentials: credentials.NewStaticCredentials("xxxxx", "xxxxx", "xxxxx"), 41 })), 42 firehose: &mockKinesisFirehose{ 43 fn: func(input *firehose.PutRecordBatchInput) (*firehose.PutRecordBatchOutput, error) { 44 if exp, act := 1, len(input.Records); exp != act { 45 return nil, fmt.Errorf("expected input to have records with length %d, got %d", exp, act) 46 } 47 return &firehose.PutRecordBatchOutput{}, nil 48 }, 49 }, 50 log: log.Noop(), 51 } 52 53 msg := message.New(nil) 54 part := message.NewPart([]byte(`{"foo":"bar","id":123}`)) 55 msg.Append(part) 56 57 if err := k.Write(msg); err != nil { 58 t.Error(err) 59 } 60 } 61 62 func TestKinesisFirehoseWriteMultiPartMessage(t *testing.T) { 63 parts := []struct { 64 data []byte 65 key string 66 }{ 67 {[]byte(`{"foo":"bar","id":123}`), "123"}, 68 {[]byte(`{"foo":"baz","id":456}`), "456"}, 69 } 70 k := KinesisFirehose{ 71 backoffCtor: func() backoff.BackOff { 72 return backoff.NewExponentialBackOff() 73 }, 74 session: session.Must(session.NewSession(&aws.Config{ 75 Credentials: credentials.NewStaticCredentials("xxxxx", "xxxxx", "xxxxx"), 76 })), 77 firehose: &mockKinesisFirehose{ 78 fn: func(input *firehose.PutRecordBatchInput) (*firehose.PutRecordBatchOutput, error) { 79 if exp, act := len(parts), len(input.Records); exp != act { 80 return nil, fmt.Errorf("expected input to have records with length %d, got %d", exp, act) 81 } 82 return &firehose.PutRecordBatchOutput{}, nil 83 }, 84 }, 85 log: log.Noop(), 86 } 87 88 msg := message.New(nil) 89 for _, p := range parts { 90 part := message.NewPart(p.data) 91 msg.Append(part) 92 } 93 94 if err := k.Write(msg); err != nil { 95 t.Error(err) 96 } 97 } 98 99 func TestKinesisFirehoseWriteChunk(t *testing.T) { 100 batchLengths := []int{} 101 n := 1200 102 k := KinesisFirehose{ 103 backoffCtor: func() backoff.BackOff { 104 return backoff.NewExponentialBackOff() 105 }, 106 session: session.Must(session.NewSession(&aws.Config{ 107 Credentials: credentials.NewStaticCredentials("xxxxx", "xxxxx", "xxxxx"), 108 })), 109 firehose: &mockKinesisFirehose{ 110 fn: func(input *firehose.PutRecordBatchInput) (*firehose.PutRecordBatchOutput, error) { 111 batchLengths = append(batchLengths, len(input.Records)) 112 return &firehose.PutRecordBatchOutput{}, nil 113 }, 114 }, 115 log: log.Noop(), 116 } 117 118 msg := message.New(nil) 119 for i := 0; i < n; i++ { 120 part := message.NewPart([]byte(`{"foo":"bar","id":123}`)) 121 msg.Append(part) 122 } 123 124 if err := k.Write(msg); err != nil { 125 t.Error(err) 126 } 127 if exp, act := n/kinesisMaxRecordsCount+1, len(batchLengths); act != exp { 128 t.Errorf("Expected kinesis firehose PutRecordBatch to have call count %d, got %d", exp, act) 129 } 130 for i, act := range batchLengths { 131 exp := n 132 if exp > kinesisMaxRecordsCount { 133 exp = kinesisMaxRecordsCount 134 n -= kinesisMaxRecordsCount 135 } 136 if act != exp { 137 t.Errorf("Expected kinesis firehose PutRecordBatch call %d to have batch size %d, got %d", i, exp, act) 138 } 139 } 140 } 141 142 func TestKinesisFirehoseWriteChunkWithThrottling(t *testing.T) { 143 t.Parallel() 144 batchLengths := []int{} 145 n := 1200 146 k := KinesisFirehose{ 147 backoffCtor: func() backoff.BackOff { 148 return backoff.NewExponentialBackOff() 149 }, 150 session: session.Must(session.NewSession(&aws.Config{ 151 Credentials: credentials.NewStaticCredentials("xxxxx", "xxxxx", "xxxxx"), 152 })), 153 firehose: &mockKinesisFirehose{ 154 fn: func(input *firehose.PutRecordBatchInput) (*firehose.PutRecordBatchOutput, error) { 155 count := len(input.Records) 156 batchLengths = append(batchLengths, count) 157 var failed int64 158 output := firehose.PutRecordBatchOutput{ 159 RequestResponses: make([]*firehose.PutRecordBatchResponseEntry, count), 160 } 161 for i := 0; i < count; i++ { 162 var entry firehose.PutRecordBatchResponseEntry 163 if i >= 300 { 164 failed++ 165 entry.SetErrorCode(firehose.ErrCodeServiceUnavailableException) 166 entry.SetErrorMessage("Mocked ProvisionedThroughputExceededException") 167 } 168 output.RequestResponses[i] = &entry 169 } 170 output.SetFailedPutCount(failed) 171 return &output, nil 172 }, 173 }, 174 mThrottled: firehoseMThrottled, 175 mThrottledF: firehoseMThrottledF, 176 mPartsThrottled: firehoseMPartsThrottled, 177 mPartsThrottledF: firehoseMPartsThrottledF, 178 log: log.Noop(), 179 } 180 181 msg := message.New(nil) 182 for i := 0; i < n; i++ { 183 part := message.NewPart([]byte(`{"foo":"bar","id":123}`)) 184 msg.Append(part) 185 } 186 187 expectedLengths := []int{ 188 500, 500, 500, 300, 189 } 190 191 if err := k.Write(msg); err != nil { 192 t.Error(err) 193 } 194 if exp, act := len(expectedLengths), len(batchLengths); act != exp { 195 t.Errorf("Expected kinesis firehose PutRecordBatch to have call count %d, got %d", exp, act) 196 } 197 for i, act := range batchLengths { 198 if exp := expectedLengths[i]; act != exp { 199 t.Errorf("Expected kinesis firehose PutRecordBatch call %d to have batch size %d, got %d", i, exp, act) 200 } 201 } 202 } 203 204 func TestKinesisFirehoseWriteError(t *testing.T) { 205 t.Parallel() 206 var calls int 207 k := KinesisFirehose{ 208 backoffCtor: func() backoff.BackOff { 209 return backoff.WithMaxRetries(backoff.NewExponentialBackOff(), 2) 210 }, 211 session: session.Must(session.NewSession(&aws.Config{ 212 Credentials: credentials.NewStaticCredentials("xxxxx", "xxxxx", "xxxxx"), 213 })), 214 firehose: &mockKinesisFirehose{ 215 fn: func(input *firehose.PutRecordBatchInput) (*firehose.PutRecordBatchOutput, error) { 216 calls++ 217 return nil, errors.New("blah") 218 }, 219 }, 220 log: log.Noop(), 221 } 222 223 msg := message.New(nil) 224 msg.Append(message.NewPart([]byte(`{"foo":"bar"}`))) 225 226 if exp, err := "blah", k.Write(msg); err.Error() != exp { 227 t.Errorf("Expected err to equal %s, got %v", exp, err) 228 } 229 if exp, act := 3, calls; act != exp { 230 t.Errorf("Expected firehose PutRecordbatch to have call count %d, got %d", exp, act) 231 } 232 } 233 234 func TestKinesisFirehoseWriteMessageThrottling(t *testing.T) { 235 t.Parallel() 236 var calls [][]*firehose.Record 237 k := KinesisFirehose{ 238 backoffCtor: func() backoff.BackOff { 239 return backoff.NewExponentialBackOff() 240 }, 241 session: session.Must(session.NewSession(&aws.Config{ 242 Credentials: credentials.NewStaticCredentials("xxxxx", "xxxxx", "xxxxx"), 243 })), 244 firehose: &mockKinesisFirehose{ 245 fn: func(input *firehose.PutRecordBatchInput) (*firehose.PutRecordBatchOutput, error) { 246 records := make([]*firehose.Record, len(input.Records)) 247 copy(records, input.Records) 248 calls = append(calls, records) 249 var failed int64 250 var output firehose.PutRecordBatchOutput 251 for i := 0; i < len(input.Records); i++ { 252 entry := firehose.PutRecordBatchResponseEntry{} 253 if i > 0 { 254 failed++ 255 entry.SetErrorCode(firehose.ErrCodeServiceUnavailableException) 256 } 257 output.RequestResponses = append(output.RequestResponses, &entry) 258 } 259 output.SetFailedPutCount(failed) 260 return &output, nil 261 }, 262 }, 263 mThrottled: mThrottled, 264 mThrottledF: mThrottledF, 265 mPartsThrottled: mPartsThrottled, 266 mPartsThrottledF: mPartsThrottledF, 267 log: log.Noop(), 268 } 269 270 msg := message.New(nil) 271 msg.Append(message.NewPart([]byte(`{"foo":"bar","id":123}`))) 272 msg.Append(message.NewPart([]byte(`{"foo":"baz","id":456}`))) 273 msg.Append(message.NewPart([]byte(`{"foo":"qux","id":789}`))) 274 275 if err := k.Write(msg); err != nil { 276 t.Error(err) 277 } 278 if exp, act := msg.Len(), len(calls); act != exp { 279 t.Errorf("Expected kinesis firehose PutRecordBatch to have call count %d, got %d", exp, act) 280 } 281 for i, c := range calls { 282 if exp, act := msg.Len()-i, len(c); act != exp { 283 t.Errorf("Expected kinesis firehose PutRecordBatch call %d input to have Records with length %d, got %d", i, exp, act) 284 } 285 } 286 } 287 288 func TestKinesisFirehoseWriteBackoffMaxRetriesExceeded(t *testing.T) { 289 t.Parallel() 290 var calls int 291 k := KinesisFirehose{ 292 backoffCtor: func() backoff.BackOff { 293 return backoff.WithMaxRetries(backoff.NewExponentialBackOff(), 2) 294 }, 295 session: session.Must(session.NewSession(&aws.Config{ 296 Credentials: credentials.NewStaticCredentials("xxxxx", "xxxxx", "xxxxx"), 297 })), 298 firehose: &mockKinesisFirehose{ 299 fn: func(input *firehose.PutRecordBatchInput) (*firehose.PutRecordBatchOutput, error) { 300 calls++ 301 var output firehose.PutRecordBatchOutput 302 output.SetFailedPutCount(int64(1)) 303 output.RequestResponses = append(output.RequestResponses, &firehose.PutRecordBatchResponseEntry{ 304 ErrorCode: aws.String(firehose.ErrCodeServiceUnavailableException), 305 }) 306 return &output, nil 307 }, 308 }, 309 mThrottled: mThrottled, 310 mThrottledF: mThrottledF, 311 mPartsThrottled: mPartsThrottled, 312 mPartsThrottledF: mPartsThrottledF, 313 log: log.Noop(), 314 } 315 316 msg := message.New(nil) 317 msg.Append(message.NewPart([]byte(`{"foo":"bar","id":123}`))) 318 319 if err := k.Write(msg); err == nil { 320 t.Error(errors.New("expected kinesis.Write to error")) 321 } 322 if exp := 3; calls != exp { 323 t.Errorf("Expected kinesis firehose PutRecordBatch to have call count %d, got %d", exp, calls) 324 } 325 }