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  }