github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/client/host_queue_fetch_batch_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 client
    22  
    23  import (
    24  	"fmt"
    25  	"sync"
    26  	"testing"
    27  
    28  	"github.com/m3db/m3/src/dbnode/generated/thrift/rpc"
    29  
    30  	"github.com/golang/mock/gomock"
    31  	"github.com/stretchr/testify/assert"
    32  	"github.com/uber/tchannel-go/thrift"
    33  )
    34  
    35  func TestHostQueueFetchBatches(t *testing.T) {
    36  	namespace := "testNs"
    37  	ids := []string{"foo", "bar", "baz", "qux"}
    38  	result := &rpc.FetchBatchRawResult_{}
    39  	for range ids {
    40  		result.Elements = append(result.Elements, &rpc.FetchRawResult_{Segments: []*rpc.Segments{}})
    41  	}
    42  	var expected []hostQueueResult
    43  	for i := range ids {
    44  		expected = append(expected, hostQueueResult{result.Elements[i].Segments, nil})
    45  	}
    46  	testHostQueueFetchBatches(t, namespace, ids, result, expected, nil, func(results []hostQueueResult) {
    47  		assert.Equal(t, expected, results)
    48  	})
    49  }
    50  
    51  func TestHostQueueFetchBatchesV2MultiNS(t *testing.T) {
    52  	ids := []string{"foo", "bar", "baz", "qux"}
    53  	result := &rpc.FetchBatchRawResult_{}
    54  	for range ids {
    55  		result.Elements = append(result.Elements, &rpc.FetchRawResult_{Segments: []*rpc.Segments{}})
    56  	}
    57  	var expected []hostQueueResult
    58  	for i := range ids {
    59  		expected = append(expected, hostQueueResult{result.Elements[i].Segments, nil})
    60  	}
    61  	opts := newHostQueueTestOptions().SetUseV2BatchAPIs(true)
    62  	ctrl := gomock.NewController(t)
    63  	defer ctrl.Finish()
    64  
    65  	mockConnPool := NewMockconnectionPool(ctrl)
    66  
    67  	queue := newTestHostQueue(opts)
    68  	queue.connPool = mockConnPool
    69  
    70  	// Open.
    71  	mockConnPool.EXPECT().Open()
    72  	queue.Open()
    73  	assert.Equal(t, statusOpen, queue.status)
    74  
    75  	// Prepare callback for fetches.
    76  	var (
    77  		results []hostQueueResult
    78  		wg      sync.WaitGroup
    79  	)
    80  	callback := func(r interface{}, err error) {
    81  		results = append(results, hostQueueResult{r, err})
    82  		wg.Done()
    83  	}
    84  
    85  	fetchBatches := []*fetchBatchOp{}
    86  	for i, id := range ids {
    87  		fetchBatch := &fetchBatchOp{
    88  			request: rpc.FetchBatchRawRequest{
    89  				NameSpace: []byte(fmt.Sprintf("ns-%d", i)),
    90  			},
    91  			requestV2Elements: []rpc.FetchBatchRawV2RequestElement{
    92  				{
    93  					ID:         []byte(id),
    94  					RangeStart: int64(i),
    95  					RangeEnd:   int64(i + 1),
    96  				},
    97  			},
    98  		}
    99  		fetchBatches = append(fetchBatches, fetchBatch)
   100  		fetchBatch.completionFns = append(fetchBatch.completionFns, callback)
   101  	}
   102  	wg.Add(len(ids))
   103  
   104  	// Prepare mocks for flush
   105  	mockClient := rpc.NewMockTChanNode(ctrl)
   106  
   107  	verifyFetchBatchRawV2 := func(ctx thrift.Context, req *rpc.FetchBatchRawV2Request) {
   108  		assert.Equal(t, len(ids), len(req.NameSpaces))
   109  		for i, ns := range req.NameSpaces {
   110  			assert.Equal(t, []byte(fmt.Sprintf("ns-%d", i)), ns)
   111  		}
   112  		assert.Equal(t, len(ids), len(req.Elements))
   113  		for i, elem := range req.Elements {
   114  			assert.Equal(t, int64(i), elem.NameSpace)
   115  			assert.Equal(t, int64(i), elem.RangeStart)
   116  			assert.Equal(t, int64(i+1), elem.RangeEnd)
   117  			assert.Equal(t, []byte(ids[i]), elem.ID)
   118  		}
   119  	}
   120  
   121  	mockClient.EXPECT().
   122  		FetchBatchRawV2(gomock.Any(), gomock.Any()).
   123  		Do(verifyFetchBatchRawV2).
   124  		Return(result, nil)
   125  
   126  	mockConnPool.EXPECT().NextClient().Return(mockClient, &noopPooledChannel{}, nil)
   127  
   128  	for _, fetchBatch := range fetchBatches {
   129  		assert.NoError(t, queue.Enqueue(fetchBatch))
   130  	}
   131  
   132  	// Wait for fetch to complete.
   133  	wg.Wait()
   134  
   135  	assert.Equal(t, len(ids), len(results))
   136  
   137  	// Close.
   138  	var closeWg sync.WaitGroup
   139  	closeWg.Add(1)
   140  	mockConnPool.EXPECT().Close().Do(func() {
   141  		closeWg.Done()
   142  	})
   143  	queue.Close()
   144  	closeWg.Wait()
   145  }
   146  
   147  func TestHostQueueFetchBatchesErrorOnNextClientUnavailable(t *testing.T) {
   148  	namespace := "testNs"
   149  	ids := []string{"foo", "bar", "baz", "qux"}
   150  	expectedErr := fmt.Errorf("an error")
   151  	var expected []hostQueueResult
   152  	for range ids {
   153  		expected = append(expected, hostQueueResult{nil, expectedErr})
   154  	}
   155  	opts := &testHostQueueFetchBatchesOptions{
   156  		nextClientErr: expectedErr,
   157  	}
   158  	testHostQueueFetchBatches(t, namespace, ids, nil, expected, opts, func(results []hostQueueResult) {
   159  		assert.Equal(t, expected, results)
   160  	})
   161  }
   162  
   163  func TestHostQueueFetchBatchesErrorOnFetchRawBatchError(t *testing.T) {
   164  	namespace := "testNs"
   165  	ids := []string{"foo", "bar", "baz", "qux"}
   166  	expectedErr := fmt.Errorf("an error")
   167  	var expected []hostQueueResult
   168  	for range ids {
   169  		expected = append(expected, hostQueueResult{nil, expectedErr})
   170  	}
   171  	opts := &testHostQueueFetchBatchesOptions{
   172  		fetchRawBatchErr: expectedErr,
   173  	}
   174  	testHostQueueFetchBatches(t, namespace, ids, nil, expected, opts, func(results []hostQueueResult) {
   175  		assert.Equal(t, expected, results)
   176  	})
   177  }
   178  
   179  func TestHostQueueFetchBatchesErrorOnFetchNoResponse(t *testing.T) {
   180  	namespace := "testNs"
   181  	ids := []string{"foo", "bar", "baz", "qux"}
   182  	result := &rpc.FetchBatchRawResult_{}
   183  	for range ids[:len(ids)-1] {
   184  		result.Elements = append(result.Elements, &rpc.FetchRawResult_{Segments: []*rpc.Segments{}})
   185  	}
   186  	var expected []hostQueueResult
   187  	for i := range ids[:len(ids)-1] {
   188  		expected = append(expected, hostQueueResult{result.Elements[i].Segments, nil})
   189  	}
   190  
   191  	testHostQueueFetchBatches(t, namespace, ids, result, expected, nil, func(results []hostQueueResult) {
   192  		assert.Equal(t, expected, results[:len(results)-1])
   193  		lastResult := results[len(results)-1]
   194  		assert.Nil(t, lastResult.result)
   195  		assert.IsType(t, errQueueFetchNoResponse(""), lastResult.err)
   196  	})
   197  }
   198  
   199  func TestHostQueueFetchBatchesErrorOnResultError(t *testing.T) {
   200  	namespace := "testNs"
   201  	ids := []string{"foo", "bar", "baz", "qux"}
   202  	anError := &rpc.Error{Type: rpc.ErrorType_INTERNAL_ERROR, Message: "an error"}
   203  	result := &rpc.FetchBatchRawResult_{}
   204  	for range ids[:len(ids)-1] {
   205  		result.Elements = append(result.Elements, &rpc.FetchRawResult_{Segments: []*rpc.Segments{}})
   206  	}
   207  	result.Elements = append(result.Elements, &rpc.FetchRawResult_{Err: anError})
   208  	var expected []hostQueueResult
   209  	for i := range ids[:len(ids)-1] {
   210  		expected = append(expected, hostQueueResult{result.Elements[i].Segments, nil})
   211  	}
   212  	testHostQueueFetchBatches(t, namespace, ids, result, expected, nil, func(results []hostQueueResult) {
   213  		assert.Equal(t, expected, results[:len(results)-1])
   214  		rpcErr, ok := results[len(results)-1].err.(*rpc.Error)
   215  		assert.True(t, ok)
   216  		assert.Equal(t, anError.Type, rpcErr.Type)
   217  		assert.Equal(t, anError.Message, rpcErr.Message)
   218  	})
   219  }
   220  
   221  type testHostQueueFetchBatchesOptions struct {
   222  	nextClientErr    error
   223  	fetchRawBatchErr error
   224  }
   225  
   226  func testHostQueueFetchBatches(
   227  	t *testing.T,
   228  	namespace string,
   229  	ids []string,
   230  	result *rpc.FetchBatchRawResult_,
   231  	expected []hostQueueResult,
   232  	testOpts *testHostQueueFetchBatchesOptions,
   233  	assertion func(results []hostQueueResult),
   234  ) {
   235  	for _, opts := range []Options{
   236  		newHostQueueTestOptions().SetUseV2BatchAPIs(false),
   237  		newHostQueueTestOptions().SetUseV2BatchAPIs(true),
   238  	} {
   239  		t.Run(fmt.Sprintf("useV2: %v", opts.UseV2BatchAPIs()), func(t *testing.T) {
   240  			ctrl := gomock.NewController(t)
   241  			defer ctrl.Finish()
   242  
   243  			mockConnPool := NewMockconnectionPool(ctrl)
   244  
   245  			queue := newTestHostQueue(opts)
   246  			queue.connPool = mockConnPool
   247  
   248  			// Open
   249  			mockConnPool.EXPECT().Open()
   250  			queue.Open()
   251  			assert.Equal(t, statusOpen, queue.status)
   252  
   253  			// Prepare callback for fetches
   254  			var (
   255  				results []hostQueueResult
   256  				wg      sync.WaitGroup
   257  			)
   258  			callback := func(r interface{}, err error) {
   259  				results = append(results, hostQueueResult{r, err})
   260  				wg.Done()
   261  			}
   262  
   263  			rawIDs := make([][]byte, len(ids))
   264  
   265  			for i, id := range ids {
   266  				rawIDs[i] = []byte(id)
   267  			}
   268  
   269  			var fetchBatch *fetchBatchOp
   270  			if opts.UseV2BatchAPIs() {
   271  				fetchBatch = &fetchBatchOp{
   272  					request: rpc.FetchBatchRawRequest{
   273  						NameSpace: []byte(namespace),
   274  					},
   275  				}
   276  			} else {
   277  				fetchBatch = &fetchBatchOp{
   278  					request: rpc.FetchBatchRawRequest{
   279  						RangeStart: 0,
   280  						RangeEnd:   1,
   281  						NameSpace:  []byte(namespace),
   282  						Ids:        rawIDs,
   283  					},
   284  				}
   285  			}
   286  
   287  			for _, id := range ids {
   288  				if opts.UseV2BatchAPIs() {
   289  					fetchBatch.requestV2Elements = append(fetchBatch.requestV2Elements, rpc.FetchBatchRawV2RequestElement{
   290  						ID:         []byte(id),
   291  						RangeStart: 0,
   292  						RangeEnd:   1,
   293  					})
   294  				}
   295  				fetchBatch.completionFns = append(fetchBatch.completionFns, callback)
   296  			}
   297  			wg.Add(len(ids))
   298  
   299  			// Prepare mocks for flush
   300  			mockClient := rpc.NewMockTChanNode(ctrl)
   301  
   302  			verifyFetchBatchRawV2 := func(ctx thrift.Context, req *rpc.FetchBatchRawV2Request) {
   303  				assert.Equal(t, 1, len(req.NameSpaces))
   304  				assert.Equal(t, len(ids), len(req.Elements))
   305  				for i, elem := range req.Elements {
   306  					assert.Equal(t, int64(0), elem.NameSpace)
   307  					assert.Equal(t, int64(0), elem.RangeStart)
   308  					assert.Equal(t, int64(1), elem.RangeEnd)
   309  					assert.Equal(t, []byte(ids[i]), elem.ID)
   310  				}
   311  			}
   312  			if testOpts != nil && testOpts.nextClientErr != nil {
   313  				mockConnPool.EXPECT().NextClient().Return(nil, nil, testOpts.nextClientErr)
   314  			} else if testOpts != nil && testOpts.fetchRawBatchErr != nil {
   315  				if opts.UseV2BatchAPIs() {
   316  					mockClient.EXPECT().
   317  						FetchBatchRawV2(gomock.Any(), gomock.Any()).
   318  						Do(verifyFetchBatchRawV2).
   319  						Return(nil, testOpts.fetchRawBatchErr)
   320  				} else {
   321  					fetchBatchRaw := func(ctx thrift.Context, req *rpc.FetchBatchRawRequest) {
   322  						assert.Equal(t, &fetchBatch.request, req)
   323  					}
   324  					mockClient.EXPECT().
   325  						FetchBatchRaw(gomock.Any(), gomock.Any()).
   326  						Do(fetchBatchRaw).
   327  						Return(nil, testOpts.fetchRawBatchErr)
   328  				}
   329  				mockConnPool.EXPECT().NextClient().Return(mockClient, &noopPooledChannel{}, nil)
   330  			} else {
   331  				if opts.UseV2BatchAPIs() {
   332  					mockClient.EXPECT().
   333  						FetchBatchRawV2(gomock.Any(), gomock.Any()).
   334  						Do(verifyFetchBatchRawV2).
   335  						Return(result, nil)
   336  				} else {
   337  					fetchBatchRaw := func(ctx thrift.Context, req *rpc.FetchBatchRawRequest) {
   338  						assert.Equal(t, &fetchBatch.request, req)
   339  					}
   340  					mockClient.EXPECT().
   341  						FetchBatchRaw(gomock.Any(), gomock.Any()).
   342  						Do(fetchBatchRaw).
   343  						Return(result, nil)
   344  				}
   345  
   346  				mockConnPool.EXPECT().NextClient().Return(mockClient, &noopPooledChannel{}, nil)
   347  			}
   348  
   349  			// Fetch
   350  			assert.NoError(t, queue.Enqueue(fetchBatch))
   351  
   352  			// Wait for fetch to complete
   353  			wg.Wait()
   354  
   355  			// Assert results match expected
   356  			assertion(results)
   357  
   358  			// Close
   359  			var closeWg sync.WaitGroup
   360  			closeWg.Add(1)
   361  			mockConnPool.EXPECT().Close().Do(func() {
   362  				closeWg.Done()
   363  			})
   364  			queue.Close()
   365  			closeWg.Wait()
   366  		})
   367  	}
   368  }