github.com/m3db/m3@v1.5.0/src/cmd/services/m3coordinator/ingest/m3msg/ingest_test.go (about)

     1  // Copyright (c) 2018 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package ingestm3msg
    22  
    23  import (
    24  	"context"
    25  	"errors"
    26  	"sync"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/m3db/m3/src/cmd/services/m3coordinator/server/m3msg"
    31  	"github.com/m3db/m3/src/metrics/encoding/protobuf"
    32  	"github.com/m3db/m3/src/metrics/policy"
    33  	"github.com/m3db/m3/src/msg/consumer"
    34  	"github.com/m3db/m3/src/query/models"
    35  	"github.com/m3db/m3/src/query/storage"
    36  	"github.com/m3db/m3/src/query/storage/m3/storagemetadata"
    37  	"github.com/m3db/m3/src/query/ts"
    38  	xerrors "github.com/m3db/m3/src/x/errors"
    39  	"github.com/m3db/m3/src/x/ident"
    40  	"github.com/m3db/m3/src/x/instrument"
    41  	"github.com/m3db/m3/src/x/pool"
    42  	"github.com/m3db/m3/src/x/serialize"
    43  	xtime "github.com/m3db/m3/src/x/time"
    44  
    45  	"github.com/golang/mock/gomock"
    46  	"github.com/stretchr/testify/require"
    47  	"github.com/uber-go/tally"
    48  )
    49  
    50  func TestIngest(t *testing.T) {
    51  	ctrl := gomock.NewController(t)
    52  	defer ctrl.Finish()
    53  
    54  	cfg := Configuration{
    55  		WorkerPoolSize: 2,
    56  		OpPool: pool.ObjectPoolConfiguration{
    57  			Size: 1,
    58  		},
    59  	}
    60  	appender := &mockAppender{}
    61  	ingester, err := cfg.NewIngester(appender, models.NewTagOptions(),
    62  		instrument.NewOptions())
    63  	require.NoError(t, err)
    64  
    65  	id := newTestID(t, "__name__", "foo", "app", "bar")
    66  	metricNanos := int64(1234)
    67  	val := float64(1)
    68  	sp := policy.MustParseStoragePolicy("1m:40d")
    69  	m := consumer.NewMockMessage(ctrl)
    70  	var wg sync.WaitGroup
    71  	wg.Add(1)
    72  	callback := m3msg.NewProtobufCallback(m, protobuf.NewAggregatedDecoder(nil), &wg)
    73  
    74  	m.EXPECT().Ack()
    75  	ingester.Ingest(context.TODO(), id, metricNanos, 0, val, nil, sp, callback)
    76  
    77  	for appender.cnt() != 1 {
    78  		time.Sleep(100 * time.Millisecond)
    79  	}
    80  
    81  	expected, err := storage.NewWriteQuery(storage.WriteQueryOptions{
    82  		Annotation: nil,
    83  		Attributes: storagemetadata.Attributes{
    84  			MetricsType: storagemetadata.AggregatedMetricsType,
    85  			Resolution:  time.Minute,
    86  			Retention:   40 * 24 * time.Hour,
    87  		},
    88  		Datapoints: ts.Datapoints{
    89  			ts.Datapoint{
    90  				Timestamp: xtime.UnixNano(metricNanos),
    91  				Value:     val,
    92  			},
    93  		},
    94  		Tags: models.NewTags(2, nil).AddTags(
    95  			[]models.Tag{
    96  				{
    97  					Name:  []byte("__name__"),
    98  					Value: []byte("foo"),
    99  				},
   100  				{
   101  					Name:  []byte("app"),
   102  					Value: []byte("bar"),
   103  				},
   104  			},
   105  		),
   106  		Unit: xtime.Second,
   107  	})
   108  	require.NoError(t, err)
   109  
   110  	require.Equal(t, *expected, *appender.received[0])
   111  
   112  	// Make sure the op is put back to pool.
   113  	op := ingester.p.Get().(*ingestOp)
   114  	require.Equal(t, id, op.id)
   115  }
   116  
   117  func TestIngestNonRetryableError(t *testing.T) {
   118  	ctrl := gomock.NewController(t)
   119  	defer ctrl.Finish()
   120  
   121  	cfg := Configuration{
   122  		WorkerPoolSize: 2,
   123  		OpPool: pool.ObjectPoolConfiguration{
   124  			Size: 1,
   125  		},
   126  	}
   127  
   128  	scope := tally.NewTestScope("", nil)
   129  	instrumentOpts := instrument.NewOptions().SetMetricsScope(scope)
   130  
   131  	nonRetryableError := xerrors.NewNonRetryableError(errors.New("bad request error"))
   132  	appender := &mockAppender{expectErr: nonRetryableError}
   133  	ingester, err := cfg.NewIngester(appender, models.NewTagOptions(),
   134  		instrumentOpts)
   135  	require.NoError(t, err)
   136  
   137  	id := newTestID(t, "__name__", "foo", "app", "bar")
   138  	metricNanos := int64(1234)
   139  	val := float64(1)
   140  	sp := policy.MustParseStoragePolicy("1m:40d")
   141  	m := consumer.NewMockMessage(ctrl)
   142  	var wg sync.WaitGroup
   143  	wg.Add(1)
   144  	callback := m3msg.NewProtobufCallback(m, protobuf.NewAggregatedDecoder(nil), &wg)
   145  
   146  	m.EXPECT().Ack()
   147  	ingester.Ingest(context.TODO(), id, metricNanos, 0, val, nil, sp, callback)
   148  
   149  	for appender.cntErr() != 1 {
   150  		time.Sleep(100 * time.Millisecond)
   151  	}
   152  
   153  	// Make non-retryable error marked.
   154  	counters := scope.Snapshot().Counters()
   155  
   156  	counter, ok := counters["errors+component=ingester,type=not-retryable"]
   157  	require.True(t, ok)
   158  	require.Equal(t, int64(1), counter.Value())
   159  }
   160  
   161  type mockAppender struct {
   162  	sync.RWMutex
   163  
   164  	expectErr   error
   165  	receivedErr []*storage.WriteQuery
   166  	received    []*storage.WriteQuery
   167  }
   168  
   169  func (m *mockAppender) Write(ctx context.Context, query *storage.WriteQuery) error {
   170  	m.Lock()
   171  	defer m.Unlock()
   172  
   173  	if m.expectErr != nil {
   174  		m.receivedErr = append(m.receivedErr, query)
   175  		return m.expectErr
   176  	}
   177  	m.received = append(m.received, query)
   178  	return nil
   179  }
   180  
   181  func (m *mockAppender) cnt() int {
   182  	m.Lock()
   183  	defer m.Unlock()
   184  
   185  	return len(m.received)
   186  }
   187  
   188  func (m *mockAppender) cntErr() int {
   189  	m.Lock()
   190  	defer m.Unlock()
   191  
   192  	return len(m.receivedErr)
   193  }
   194  
   195  func newTestID(t *testing.T, tags ...string) []byte {
   196  	tagEncoderPool := serialize.NewTagEncoderPool(serialize.NewTagEncoderOptions(),
   197  		pool.NewObjectPoolOptions().SetSize(1))
   198  	tagEncoderPool.Init()
   199  
   200  	tagsIter := ident.MustNewTagStringsIterator(tags...)
   201  	tagEncoder := tagEncoderPool.Get()
   202  	err := tagEncoder.Encode(tagsIter)
   203  	require.NoError(t, err)
   204  
   205  	data, ok := tagEncoder.Data()
   206  	require.True(t, ok)
   207  	return data.Bytes()
   208  }