github.com/m3db/m3@v1.5.0/src/query/api/v1/handler/prometheus/native/read_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 native 22 23 import ( 24 "context" 25 "net/http" 26 "testing" 27 "time" 28 29 "github.com/m3db/m3/src/cmd/services/m3query/config" 30 "github.com/m3db/m3/src/query/api/v1/handler/prometheus/handleroptions" 31 "github.com/m3db/m3/src/query/api/v1/options" 32 "github.com/m3db/m3/src/query/block" 33 "github.com/m3db/m3/src/query/executor" 34 "github.com/m3db/m3/src/query/models" 35 "github.com/m3db/m3/src/query/parser" 36 "github.com/m3db/m3/src/query/storage" 37 "github.com/m3db/m3/src/query/storage/mock" 38 "github.com/m3db/m3/src/query/test" 39 "github.com/m3db/m3/src/x/instrument" 40 xtest "github.com/m3db/m3/src/x/test" 41 42 "github.com/golang/mock/gomock" 43 "github.com/stretchr/testify/assert" 44 "github.com/stretchr/testify/require" 45 ) 46 47 func TestParseRequest(t *testing.T) { 48 setup := newTestSetup(t, nil) 49 req, _ := http.NewRequest("GET", PromReadURL, nil) 50 req.URL.RawQuery = defaultParams().Encode() 51 52 ctx, parsed, err := ParseRequest(req.Context(), req, false, setup.options) 53 require.NoError(t, err) 54 require.Equal(t, 15*time.Second, parsed.Params.Timeout) 55 require.Equal(t, 15*time.Second, parsed.FetchOpts.Timeout) 56 require.Equal(t, 0, parsed.FetchOpts.DocsLimit) 57 require.Equal(t, 0, parsed.FetchOpts.SeriesLimit) 58 require.Equal(t, false, parsed.FetchOpts.RequireExhaustive) 59 require.Equal(t, 0, parsed.QueryOpts.QueryContextOptions.LimitMaxDocs) 60 require.Equal(t, 0, parsed.QueryOpts.QueryContextOptions.LimitMaxTimeseries) 61 require.Equal(t, false, parsed.QueryOpts.QueryContextOptions.RequireExhaustive) 62 require.Nil(t, parsed.QueryOpts.QueryContextOptions.RestrictFetchType) 63 // Make sure the context has the deadline and http header set. 64 _, ok := ctx.Deadline() 65 require.True(t, ok) 66 header := ctx.Value(handleroptions.RequestHeaderKey) 67 require.NotNil(t, header) 68 _, ok = header.(http.Header) 69 require.True(t, ok) 70 } 71 72 func TestPromReadHandlerRead(t *testing.T) { 73 testPromReadHandlerRead(t, block.NewResultMetadata()) 74 testPromReadHandlerRead(t, buildWarningMeta("foo", "bar")) 75 testPromReadHandlerRead(t, block.ResultMetadata{Exhaustive: false}) 76 } 77 78 func TestPromReadHandlerWithTimeout(t *testing.T) { 79 ctrl := xtest.NewController(t) 80 engine := executor.NewMockEngine(ctrl) 81 engine.EXPECT(). 82 Options(). 83 Return(executor.NewEngineOptions()) 84 engine.EXPECT(). 85 ExecuteExpr(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). 86 DoAndReturn(func(ctx context.Context, 87 parser parser.Parser, 88 opts *executor.QueryOptions, 89 fetchOpts *storage.FetchOptions, 90 params models.RequestParams, 91 ) (block.Block, error) { 92 if err := ctx.Err(); err != nil { 93 return nil, err 94 } 95 return nil, nil 96 }) 97 98 setup := newTestSetup(t, engine) 99 promRead := setup.Handlers.read 100 101 req, _ := http.NewRequest("GET", PromReadURL, nil) 102 req.URL.RawQuery = defaultParams().Encode() 103 ctx := req.Context() 104 var cancel context.CancelFunc 105 // Clients calling into read have the timeout set from the defined fetch params 106 ctx, cancel = context.WithTimeout(ctx, 1*time.Nanosecond) 107 defer cancel() 108 109 r, parseErr := testParseParams(req) 110 require.Nil(t, parseErr) 111 assert.Equal(t, models.FormatPromQL, r.FormatType) 112 parsed := ParsedOptions{ 113 QueryOpts: setup.QueryOpts, 114 FetchOpts: setup.FetchOpts, 115 Params: r, 116 } 117 118 _, err := read(ctx, parsed, promRead.opts) 119 require.Error(t, err) 120 require.Equal(t, 121 "context deadline exceeded", 122 err.Error()) 123 } 124 125 func testPromReadHandlerRead(t *testing.T, resultMeta block.ResultMetadata) { 126 t.Helper() 127 128 values, bounds := test.GenerateValuesAndBounds(nil, nil) 129 130 setup := newTestSetup(t, nil) 131 promRead := setup.Handlers.read 132 133 seriesMeta := test.NewSeriesMeta("dummy", len(values)) 134 m := block.Metadata{ 135 Bounds: bounds, 136 Tags: models.NewTags(0, models.NewTagOptions()), 137 ResultMetadata: resultMeta, 138 } 139 140 b := test.NewBlockFromValuesWithMetaAndSeriesMeta(m, seriesMeta, values) 141 setup.Storage.SetFetchBlocksResult(block.Result{Blocks: []block.Block{b}}, nil) 142 143 req, _ := http.NewRequest("GET", PromReadURL, nil) 144 req.URL.RawQuery = defaultParams().Encode() 145 ctx := req.Context() 146 147 r, parseErr := testParseParams(req) 148 require.Nil(t, parseErr) 149 assert.Equal(t, models.FormatPromQL, r.FormatType) 150 parsed := ParsedOptions{ 151 QueryOpts: setup.QueryOpts, 152 FetchOpts: setup.FetchOpts, 153 Params: r, 154 } 155 156 result, err := read(ctx, parsed, promRead.opts) 157 require.NoError(t, err) 158 seriesList := result.Series 159 160 require.Len(t, seriesList, 2) 161 s := seriesList[0] 162 163 assert.Equal(t, 5, s.Values().Len()) 164 for i := 0; i < s.Values().Len(); i++ { 165 assert.Equal(t, float64(i), s.Values().ValueAt(i)) 166 } 167 } 168 169 type testSetup struct { 170 Storage mock.Storage 171 Handlers testSetupHandlers 172 QueryOpts *executor.QueryOptions 173 FetchOpts *storage.FetchOptions 174 options options.HandlerOptions 175 } 176 177 type testSetupHandlers struct { 178 read *promReadHandler 179 instantRead *promReadHandler 180 } 181 182 func newTestSetup( 183 t *testing.T, 184 mockEngine *executor.MockEngine, 185 ) *testSetup { 186 mockStorage := mock.NewMockStorage() 187 188 instrumentOpts := instrument.NewOptions() 189 engineOpts := executor.NewEngineOptions(). 190 SetStore(mockStorage). 191 SetLookbackDuration(time.Minute). 192 SetInstrumentOptions(instrumentOpts) 193 engine := executor.NewEngine(engineOpts) 194 if mockEngine != nil { 195 engine = mockEngine 196 } 197 fetchOptsBuilderCfg := handleroptions.FetchOptionsBuilderOptions{ 198 Timeout: 15 * time.Second, 199 } 200 fetchOptsBuilder, err := handleroptions.NewFetchOptionsBuilder(fetchOptsBuilderCfg) 201 require.NoError(t, err) 202 tagOpts := models.NewTagOptions() 203 limitsConfig := config.LimitsConfiguration{} 204 keepNaNs := false 205 206 opts := options.EmptyHandlerOptions(). 207 SetEngine(engine). 208 SetFetchOptionsBuilder(fetchOptsBuilder). 209 SetTagOptions(tagOpts). 210 SetInstrumentOpts(instrumentOpts). 211 SetStorage(mockStorage). 212 SetConfig(config.Configuration{ 213 Limits: limitsConfig, 214 ResultOptions: config.ResultOptions{ 215 KeepNaNs: keepNaNs, 216 }, 217 }) 218 219 read := NewPromReadHandler(opts).(*promReadHandler) 220 instantRead := NewPromReadInstantHandler(opts).(*promReadHandler) 221 222 return &testSetup{ 223 Storage: mockStorage, 224 Handlers: testSetupHandlers{ 225 read: read, 226 instantRead: instantRead, 227 }, 228 QueryOpts: &executor.QueryOptions{}, 229 FetchOpts: storage.NewFetchOptions(), 230 options: opts, 231 } 232 }