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 }