github.com/m3db/m3@v1.5.0/src/query/api/v1/handler/graphite/render_test.go (about)

     1  // Copyright (c) 2019 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 graphite
    22  
    23  import (
    24  	"fmt"
    25  	"io/ioutil"
    26  	"math"
    27  	"net/http"
    28  	"net/http/httptest"
    29  	"testing"
    30  	"time"
    31  
    32  	"github.com/m3db/m3/src/query/api/v1/handler/prometheus/handleroptions"
    33  	"github.com/m3db/m3/src/query/api/v1/options"
    34  	"github.com/m3db/m3/src/query/block"
    35  	"github.com/m3db/m3/src/query/graphite/graphite"
    36  	graphiteStorage "github.com/m3db/m3/src/query/graphite/storage"
    37  	"github.com/m3db/m3/src/query/models"
    38  	"github.com/m3db/m3/src/query/storage"
    39  	"github.com/m3db/m3/src/query/storage/mock"
    40  	"github.com/m3db/m3/src/query/ts"
    41  	"github.com/m3db/m3/src/x/headers"
    42  	xtest "github.com/m3db/m3/src/x/test"
    43  	xtime "github.com/m3db/m3/src/x/time"
    44  
    45  	"github.com/golang/mock/gomock"
    46  	"github.com/stretchr/testify/assert"
    47  	"github.com/stretchr/testify/require"
    48  )
    49  
    50  func testHandlerOptions(t *testing.T) options.HandlerOptions {
    51  	fetchOptsBuilder, err := handleroptions.NewFetchOptionsBuilder(
    52  		handleroptions.FetchOptionsBuilderOptions{
    53  			Timeout: 15 * time.Second,
    54  		})
    55  	require.NoError(t, err)
    56  
    57  	return options.EmptyHandlerOptions().
    58  		SetQueryContextOptions(models.QueryContextOptions{}).
    59  		SetGraphiteFindFetchOptionsBuilder(fetchOptsBuilder).
    60  		SetGraphiteRenderFetchOptionsBuilder(fetchOptsBuilder)
    61  }
    62  
    63  func makeBlockResult(
    64  	ctrl *gomock.Controller,
    65  	results *storage.FetchResult,
    66  ) block.Result {
    67  	size := len(results.SeriesList)
    68  	unconsolidatedSeries := make([]block.UnconsolidatedSeries, 0, size)
    69  	metas := make([]block.SeriesMeta, 0, size)
    70  	for _, elem := range results.SeriesList {
    71  		meta := block.SeriesMeta{Name: elem.Name()}
    72  		series := block.NewUnconsolidatedSeries(elem.Values().Datapoints(),
    73  			meta, block.UnconsolidatedSeriesStats{})
    74  		unconsolidatedSeries = append(unconsolidatedSeries, series)
    75  		metas = append(metas, meta)
    76  	}
    77  
    78  	bl := block.NewMockBlock(ctrl)
    79  	bl.EXPECT().
    80  		SeriesIter().
    81  		DoAndReturn(func() (block.SeriesIter, error) {
    82  			return block.NewUnconsolidatedSeriesIter(unconsolidatedSeries), nil
    83  		}).
    84  		AnyTimes()
    85  	bl.EXPECT().Close().Return(nil)
    86  
    87  	return block.Result{
    88  		Blocks:   []block.Block{bl},
    89  		Metadata: results.Metadata,
    90  	}
    91  }
    92  
    93  func TestParseNoQuery(t *testing.T) {
    94  	mockStorage := mock.NewMockStorage()
    95  
    96  	opts := testHandlerOptions(t).SetStorage(mockStorage)
    97  	handler := NewRenderHandler(opts)
    98  
    99  	recorder := httptest.NewRecorder()
   100  	handler.ServeHTTP(recorder, newGraphiteReadHTTPRequest(t))
   101  
   102  	res := recorder.Result()
   103  	require.Equal(t, 400, res.StatusCode)
   104  }
   105  
   106  func TestParseQueryNoResults(t *testing.T) {
   107  	ctrl := xtest.NewController(t)
   108  	defer ctrl.Finish()
   109  
   110  	store := storage.NewMockStorage(ctrl)
   111  	blockResult := makeBlockResult(ctrl, &storage.FetchResult{})
   112  	store.EXPECT().FetchBlocks(gomock.Any(), gomock.Any(), gomock.Any()).
   113  		Return(blockResult, nil)
   114  
   115  	opts := testHandlerOptions(t).SetStorage(store)
   116  	handler := NewRenderHandler(opts)
   117  
   118  	req := newGraphiteReadHTTPRequest(t)
   119  	req.URL.RawQuery = "target=foo.bar&from=-2h&until=now"
   120  	recorder := httptest.NewRecorder()
   121  	handler.ServeHTTP(recorder, req)
   122  
   123  	res := recorder.Result()
   124  	require.Equal(t, 200, res.StatusCode)
   125  
   126  	buf, err := ioutil.ReadAll(res.Body)
   127  	require.NoError(t, err)
   128  	require.Equal(t, []byte("[]"), buf)
   129  }
   130  
   131  func TestParseQueryResults(t *testing.T) {
   132  	resolution := 10 * time.Second
   133  	truncateStart := xtime.Now().Add(-30 * time.Minute).Truncate(resolution)
   134  	start := truncateStart.Add(time.Second)
   135  	vals := ts.NewFixedStepValues(resolution, 3, 3, start)
   136  	tags := models.NewTags(0, nil)
   137  	tags = tags.AddTag(models.Tag{Name: graphite.TagName(0), Value: []byte("foo")})
   138  	tags = tags.AddTag(models.Tag{Name: graphite.TagName(1), Value: []byte("bar")})
   139  	seriesList := ts.SeriesList{
   140  		ts.NewSeries([]byte("series_name"), vals, tags),
   141  	}
   142  
   143  	meta := block.NewResultMetadata()
   144  	meta.Resolutions = []time.Duration{resolution}
   145  	fr := &storage.FetchResult{
   146  		SeriesList: seriesList,
   147  		Metadata:   meta,
   148  	}
   149  
   150  	ctrl := xtest.NewController(t)
   151  	defer ctrl.Finish()
   152  
   153  	store := storage.NewMockStorage(ctrl)
   154  	blockResult := makeBlockResult(ctrl, fr)
   155  	store.EXPECT().FetchBlocks(gomock.Any(), gomock.Any(), gomock.Any()).
   156  		Return(blockResult, nil)
   157  
   158  	opts := testHandlerOptions(t).SetStorage(store)
   159  	handler := NewRenderHandler(opts)
   160  
   161  	req := newGraphiteReadHTTPRequest(t)
   162  	req.URL.RawQuery = fmt.Sprintf("target=foo.bar&from=%d&until=%d",
   163  		start.Seconds(), start.Seconds()+30)
   164  	recorder := httptest.NewRecorder()
   165  	handler.ServeHTTP(recorder, req)
   166  
   167  	res := recorder.Result()
   168  	assert.Equal(t, 200, res.StatusCode)
   169  
   170  	buf, err := ioutil.ReadAll(res.Body)
   171  	require.NoError(t, err)
   172  	exTimestamp := truncateStart.Seconds() + 10
   173  	expected := fmt.Sprintf(
   174  		`[{"target":"series_name","datapoints":[[3.000000,%d],`+
   175  			`[3.000000,%d],[null,%d]],"step_size_ms":%d}]`,
   176  		exTimestamp, exTimestamp+10, exTimestamp+20, resolution/time.Millisecond)
   177  
   178  	require.Equal(t, expected, string(buf))
   179  }
   180  
   181  func TestParseQueryResultsMaxDatapoints(t *testing.T) {
   182  	startStr := "03/07/14"
   183  	endStr := "03/07/15"
   184  	start, err := graphite.ParseTime(startStr, time.Now(), 0)
   185  	require.NoError(t, err)
   186  	end, err := graphite.ParseTime(endStr, time.Now(), 0)
   187  	require.NoError(t, err)
   188  
   189  	resolution := 10 * time.Second
   190  	vals := ts.NewFixedStepValues(resolution, 4, 4, xtime.ToUnixNano(start))
   191  	seriesList := ts.SeriesList{
   192  		ts.NewSeries([]byte("a"), vals, models.NewTags(0, nil)),
   193  	}
   194  
   195  	meta := block.NewResultMetadata()
   196  	meta.Resolutions = []time.Duration{resolution}
   197  	fr := &storage.FetchResult{
   198  		SeriesList: seriesList,
   199  		Metadata:   meta,
   200  	}
   201  
   202  	ctrl := xtest.NewController(t)
   203  	defer ctrl.Finish()
   204  
   205  	store := storage.NewMockStorage(ctrl)
   206  	blockResult := makeBlockResult(ctrl, fr)
   207  	store.EXPECT().FetchBlocks(gomock.Any(), gomock.Any(), gomock.Any()).
   208  		Return(blockResult, nil)
   209  
   210  	opts := testHandlerOptions(t).SetStorage(store)
   211  	handler := NewRenderHandler(opts)
   212  
   213  	req := newGraphiteReadHTTPRequest(t)
   214  	req.URL.RawQuery = fmt.Sprintf(
   215  		"target=foo.bar&from=%s&until=%s&maxDataPoints=1",
   216  		startStr, endStr,
   217  	)
   218  	recorder := httptest.NewRecorder()
   219  	handler.ServeHTTP(recorder, req)
   220  
   221  	res := recorder.Result()
   222  	require.Equal(t, 200, res.StatusCode)
   223  
   224  	buf, err := ioutil.ReadAll(res.Body)
   225  	require.NoError(t, err)
   226  
   227  	// Expected resolution should be in milliseconds and subsume all datapoints.
   228  	exStep := end.Sub(start) / time.Millisecond
   229  	expected := fmt.Sprintf(
   230  		`[{"target":"a","datapoints":[[4.000000,%d]],"step_size_ms":%d}]`,
   231  		start.Unix(), exStep)
   232  
   233  	require.Equal(t, expected, string(buf))
   234  }
   235  
   236  func TestParseQueryResultsMultiTarget(t *testing.T) {
   237  	minsAgo := 12
   238  	resolution := 10 * time.Second
   239  	start := time.Now().
   240  		Add(-1 * time.Duration(minsAgo) * time.Minute).
   241  		Truncate(resolution)
   242  
   243  	vals := ts.NewFixedStepValues(resolution, 3, 3, xtime.ToUnixNano(start))
   244  	seriesList := ts.SeriesList{
   245  		ts.NewSeries([]byte("a"), vals, models.NewTags(0, nil)),
   246  	}
   247  
   248  	meta := block.NewResultMetadata()
   249  	meta.Resolutions = []time.Duration{resolution}
   250  	fr := &storage.FetchResult{
   251  		SeriesList: seriesList,
   252  		Metadata:   meta,
   253  	}
   254  
   255  	ctrl := xtest.NewController(t)
   256  	defer ctrl.Finish()
   257  
   258  	store := storage.NewMockStorage(ctrl)
   259  	store.EXPECT().FetchBlocks(gomock.Any(), gomock.Any(), gomock.Any()).
   260  		Return(makeBlockResult(ctrl, fr), nil)
   261  	store.EXPECT().FetchBlocks(gomock.Any(), gomock.Any(), gomock.Any()).
   262  		Return(makeBlockResult(ctrl, fr), nil)
   263  
   264  	opts := testHandlerOptions(t).SetStorage(store)
   265  	handler := NewRenderHandler(opts)
   266  
   267  	req := newGraphiteReadHTTPRequest(t)
   268  	req.URL.RawQuery = fmt.Sprintf(
   269  		"target=foo.bar&target=baz.qux&from=%d&until=%d",
   270  		start.Unix(), start.Unix()+30,
   271  	)
   272  	recorder := httptest.NewRecorder()
   273  	handler.ServeHTTP(recorder, req)
   274  
   275  	res := recorder.Result()
   276  	require.Equal(t, 200, res.StatusCode)
   277  
   278  	buf, err := ioutil.ReadAll(res.Body)
   279  	require.NoError(t, err)
   280  
   281  	expected := fmt.Sprintf(
   282  		`[{"target":"a","datapoints":[[3.000000,%d],`+
   283  			`[3.000000,%d],[3.000000,%d]],"step_size_ms":%d},`+
   284  			`{"target":"a","datapoints":[[3.000000,%d],`+
   285  			`[3.000000,%d],[3.000000,%d]],"step_size_ms":%d}]`,
   286  		start.Unix(), start.Unix()+10, start.Unix()+20, resolution/time.Millisecond,
   287  		start.Unix(), start.Unix()+10, start.Unix()+20, resolution/time.Millisecond)
   288  
   289  	require.Equal(t, expected, string(buf))
   290  }
   291  
   292  func TestParseQueryResultsMultiTargetWithLimits(t *testing.T) {
   293  	for _, tt := range limitTests {
   294  		t.Run(tt.name, func(t *testing.T) {
   295  			minsAgo := 12
   296  			start := time.Now().Add(-1 * time.Duration(minsAgo) * time.Minute)
   297  			resolution := 10 * time.Second
   298  			vals := ts.NewFixedStepValues(resolution, 3, 3, xtime.ToUnixNano(start))
   299  			seriesList := ts.SeriesList{
   300  				ts.NewSeries([]byte("a"), vals, models.NewTags(0, nil)),
   301  			}
   302  
   303  			meta := block.NewResultMetadata()
   304  			meta.Resolutions = []time.Duration{resolution}
   305  			meta.Exhaustive = tt.ex
   306  			frOne := &storage.FetchResult{SeriesList: seriesList, Metadata: meta}
   307  
   308  			metaTwo := block.NewResultMetadata()
   309  			metaTwo.Resolutions = []time.Duration{resolution}
   310  			if !tt.ex2 {
   311  				metaTwo.AddWarning("foo", "bar")
   312  			}
   313  
   314  			frTwo := &storage.FetchResult{SeriesList: seriesList, Metadata: metaTwo}
   315  
   316  			ctrl := xtest.NewController(t)
   317  			defer ctrl.Finish()
   318  
   319  			store := storage.NewMockStorage(ctrl)
   320  			store.EXPECT().FetchBlocks(gomock.Any(), gomock.Any(), gomock.Any()).
   321  				Return(makeBlockResult(ctrl, frOne), nil)
   322  			store.EXPECT().FetchBlocks(gomock.Any(), gomock.Any(), gomock.Any()).
   323  				Return(makeBlockResult(ctrl, frTwo), nil)
   324  
   325  			opts := testHandlerOptions(t).SetStorage(store)
   326  			handler := NewRenderHandler(opts)
   327  
   328  			req := newGraphiteReadHTTPRequest(t)
   329  			req.URL.RawQuery = fmt.Sprintf(
   330  				"target=foo.bar&target=bar.baz&from=%d&until=%d",
   331  				start.Unix(), start.Unix()+30,
   332  			)
   333  			recorder := httptest.NewRecorder()
   334  			handler.ServeHTTP(recorder, req)
   335  
   336  			actual := recorder.Header().Get(headers.LimitHeader)
   337  			assert.Equal(t, tt.header, actual)
   338  		})
   339  	}
   340  }
   341  
   342  func TestParseQueryResultsAllNaN(t *testing.T) {
   343  	resolution := 10 * time.Second
   344  	truncateStart := time.Now().Add(-30 * time.Minute).Truncate(resolution)
   345  	start := truncateStart.Add(time.Second)
   346  	vals := ts.NewFixedStepValues(resolution, 3, math.NaN(), xtime.ToUnixNano(start))
   347  	tags := models.NewTags(0, nil)
   348  	seriesList := ts.SeriesList{
   349  		ts.NewSeries([]byte("series_name"), vals, tags),
   350  	}
   351  
   352  	meta := block.NewResultMetadata()
   353  	meta.Resolutions = []time.Duration{resolution}
   354  	fr := &storage.FetchResult{
   355  		SeriesList: seriesList,
   356  		Metadata:   meta,
   357  	}
   358  
   359  	ctrl := xtest.NewController(t)
   360  	defer ctrl.Finish()
   361  
   362  	store := storage.NewMockStorage(ctrl)
   363  	blockResult := makeBlockResult(ctrl, fr)
   364  	store.EXPECT().FetchBlocks(gomock.Any(), gomock.Any(), gomock.Any()).
   365  		Return(blockResult, nil)
   366  
   367  	graphiteStorageOpts := graphiteStorage.M3WrappedStorageOptions{
   368  		RenderSeriesAllNaNs: true,
   369  	}
   370  	opts := testHandlerOptions(t).
   371  		SetStorage(store).
   372  		SetGraphiteStorageOptions(graphiteStorageOpts)
   373  	handler := NewRenderHandler(opts)
   374  
   375  	req := newGraphiteReadHTTPRequest(t)
   376  	req.URL.RawQuery = fmt.Sprintf("target=foo.bar&from=%d&until=%d",
   377  		start.Unix(), start.Unix()+30)
   378  	recorder := httptest.NewRecorder()
   379  	handler.ServeHTTP(recorder, req)
   380  
   381  	res := recorder.Result()
   382  	assert.Equal(t, 200, res.StatusCode)
   383  
   384  	buf, err := ioutil.ReadAll(res.Body)
   385  	require.NoError(t, err)
   386  	exTimestamp := truncateStart.Unix() + 10
   387  	expected := fmt.Sprintf(
   388  		`[{"target":"series_name","datapoints":[[null,%d],`+
   389  			`[null,%d],[null,%d]],"step_size_ms":%d}]`,
   390  		exTimestamp, exTimestamp+10, exTimestamp+20, resolution/time.Millisecond)
   391  
   392  	require.Equal(t, expected, string(buf))
   393  }
   394  
   395  func newGraphiteReadHTTPRequest(t *testing.T) *http.Request {
   396  	req, err := http.NewRequest(ReadHTTPMethods[0], ReadURL, nil)
   397  	require.NoError(t, err)
   398  	return req
   399  }