github.com/m3db/m3@v1.5.0/src/dbnode/client/host_queue_aggregate_test.go (about)

     1  // Copyright (c) 2019 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 client
    22  
    23  import (
    24  	"fmt"
    25  	"sync"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/m3db/m3/src/dbnode/generated/thrift/rpc"
    30  	xtest "github.com/m3db/m3/src/x/test"
    31  
    32  	"github.com/golang/mock/gomock"
    33  	"github.com/stretchr/testify/assert"
    34  	"github.com/stretchr/testify/require"
    35  	"github.com/uber/tchannel-go/thrift"
    36  )
    37  
    38  func TestHostQueueDrainOnCloseAggregate(t *testing.T) {
    39  	ctrl := gomock.NewController(xtest.Reporter{T: t})
    40  	defer ctrl.Finish()
    41  
    42  	mockConnPool := NewMockconnectionPool(ctrl)
    43  
    44  	opts := newHostQueueTestOptions()
    45  	queue := newTestHostQueue(opts)
    46  	queue.connPool = mockConnPool
    47  
    48  	// Open
    49  	mockConnPool.EXPECT().Open()
    50  	queue.Open()
    51  	assert.Equal(t, statusOpen, queue.status)
    52  
    53  	// Prepare callback for writes
    54  	var (
    55  		results []hostQueueResult
    56  		wg      sync.WaitGroup
    57  	)
    58  	callback := func(r interface{}, err error) {
    59  		results = append(results, hostQueueResult{r, err})
    60  		wg.Done()
    61  	}
    62  
    63  	// Prepare aggregates
    64  	aggregate := testAggregateOp("testNs", callback)
    65  
    66  	mockClient := rpc.NewMockTChanNode(ctrl)
    67  	aggregateExec := func(ctx thrift.Context, req *rpc.AggregateQueryRawRequest) {
    68  		assert.Equal(t, aggregate.request.NameSpace, req.NameSpace)
    69  	}
    70  	mockClient.EXPECT().AggregateRaw(gomock.Any(), gomock.Any()).Do(aggregateExec).Return(nil, nil)
    71  	mockConnPool.EXPECT().NextClient().Return(mockClient, &noopPooledChannel{}, nil)
    72  	mockConnPool.EXPECT().Close().AnyTimes()
    73  
    74  	// Execute aggregate
    75  	wg.Add(1)
    76  	assert.NoError(t, queue.Enqueue(aggregate))
    77  
    78  	// Close the queue should cause all writes to be flushed
    79  	queue.Close()
    80  	closeCh := make(chan struct{})
    81  	go func() {
    82  		// Wait for all writes
    83  		wg.Wait()
    84  		close(closeCh)
    85  	}()
    86  
    87  	select {
    88  	case <-closeCh:
    89  	case <-time.After(time.Minute):
    90  		assert.Fail(t, "Not flushing writes")
    91  	}
    92  
    93  	// Assert aggregate successful
    94  	assert.Equal(t, 1, len(results))
    95  	for _, result := range results {
    96  		assert.Nil(t, result.err)
    97  	}
    98  }
    99  
   100  func TestHostQueueAggregate(t *testing.T) {
   101  	namespace := "testNs"
   102  	res := &rpc.AggregateQueryRawResult_{
   103  		Results: []*rpc.AggregateQueryRawResultTagNameElement{
   104  			{
   105  				TagName: []byte("tagName"),
   106  			},
   107  		},
   108  		Exhaustive: true,
   109  	}
   110  	expectedResults := []hostQueueResult{
   111  		{
   112  			result: aggregateResultAccumulatorOpts{
   113  				response: res,
   114  				host:     h,
   115  			},
   116  		},
   117  	}
   118  	testHostQueueAggregate(t, namespace, res, expectedResults, nil, func(results []hostQueueResult) {
   119  		assert.Equal(t, expectedResults, results)
   120  	})
   121  }
   122  
   123  func TestHostQueueAggregateErrorOnNextClientUnavailable(t *testing.T) {
   124  	namespace := "testNs"
   125  	expectedErr := fmt.Errorf("an error")
   126  	expectedResults := []hostQueueResult{
   127  		{
   128  			result: aggregateResultAccumulatorOpts{
   129  				host: h,
   130  			},
   131  			err: expectedErr,
   132  		},
   133  	}
   134  	opts := &testHostQueueAggregateOptions{
   135  		nextClientErr: expectedErr,
   136  	}
   137  	testHostQueueAggregate(t, namespace, nil, expectedResults, opts, func(results []hostQueueResult) {
   138  		assert.Equal(t, expectedResults, results)
   139  	})
   140  }
   141  
   142  func TestHostQueueAggregateErrorOnAggregateError(t *testing.T) {
   143  	namespace := "testNs"
   144  	expectedErr := fmt.Errorf("an error")
   145  	expectedResults := []hostQueueResult{
   146  		{
   147  			result: aggregateResultAccumulatorOpts{host: h},
   148  			err:    expectedErr,
   149  		},
   150  	}
   151  	opts := &testHostQueueAggregateOptions{
   152  		aggregateErr: expectedErr,
   153  	}
   154  	testHostQueueAggregate(t, namespace, nil, expectedResults, opts, func(results []hostQueueResult) {
   155  		assert.Equal(t, expectedResults, results)
   156  	})
   157  }
   158  
   159  type testHostQueueAggregateOptions struct {
   160  	nextClientErr error
   161  	aggregateErr  error
   162  }
   163  
   164  func testHostQueueAggregate(
   165  	t *testing.T,
   166  	namespace string,
   167  	result *rpc.AggregateQueryRawResult_,
   168  	expected []hostQueueResult,
   169  	testOpts *testHostQueueAggregateOptions,
   170  	assertion func(results []hostQueueResult),
   171  ) {
   172  	ctrl := gomock.NewController(t)
   173  	defer ctrl.Finish()
   174  
   175  	mockConnPool := NewMockconnectionPool(ctrl)
   176  
   177  	opts := newHostQueueTestOptions().
   178  		SetHostQueueOpsFlushInterval(time.Millisecond)
   179  	queue := newTestHostQueue(opts)
   180  	queue.connPool = mockConnPool
   181  
   182  	// Open
   183  	mockConnPool.EXPECT().Open()
   184  	queue.Open()
   185  	assert.Equal(t, statusOpen, queue.status)
   186  
   187  	// Prepare callback for aggregates
   188  	var (
   189  		results []hostQueueResult
   190  		wg      sync.WaitGroup
   191  	)
   192  	callback := func(r interface{}, err error) {
   193  		results = append(results, hostQueueResult{r, err})
   194  		wg.Done()
   195  	}
   196  
   197  	// Prepare aggregate op
   198  	aggregateOp := testAggregateOp("testNs", callback)
   199  	wg.Add(1)
   200  
   201  	// Prepare mocks for flush
   202  	mockClient := rpc.NewMockTChanNode(ctrl)
   203  	if testOpts != nil && testOpts.nextClientErr != nil {
   204  		mockConnPool.EXPECT().NextClient().Return(nil, nil, testOpts.nextClientErr)
   205  	} else if testOpts != nil && testOpts.aggregateErr != nil {
   206  		aggregateExec := func(ctx thrift.Context, req *rpc.AggregateQueryRawRequest) {
   207  			require.NotNil(t, req)
   208  			assert.Equal(t, aggregateOp.request, *req)
   209  		}
   210  		mockClient.EXPECT().
   211  			AggregateRaw(gomock.Any(), gomock.Any()).
   212  			Do(aggregateExec).
   213  			Return(nil, testOpts.aggregateErr)
   214  
   215  		mockConnPool.EXPECT().NextClient().Return(mockClient, &noopPooledChannel{}, nil)
   216  	} else {
   217  		aggregateExec := func(ctx thrift.Context, req *rpc.AggregateQueryRawRequest) {
   218  			require.NotNil(t, req)
   219  			assert.Equal(t, aggregateOp.request, *req)
   220  		}
   221  		mockClient.EXPECT().
   222  			AggregateRaw(gomock.Any(), gomock.Any()).
   223  			Do(aggregateExec).
   224  			Return(result, nil)
   225  
   226  		mockConnPool.EXPECT().NextClient().Return(mockClient, &noopPooledChannel{}, nil)
   227  	}
   228  
   229  	// Fetch
   230  	assert.NoError(t, queue.Enqueue(aggregateOp))
   231  
   232  	// Wait for aggregate to complete
   233  	wg.Wait()
   234  
   235  	// Assert results match expected
   236  	assertion(results)
   237  
   238  	// Close
   239  	var closeWg sync.WaitGroup
   240  	closeWg.Add(1)
   241  	mockConnPool.EXPECT().Close().Do(func() {
   242  		closeWg.Done()
   243  	})
   244  	queue.Close()
   245  	closeWg.Wait()
   246  }
   247  
   248  func testAggregateOp(
   249  	namespace string,
   250  	completionFn completionFn,
   251  ) *aggregateOp {
   252  	f := newAggregateOp(nil)
   253  	f.incRef()
   254  	f.context = testContext()
   255  	f.request = rpc.AggregateQueryRawRequest{
   256  		NameSpace: []byte(namespace),
   257  	}
   258  	f.completionFn = completionFn
   259  	return f
   260  }