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  }