github.com/m3db/m3@v1.5.0/src/dbnode/network/server/tchannelthrift/convert/convert_test.go (about)

     1  // Copyright (c) 2021 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 convert_test
    22  
    23  import (
    24  	stdctx "context"
    25  	"errors"
    26  	"fmt"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/m3db/m3/src/dbnode/generated/thrift/rpc"
    31  	"github.com/m3db/m3/src/dbnode/network/server/tchannelthrift/convert"
    32  	tterrors "github.com/m3db/m3/src/dbnode/network/server/tchannelthrift/errors"
    33  	"github.com/m3db/m3/src/dbnode/storage/index"
    34  	"github.com/m3db/m3/src/dbnode/storage/limits"
    35  	"github.com/m3db/m3/src/dbnode/x/xpool"
    36  	"github.com/m3db/m3/src/m3ninx/idx"
    37  	xerrors "github.com/m3db/m3/src/x/errors"
    38  	"github.com/m3db/m3/src/x/ident"
    39  	"github.com/m3db/m3/src/x/pool"
    40  	xtime "github.com/m3db/m3/src/x/time"
    41  
    42  	"github.com/google/go-cmp/cmp"
    43  	"github.com/stretchr/testify/assert"
    44  	"github.com/stretchr/testify/require"
    45  )
    46  
    47  func mustToRPCTime(t *testing.T, ts xtime.UnixNano) int64 {
    48  	r, err := convert.ToValue(ts, rpc.TimeType_UNIX_NANOSECONDS)
    49  	require.NoError(t, err)
    50  	return r
    51  }
    52  
    53  func allQueryTestCase(t *testing.T) (idx.Query, []byte) {
    54  	q := idx.NewAllQuery()
    55  	d, err := idx.Marshal(q)
    56  	require.NoError(t, err)
    57  	return q, d
    58  }
    59  
    60  func fieldQueryTestCase(t *testing.T) (idx.Query, []byte) {
    61  	q1 := idx.NewFieldQuery([]byte("dat"))
    62  	data, err := idx.Marshal(q1)
    63  	require.NoError(t, err)
    64  	return q1, data
    65  }
    66  
    67  func termQueryTestCase(t *testing.T) (idx.Query, []byte) {
    68  	q1 := idx.NewTermQuery([]byte("dat"), []byte("baz"))
    69  	data, err := idx.Marshal(q1)
    70  	require.NoError(t, err)
    71  	return q1, data
    72  }
    73  
    74  func regexpQueryTestCase(t *testing.T) (idx.Query, []byte) {
    75  	q2, err := idx.NewRegexpQuery([]byte("foo"), []byte("b.*"))
    76  	require.NoError(t, err)
    77  	data, err := idx.Marshal(q2)
    78  	require.NoError(t, err)
    79  	return q2, data
    80  }
    81  
    82  func negateTermQueryTestCase(t *testing.T) (idx.Query, []byte) {
    83  	q3 := idx.NewNegationQuery(idx.NewTermQuery([]byte("foo"), []byte("bar")))
    84  	data, err := idx.Marshal(q3)
    85  	require.NoError(t, err)
    86  	return q3, data
    87  }
    88  
    89  func negateRegexpQueryTestCase(t *testing.T) (idx.Query, []byte) {
    90  	inner, err := idx.NewRegexpQuery([]byte("foo"), []byte("b.*"))
    91  	require.NoError(t, err)
    92  	q4 := idx.NewNegationQuery(inner)
    93  	data, err := idx.Marshal(q4)
    94  	require.NoError(t, err)
    95  	return q4, data
    96  }
    97  
    98  func conjunctionQueryATestCase(t *testing.T) (idx.Query, []byte) {
    99  	q1, _ := termQueryTestCase(t)
   100  	q2, _ := regexpQueryTestCase(t)
   101  	q3, _ := negateTermQueryTestCase(t)
   102  	q4, _ := negateRegexpQueryTestCase(t)
   103  	q := idx.NewConjunctionQuery(q1, q2, q3, q4)
   104  	data, err := idx.Marshal(q)
   105  	require.NoError(t, err)
   106  	return q, data
   107  }
   108  
   109  func TestConvertFetchTaggedRequest(t *testing.T) {
   110  	var (
   111  		seriesLimit int64 = 10
   112  		docsLimit   int64 = 10
   113  	)
   114  	ns := ident.StringID("abc")
   115  	opts := index.QueryOptions{
   116  		StartInclusive:    xtime.Now().Add(-900 * time.Hour),
   117  		EndExclusive:      xtime.Now(),
   118  		SeriesLimit:       int(seriesLimit),
   119  		DocsLimit:         int(docsLimit),
   120  		RequireExhaustive: true,
   121  		RequireNoWait:     true,
   122  	}
   123  	fetchData := true
   124  	requestSkeleton := &rpc.FetchTaggedRequest{
   125  		NameSpace:         ns.Bytes(),
   126  		RangeStart:        mustToRPCTime(t, opts.StartInclusive),
   127  		RangeEnd:          mustToRPCTime(t, opts.EndExclusive),
   128  		FetchData:         fetchData,
   129  		SeriesLimit:       &seriesLimit,
   130  		DocsLimit:         &docsLimit,
   131  		RequireExhaustive: true,
   132  		RequireNoWait:     true,
   133  	}
   134  	requireEqual := func(a, b interface{}) {
   135  		d := cmp.Diff(a, b)
   136  		assert.Equal(t, "", d, d)
   137  	}
   138  
   139  	type inputFn func(t *testing.T) (idx.Query, []byte)
   140  
   141  	for _, pools := range []struct {
   142  		name string
   143  		pool convert.FetchTaggedConversionPools
   144  	}{
   145  		{"nil pools", nil},
   146  		{"valid pools", newTestPools()},
   147  	} {
   148  		testCases := []struct {
   149  			name string
   150  			fn   inputFn
   151  		}{
   152  			{"Field Query", fieldQueryTestCase},
   153  			{"Term Query", termQueryTestCase},
   154  			{"Regexp Query", regexpQueryTestCase},
   155  			{"Negate Term Query", negateTermQueryTestCase},
   156  			{"Negate Regexp Query", negateRegexpQueryTestCase},
   157  			{"Conjunction Query A", conjunctionQueryATestCase},
   158  		}
   159  		for _, tc := range testCases {
   160  			t.Run(fmt.Sprintf("(%s pools) Forward %s", pools.name, tc.name), func(t *testing.T) {
   161  				q, rpcQ := tc.fn(t)
   162  				expectedReq := &(*requestSkeleton)
   163  				expectedReq.Query = rpcQ
   164  				observedReq, err := convert.ToRPCFetchTaggedRequest(ns, index.Query{Query: q}, opts, fetchData)
   165  				require.NoError(t, err)
   166  				requireEqual(expectedReq, &observedReq)
   167  			})
   168  			t.Run(fmt.Sprintf("(%s pools) Backward %s", pools.name, tc.name), func(t *testing.T) {
   169  				expectedQuery, rpcQ := tc.fn(t)
   170  				rpcRequest := &(*requestSkeleton)
   171  				rpcRequest.Query = rpcQ
   172  				id, observedQuery, observedOpts, fetch, err := convert.FromRPCFetchTaggedRequest(rpcRequest, pools.pool)
   173  				require.NoError(t, err)
   174  				require.Equal(t, ns.String(), id.String())
   175  				require.True(t, index.NewQueryMatcher(index.Query{Query: expectedQuery}).Matches(observedQuery))
   176  				requireEqual(fetchData, fetch)
   177  				requireEqual(opts, observedOpts)
   178  			})
   179  		}
   180  	}
   181  }
   182  
   183  func TestConvertAggregateRawQueryRequest(t *testing.T) {
   184  	var (
   185  		seriesLimit       int64 = 10
   186  		docsLimit         int64 = 10
   187  		requireExhaustive       = true
   188  		requireNoWait           = true
   189  		ns                      = ident.StringID("abc")
   190  	)
   191  	opts := index.AggregationOptions{
   192  		QueryOptions: index.QueryOptions{
   193  			StartInclusive:    xtime.Now().Add(-900 * time.Hour),
   194  			EndExclusive:      xtime.Now(),
   195  			SeriesLimit:       int(seriesLimit),
   196  			DocsLimit:         int(docsLimit),
   197  			RequireExhaustive: requireExhaustive,
   198  			RequireNoWait:     requireNoWait,
   199  		},
   200  		Type: index.AggregateTagNamesAndValues,
   201  		FieldFilter: index.AggregateFieldFilter{
   202  			[]byte("some"),
   203  			[]byte("string"),
   204  		},
   205  	}
   206  	requestSkeleton := &rpc.AggregateQueryRawRequest{
   207  		NameSpace:         ns.Bytes(),
   208  		RangeStart:        mustToRPCTime(t, opts.StartInclusive),
   209  		RangeEnd:          mustToRPCTime(t, opts.EndExclusive),
   210  		SeriesLimit:       &seriesLimit,
   211  		DocsLimit:         &docsLimit,
   212  		RequireExhaustive: &requireExhaustive,
   213  		RequireNoWait:     &requireNoWait,
   214  		TagNameFilter: [][]byte{
   215  			[]byte("some"),
   216  			[]byte("string"),
   217  		},
   218  		AggregateQueryType: rpc.AggregateQueryType_AGGREGATE_BY_TAG_NAME_VALUE,
   219  	}
   220  	requireEqual := func(a, b interface{}) {
   221  		d := cmp.Diff(a, b)
   222  		assert.Equal(t, "", d, d)
   223  	}
   224  
   225  	type inputFn func(t *testing.T) (idx.Query, []byte)
   226  
   227  	for _, pools := range []struct {
   228  		name string
   229  		pool convert.FetchTaggedConversionPools
   230  	}{
   231  		{"nil pools", nil},
   232  		{"valid pools", newTestPools()},
   233  	} {
   234  		testCases := []struct {
   235  			name string
   236  			fn   inputFn
   237  		}{
   238  			{"All Query", allQueryTestCase},
   239  			{"Field Query", fieldQueryTestCase},
   240  			{"Term Query", termQueryTestCase},
   241  			{"Regexp Query", regexpQueryTestCase},
   242  			{"Negate Term Query", negateTermQueryTestCase},
   243  			{"Negate Regexp Query", negateRegexpQueryTestCase},
   244  			{"Conjunction Query A", conjunctionQueryATestCase},
   245  		}
   246  		for _, tc := range testCases {
   247  			t.Run(fmt.Sprintf("%s forward %s", pools.name, tc.name), func(t *testing.T) {
   248  				q, rpcQ := tc.fn(t)
   249  				expectedReq := &(*requestSkeleton)
   250  				expectedReq.Query = rpcQ
   251  				observedReq, err := convert.ToRPCAggregateQueryRawRequest(ns, index.Query{Query: q}, opts)
   252  				require.NoError(t, err)
   253  				requireEqual(expectedReq, &observedReq)
   254  			})
   255  			t.Run(fmt.Sprintf("%s backward %s", pools.name, tc.name), func(t *testing.T) {
   256  				expectedQuery, rpcQ := tc.fn(t)
   257  				rpcRequest := &(*requestSkeleton)
   258  				rpcRequest.Query = rpcQ
   259  				id, observedQuery, observedOpts, err := convert.FromRPCAggregateQueryRawRequest(rpcRequest, pools.pool)
   260  				require.NoError(t, err)
   261  				require.Equal(t, ns.String(), id.String())
   262  				require.True(t, index.NewQueryMatcher(index.Query{Query: expectedQuery}).Matches(observedQuery))
   263  				requireEqual(opts, observedOpts)
   264  			})
   265  		}
   266  	}
   267  }
   268  
   269  func TestToRPCError(t *testing.T) {
   270  	limitErr := limits.NewQueryLimitExceededError("limit")
   271  	invalidParamsErr := xerrors.NewInvalidParamsError(errors.New("param"))
   272  
   273  	require.Equal(t, tterrors.NewResourceExhaustedError(limitErr), convert.ToRPCError(limitErr))
   274  	require.Equal(
   275  		t,
   276  		tterrors.NewResourceExhaustedError(xerrors.Wrap(limitErr, "wrap")),
   277  		convert.ToRPCError(xerrors.Wrap(limitErr, "wrap")),
   278  	)
   279  
   280  	require.Equal(t, tterrors.NewBadRequestError(invalidParamsErr), convert.ToRPCError(invalidParamsErr))
   281  	require.Equal(
   282  		t,
   283  		tterrors.NewBadRequestError(xerrors.Wrap(invalidParamsErr, "wrap")),
   284  		convert.ToRPCError(xerrors.Wrap(invalidParamsErr, "wrap")),
   285  	)
   286  
   287  	require.Equal(t, tterrors.NewTimeoutError(stdctx.Canceled), convert.ToRPCError(stdctx.Canceled))
   288  	require.Equal(t, tterrors.NewTimeoutError(stdctx.DeadlineExceeded), convert.ToRPCError(stdctx.DeadlineExceeded))
   289  	require.Equal(
   290  		t,
   291  		tterrors.NewTimeoutError(xerrors.Wrap(stdctx.Canceled, "wrap")),
   292  		convert.ToRPCError(xerrors.Wrap(stdctx.Canceled, "wrap")),
   293  	)
   294  	require.Equal(
   295  		t,
   296  		tterrors.NewTimeoutError(xerrors.Wrap(stdctx.DeadlineExceeded, "wrap")),
   297  		convert.ToRPCError(xerrors.Wrap(stdctx.DeadlineExceeded, "wrap")),
   298  	)
   299  }
   300  
   301  type testPools struct {
   302  	id      ident.Pool
   303  	wrapper xpool.CheckedBytesWrapperPool
   304  }
   305  
   306  func newTestPools() *testPools {
   307  	poolOpts := pool.NewObjectPoolOptions().SetSize(1)
   308  	id := ident.NewPool(nil, ident.PoolOptions{
   309  		IDPoolOptions:           poolOpts,
   310  		TagsPoolOptions:         poolOpts,
   311  		TagsIteratorPoolOptions: poolOpts,
   312  	})
   313  	wrapper := xpool.NewCheckedBytesWrapperPool(poolOpts)
   314  	wrapper.Init()
   315  	return &testPools{id, wrapper}
   316  }
   317  
   318  var _ convert.FetchTaggedConversionPools = &testPools{}
   319  
   320  func (t *testPools) ID() ident.Pool                                     { return t.id }
   321  func (t *testPools) CheckedBytesWrapper() xpool.CheckedBytesWrapperPool { return t.wrapper }