github.com/galamsiva2020/kubernetes-heapster-monitoring@v0.0.0-20210823134957-3c1baa7c1e70/metrics/api/v1/historical_handlers_test.go (about)

     1  // Copyright 2016 Google Inc. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package v1
    16  
    17  import (
    18  	"bytes"
    19  	"encoding/json"
    20  	"fmt"
    21  	"net/http"
    22  	"net/url"
    23  	"testing"
    24  	"time"
    25  
    26  	"github.com/stretchr/testify/assert"
    27  	"github.com/stretchr/testify/require"
    28  
    29  	restful "github.com/emicklei/go-restful"
    30  	"k8s.io/heapster/metrics/api/v1/types"
    31  	"k8s.io/heapster/metrics/core"
    32  )
    33  
    34  type metricReq struct {
    35  	name   string
    36  	keys   []core.HistoricalKey
    37  	start  time.Time
    38  	end    time.Time
    39  	labels map[string]string
    40  
    41  	aggregations []core.AggregationType
    42  	bucketSize   time.Duration
    43  }
    44  
    45  type fakeHistoricalSource struct {
    46  	metricNames map[core.HistoricalKey][]string
    47  
    48  	nodes             []string
    49  	namespaces        []string
    50  	podsForNamespace  map[string][]string
    51  	containersForNode map[string][]string
    52  
    53  	metricRequests      []metricReq
    54  	aggregationRequests []metricReq
    55  
    56  	nowTime time.Time
    57  }
    58  
    59  func (src *fakeHistoricalSource) GetMetric(metricName string, metricKeys []core.HistoricalKey, start, end time.Time) (map[core.HistoricalKey][]core.TimestampedMetricValue, error) {
    60  	return src.GetLabeledMetric(metricName, nil, metricKeys, start, end)
    61  }
    62  
    63  func (src *fakeHistoricalSource) GetLabeledMetric(metricName string, labels map[string]string, metricKeys []core.HistoricalKey, start, end time.Time) (map[core.HistoricalKey][]core.TimestampedMetricValue, error) {
    64  	if metricName == "invalid" {
    65  		return nil, fmt.Errorf("fake error fetching metrics")
    66  	}
    67  
    68  	src.metricRequests = append(src.metricRequests, metricReq{
    69  		name:   metricName,
    70  		keys:   metricKeys,
    71  		labels: labels,
    72  		start:  start,
    73  		end:    end,
    74  	})
    75  
    76  	res := make(map[core.HistoricalKey][]core.TimestampedMetricValue, len(metricKeys))
    77  
    78  	for _, key := range metricKeys {
    79  		res[key] = []core.TimestampedMetricValue{
    80  			{
    81  				Timestamp: src.nowTime.Add(-10 * time.Second),
    82  				MetricValue: core.MetricValue{
    83  					ValueType:  core.ValueFloat,
    84  					FloatValue: 33,
    85  				},
    86  			},
    87  		}
    88  	}
    89  
    90  	return res, nil
    91  }
    92  
    93  func (src *fakeHistoricalSource) GetAggregation(metricName string, aggregations []core.AggregationType, metricKeys []core.HistoricalKey, start, end time.Time, bucketSize time.Duration) (map[core.HistoricalKey][]core.TimestampedAggregationValue, error) {
    94  	return src.GetLabeledAggregation(metricName, nil, aggregations, metricKeys, start, end, bucketSize)
    95  }
    96  
    97  func (src *fakeHistoricalSource) GetLabeledAggregation(metricName string, labels map[string]string, aggregations []core.AggregationType, metricKeys []core.HistoricalKey, start, end time.Time, bucketSize time.Duration) (map[core.HistoricalKey][]core.TimestampedAggregationValue, error) {
    98  	if metricName == "invalid" {
    99  		return nil, fmt.Errorf("fake error fetching metrics")
   100  	}
   101  
   102  	src.metricRequests = append(src.aggregationRequests, metricReq{
   103  		name:   metricName,
   104  		keys:   metricKeys,
   105  		start:  start,
   106  		end:    end,
   107  		labels: labels,
   108  
   109  		aggregations: aggregations,
   110  		bucketSize:   bucketSize,
   111  	})
   112  
   113  	res := make(map[core.HistoricalKey][]core.TimestampedAggregationValue, len(metricKeys))
   114  
   115  	for _, key := range metricKeys {
   116  		countVal := uint64(10)
   117  		res[key] = []core.TimestampedAggregationValue{
   118  			{
   119  				Timestamp:  src.nowTime.Add(-10 * time.Second),
   120  				BucketSize: 10 * time.Second,
   121  				AggregationValue: core.AggregationValue{
   122  					Count: &countVal,
   123  				},
   124  			},
   125  		}
   126  	}
   127  
   128  	return res, nil
   129  }
   130  
   131  func (src *fakeHistoricalSource) GetMetricNames(metricKey core.HistoricalKey) ([]string, error) {
   132  	if names, ok := src.metricNames[metricKey]; ok {
   133  		return names, nil
   134  	}
   135  
   136  	return nil, fmt.Errorf("no such object %q", metricKey.String())
   137  }
   138  
   139  func (src *fakeHistoricalSource) GetNodes() ([]string, error) {
   140  	return src.nodes, nil
   141  }
   142  
   143  func (src *fakeHistoricalSource) GetNamespaces() ([]string, error) {
   144  	return src.namespaces, nil
   145  }
   146  
   147  func (src *fakeHistoricalSource) GetPodsFromNamespace(namespace string) ([]string, error) {
   148  	if pods, ok := src.podsForNamespace[namespace]; ok {
   149  		return pods, nil
   150  	}
   151  
   152  	return nil, fmt.Errorf("no such namespace %q", namespace)
   153  }
   154  
   155  func (src *fakeHistoricalSource) GetSystemContainersFromNode(node string) ([]string, error) {
   156  	if conts, ok := src.containersForNode[node]; ok {
   157  		return conts, nil
   158  	}
   159  
   160  	return nil, fmt.Errorf("no such node %q", node)
   161  }
   162  
   163  func prepApi() (*HistoricalApi, *fakeHistoricalSource) {
   164  	histSrc := &fakeHistoricalSource{
   165  		metricNames: make(map[core.HistoricalKey][]string),
   166  	}
   167  
   168  	api := &HistoricalApi{
   169  		Api: &Api{
   170  			historicalSource: histSrc,
   171  		},
   172  	}
   173  
   174  	return api, histSrc
   175  }
   176  
   177  type fakeRespRecorder struct {
   178  	headers http.Header
   179  	status  int
   180  	data    *bytes.Buffer
   181  }
   182  
   183  func (r *fakeRespRecorder) Header() http.Header {
   184  	return r.headers
   185  }
   186  
   187  func (r *fakeRespRecorder) WriteHeader(status int) {
   188  	r.status = status
   189  }
   190  
   191  func (r *fakeRespRecorder) Write(content []byte) (int, error) {
   192  	return r.data.Write(content)
   193  }
   194  
   195  func TestAvailableMetrics(t *testing.T) {
   196  	api, src := prepApi()
   197  
   198  	src.metricNames = map[core.HistoricalKey][]string{
   199  		{
   200  			ObjectType: core.MetricSetTypeCluster,
   201  		}: {"cm1", "cm2"},
   202  
   203  		{
   204  			ObjectType: core.MetricSetTypeNode,
   205  			NodeName:   "somenode1",
   206  		}: {"nm1", "nm2"},
   207  
   208  		{
   209  			ObjectType:    core.MetricSetTypeNamespace,
   210  			NamespaceName: "somens1",
   211  		}: {"nsm1", "nsm2"},
   212  
   213  		{
   214  			ObjectType:    core.MetricSetTypePod,
   215  			NamespaceName: "somens1",
   216  			PodName:       "somepod1",
   217  		}: {"pm1", "pm2"},
   218  
   219  		{
   220  			ObjectType:    core.MetricSetTypePodContainer,
   221  			NamespaceName: "somens1",
   222  			PodName:       "somepod1",
   223  			ContainerName: "somecont1",
   224  		}: {"pcm1", "pcm2"},
   225  
   226  		{
   227  			ObjectType:    core.MetricSetTypeSystemContainer,
   228  			NodeName:      "somenode1",
   229  			ContainerName: "somecont1",
   230  		}: {"ncm1", "ncm2"},
   231  	}
   232  
   233  	tests := []struct {
   234  		name          string
   235  		fun           func(request *restful.Request, response *restful.Response)
   236  		pathParams    map[string]string
   237  		expectedNames []string
   238  	}{
   239  		{
   240  			name:          "cluster metrics",
   241  			fun:           api.availableClusterMetrics,
   242  			pathParams:    map[string]string{},
   243  			expectedNames: []string{"cm1", "cm2"},
   244  		},
   245  		{
   246  			name:          "node metrics",
   247  			fun:           api.availableNodeMetrics,
   248  			pathParams:    map[string]string{"node-name": "somenode1"},
   249  			expectedNames: []string{"nm1", "nm2"},
   250  		},
   251  		{
   252  			name:          "namespace metrics",
   253  			fun:           api.availableNamespaceMetrics,
   254  			pathParams:    map[string]string{"namespace-name": "somens1"},
   255  			expectedNames: []string{"nsm1", "nsm2"},
   256  		},
   257  		{
   258  			name: "pod metrics",
   259  			fun:  api.availablePodMetrics,
   260  			pathParams: map[string]string{
   261  				"namespace-name": "somens1",
   262  				"pod-name":       "somepod1",
   263  			},
   264  			expectedNames: []string{"pm1", "pm2"},
   265  		},
   266  		{
   267  			name: "pod container metrics",
   268  			fun:  api.availablePodContainerMetrics,
   269  			pathParams: map[string]string{
   270  				"namespace-name": "somens1",
   271  				"pod-name":       "somepod1",
   272  				"container-name": "somecont1",
   273  			},
   274  			expectedNames: []string{"pcm1", "pcm2"},
   275  		},
   276  		{
   277  			name: "free container metrics",
   278  			fun:  api.availableFreeContainerMetrics,
   279  			pathParams: map[string]string{
   280  				"node-name":      "somenode1",
   281  				"container-name": "somecont1",
   282  			},
   283  			expectedNames: []string{"ncm1", "ncm2"},
   284  		},
   285  	}
   286  
   287  	assert := assert.New(t)
   288  	restful.DefaultResponseMimeType = restful.MIME_JSON
   289  
   290  	for _, test := range tests {
   291  		req := restful.NewRequest(&http.Request{})
   292  		pathParams := req.PathParameters()
   293  		for k, v := range test.pathParams {
   294  			pathParams[k] = v
   295  		}
   296  		recorder := &fakeRespRecorder{
   297  			data:    new(bytes.Buffer),
   298  			headers: make(http.Header),
   299  		}
   300  		resp := restful.NewResponse(recorder)
   301  
   302  		test.fun(req, resp)
   303  
   304  		actualNames := []string{}
   305  		if err := json.Unmarshal(recorder.data.Bytes(), &actualNames); err != nil {
   306  			t.Fatalf("Unexpected error: %v", err)
   307  		}
   308  
   309  		assert.Equal(http.StatusOK, recorder.status, "status should have been OK (200)")
   310  		assert.Equal(test.expectedNames, actualNames, "should have gotten expected JSON")
   311  	}
   312  
   313  	for _, test := range tests {
   314  		if len(test.pathParams) == 0 {
   315  			// don't test tests with no parameters for invalid parameters
   316  			continue
   317  		}
   318  		req := restful.NewRequest(&http.Request{})
   319  		pathParams := req.PathParameters()
   320  		for k := range test.pathParams {
   321  			pathParams[k] = "some-other-value"
   322  		}
   323  		recorder := &fakeRespRecorder{
   324  			data:    new(bytes.Buffer),
   325  			headers: make(http.Header),
   326  		}
   327  		resp := restful.NewResponse(recorder)
   328  
   329  		test.fun(req, resp)
   330  
   331  		assert.Equal(http.StatusInternalServerError, recorder.status, "status should have been InternalServerError (500)")
   332  	}
   333  }
   334  
   335  func TestListObjects(t *testing.T) {
   336  	api, src := prepApi()
   337  
   338  	src.nodes = []string{"node1", "node2"}
   339  	src.namespaces = []string{"ns1", "ns2"}
   340  	src.podsForNamespace = map[string][]string{
   341  		"ns1": {"pod1", "pod2"},
   342  	}
   343  	src.containersForNode = map[string][]string{
   344  		"node1": {"x/y/z", "a/b/c"},
   345  	}
   346  
   347  	tests := []struct {
   348  		name          string
   349  		fun           func(request *restful.Request, response *restful.Response)
   350  		pathParams    map[string]string
   351  		expectedNames []string
   352  	}{
   353  		{
   354  			name:          "nodes",
   355  			fun:           api.nodeList,
   356  			expectedNames: []string{"node1", "node2"},
   357  		},
   358  		{
   359  			name:          "namespaces",
   360  			fun:           api.namespaceList,
   361  			expectedNames: []string{"ns1", "ns2"},
   362  		},
   363  		{
   364  			name:          "pods in namespace",
   365  			fun:           api.namespacePodList,
   366  			pathParams:    map[string]string{"namespace-name": "ns1"},
   367  			expectedNames: []string{"pod1", "pod2"},
   368  		},
   369  		{
   370  			name: "free containers on node",
   371  			fun:  api.nodeSystemContainerList,
   372  			pathParams: map[string]string{
   373  				"node-name": "node1",
   374  			},
   375  			expectedNames: []string{"x/y/z", "a/b/c"},
   376  		},
   377  	}
   378  
   379  	assert := assert.New(t)
   380  	restful.DefaultResponseMimeType = restful.MIME_JSON
   381  
   382  	for _, test := range tests {
   383  		req := restful.NewRequest(&http.Request{})
   384  		pathParams := req.PathParameters()
   385  		for k, v := range test.pathParams {
   386  			pathParams[k] = v
   387  		}
   388  		recorder := &fakeRespRecorder{
   389  			data:    new(bytes.Buffer),
   390  			headers: make(http.Header),
   391  		}
   392  		resp := restful.NewResponse(recorder)
   393  
   394  		test.fun(req, resp)
   395  
   396  		actualNames := []string{}
   397  		if err := json.Unmarshal(recorder.data.Bytes(), &actualNames); err != nil {
   398  			t.Fatalf("Unexpected error: %v", err)
   399  		}
   400  
   401  		assert.Equal(http.StatusOK, recorder.status, "status should have been OK (200)")
   402  		assert.Equal(test.expectedNames, actualNames, "should have gotten expected JSON")
   403  	}
   404  
   405  	for _, test := range tests {
   406  		if len(test.pathParams) == 0 {
   407  			// don't test tests with no parameters for invalid parameters
   408  			continue
   409  		}
   410  		req := restful.NewRequest(&http.Request{})
   411  		pathParams := req.PathParameters()
   412  		for k := range test.pathParams {
   413  			pathParams[k] = "some-other-value"
   414  		}
   415  		recorder := &fakeRespRecorder{
   416  			data:    new(bytes.Buffer),
   417  			headers: make(http.Header),
   418  		}
   419  		resp := restful.NewResponse(recorder)
   420  
   421  		test.fun(req, resp)
   422  
   423  		assert.Equal(http.StatusInternalServerError, recorder.status, "status should have been InternalServerError (500)")
   424  	}
   425  }
   426  
   427  func TestFetchMetrics(t *testing.T) {
   428  	api, src := prepApi()
   429  	nowTime := time.Now().UTC().Truncate(time.Second)
   430  	src.nowTime = nowTime
   431  	nowFunc = func() time.Time { return nowTime }
   432  
   433  	tests := []struct {
   434  		test              string
   435  		start             string
   436  		end               string
   437  		labels            string
   438  		fun               func(*restful.Request, *restful.Response)
   439  		pathParams        map[string]string
   440  		expectedMetricReq metricReq
   441  		expectedStatus    int
   442  	}{
   443  		{
   444  			test: "cluster metrics",
   445  			fun:  api.clusterMetrics,
   446  			pathParams: map[string]string{
   447  				"metric-name": "some-metric",
   448  			},
   449  			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
   450  			expectedMetricReq: metricReq{
   451  				name:  "some-metric",
   452  				start: nowTime.Add(-10 * time.Second),
   453  				keys: []core.HistoricalKey{
   454  					{ObjectType: core.MetricSetTypeCluster},
   455  				},
   456  			},
   457  		},
   458  		{
   459  			test: "node metrics",
   460  			fun:  api.nodeMetrics,
   461  			pathParams: map[string]string{
   462  				"metric-name": "some-metric",
   463  				"node-name":   "node1",
   464  			},
   465  			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
   466  			expectedMetricReq: metricReq{
   467  				name:  "some-metric",
   468  				start: nowTime.Add(-10 * time.Second),
   469  				keys: []core.HistoricalKey{
   470  					{ObjectType: core.MetricSetTypeNode, NodeName: "node1"},
   471  				},
   472  			},
   473  		},
   474  		{
   475  			test: "namespace metrics",
   476  			fun:  api.namespaceMetrics,
   477  			pathParams: map[string]string{
   478  				"metric-name":    "some-metric",
   479  				"namespace-name": "ns1",
   480  			},
   481  			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
   482  			expectedMetricReq: metricReq{
   483  				name:  "some-metric",
   484  				start: nowTime.Add(-10 * time.Second),
   485  				keys: []core.HistoricalKey{
   486  					{ObjectType: core.MetricSetTypeNamespace, NamespaceName: "ns1"},
   487  				},
   488  			},
   489  		},
   490  		{
   491  			test: "pod name metrics",
   492  			fun:  api.podMetrics,
   493  			pathParams: map[string]string{
   494  				"metric-name":    "some-metric",
   495  				"namespace-name": "ns1",
   496  				"pod-name":       "pod1",
   497  			},
   498  			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
   499  			expectedMetricReq: metricReq{
   500  				name:  "some-metric",
   501  				start: nowTime.Add(-10 * time.Second),
   502  				keys: []core.HistoricalKey{
   503  					{ObjectType: core.MetricSetTypePod, NamespaceName: "ns1", PodName: "pod1"},
   504  				},
   505  			},
   506  		},
   507  		{
   508  			test: "pod id metrics",
   509  			fun:  api.podMetrics,
   510  			pathParams: map[string]string{
   511  				"metric-name": "some-metric",
   512  				"pod-id":      "pod-1-id",
   513  			},
   514  			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
   515  			expectedMetricReq: metricReq{
   516  				name:  "some-metric",
   517  				start: nowTime.Add(-10 * time.Second),
   518  				keys: []core.HistoricalKey{
   519  					{ObjectType: core.MetricSetTypePod, PodId: "pod-1-id"},
   520  				},
   521  			},
   522  		},
   523  		{
   524  			test: "pod name container metrics",
   525  			fun:  api.podContainerMetrics,
   526  			pathParams: map[string]string{
   527  				"metric-name":    "some-metric",
   528  				"namespace-name": "ns1",
   529  				"pod-name":       "pod1",
   530  				"container-name": "cont1",
   531  			},
   532  			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
   533  			expectedMetricReq: metricReq{
   534  				name:  "some-metric",
   535  				start: nowTime.Add(-10 * time.Second),
   536  				keys: []core.HistoricalKey{
   537  					{ObjectType: core.MetricSetTypePodContainer, NamespaceName: "ns1", PodName: "pod1", ContainerName: "cont1"},
   538  				},
   539  			},
   540  		},
   541  		{
   542  			test: "pod id container metrics",
   543  			fun:  api.podContainerMetrics,
   544  			pathParams: map[string]string{
   545  				"metric-name":    "some-metric",
   546  				"pod-id":         "pod-1-id",
   547  				"container-name": "cont1",
   548  			},
   549  			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
   550  			expectedMetricReq: metricReq{
   551  				name:  "some-metric",
   552  				start: nowTime.Add(-10 * time.Second),
   553  				keys: []core.HistoricalKey{
   554  					{ObjectType: core.MetricSetTypePodContainer, PodId: "pod-1-id", ContainerName: "cont1"},
   555  				},
   556  			},
   557  		},
   558  		{
   559  			test: "system container metrics",
   560  			fun:  api.freeContainerMetrics,
   561  			pathParams: map[string]string{
   562  				"metric-name":    "some-metric",
   563  				"node-name":      "node1",
   564  				"container-name": "cont1",
   565  			},
   566  			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
   567  			expectedMetricReq: metricReq{
   568  				name:  "some-metric",
   569  				start: nowTime.Add(-10 * time.Second),
   570  				keys: []core.HistoricalKey{
   571  					{ObjectType: core.MetricSetTypeSystemContainer, NodeName: "node1", ContainerName: "cont1"},
   572  				},
   573  			},
   574  		},
   575  		{
   576  			test: "query with end",
   577  			fun:  api.clusterMetrics,
   578  			pathParams: map[string]string{
   579  				"metric-name": "some-metric",
   580  			},
   581  			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
   582  			end:   nowTime.Format(time.RFC3339),
   583  			expectedMetricReq: metricReq{
   584  				name: "some-metric",
   585  				keys: []core.HistoricalKey{
   586  					{ObjectType: core.MetricSetTypeCluster},
   587  				},
   588  				start: nowTime.Add(-10 * time.Second),
   589  				end:   nowTime,
   590  			},
   591  		},
   592  		{
   593  			test: "query with labels",
   594  			fun:  api.clusterMetrics,
   595  			pathParams: map[string]string{
   596  				"metric-name": "some-metric",
   597  			},
   598  			start:  nowTime.Add(-10 * time.Second).Format(time.RFC3339),
   599  			labels: "somelbl:v1,otherlbl:v2.3:4",
   600  			expectedMetricReq: metricReq{
   601  				name: "some-metric",
   602  				keys: []core.HistoricalKey{
   603  					{ObjectType: core.MetricSetTypeCluster},
   604  				},
   605  				labels: map[string]string{"somelbl": "v1", "otherlbl": "v2.3:4"},
   606  				start:  nowTime.Add(-10 * time.Second),
   607  			},
   608  		},
   609  		{
   610  			test:           "query with bad start",
   611  			fun:            api.clusterMetrics,
   612  			start:          "afdsfd",
   613  			expectedStatus: http.StatusBadRequest,
   614  		},
   615  		{
   616  			test:           "query with no start",
   617  			fun:            api.clusterMetrics,
   618  			expectedStatus: http.StatusBadRequest,
   619  		},
   620  		{
   621  			test:  "query with error while fetching metrics",
   622  			fun:   api.clusterMetrics,
   623  			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
   624  			pathParams: map[string]string{
   625  				"metric-name": "invalid",
   626  			},
   627  			expectedStatus: http.StatusInternalServerError,
   628  		},
   629  		{
   630  			test:           "query with bad labels",
   631  			fun:            api.clusterMetrics,
   632  			start:          nowTime.Add(-10 * time.Second).Format(time.RFC3339),
   633  			labels:         "abc:",
   634  			expectedStatus: http.StatusBadRequest,
   635  		},
   636  	}
   637  
   638  	assert := assert.New(t)
   639  	restful.DefaultResponseMimeType = restful.MIME_JSON
   640  
   641  	// doesn't particularly correspond to the query -- we're just using it to
   642  	// test conversion between internal types
   643  	expectedNormalVals := types.MetricResult{
   644  		LatestTimestamp: nowTime.Add(-10 * time.Second),
   645  		Metrics: []types.MetricPoint{
   646  			{
   647  				Timestamp: nowTime.Add(-10 * time.Second),
   648  				Value:     33,
   649  			},
   650  		},
   651  	}
   652  
   653  	for _, test := range tests {
   654  		queryParams := make(url.Values)
   655  		queryParams.Add("start", test.start)
   656  		queryParams.Add("end", test.end)
   657  		queryParams.Add("labels", test.labels)
   658  		u := &url.URL{RawQuery: queryParams.Encode()}
   659  		req := restful.NewRequest(&http.Request{URL: u})
   660  		pathParams := req.PathParameters()
   661  		for k, v := range test.pathParams {
   662  			pathParams[k] = v
   663  		}
   664  		recorder := &fakeRespRecorder{
   665  			data:    new(bytes.Buffer),
   666  			headers: make(http.Header),
   667  		}
   668  		resp := restful.NewResponse(recorder)
   669  
   670  		test.fun(req, resp)
   671  
   672  		if test.expectedStatus != 0 {
   673  			assert.Equal(test.expectedStatus, recorder.status, "for test %q: status should have been an error status", test.test)
   674  		} else {
   675  			if !assert.Equal(http.StatusOK, recorder.status, "for test %q: status should have been OK (200)", test.test) {
   676  				continue
   677  			}
   678  
   679  			actualReq := src.metricRequests[len(src.metricRequests)-1]
   680  			if test.expectedMetricReq.end.IsZero() {
   681  				test.expectedMetricReq.end = nowTime
   682  			}
   683  			assert.Equal(test.expectedMetricReq, actualReq, "for test %q: expected a different metric request to have been placed", test.test)
   684  
   685  			actualVals := types.MetricResult{}
   686  			if err := json.Unmarshal(recorder.data.Bytes(), &actualVals); err != nil {
   687  				t.Fatalf("Unexpected error: %v", err)
   688  			}
   689  
   690  			assert.Equal(expectedNormalVals, actualVals, "for test %q: should have gotten expected JSON", test.test)
   691  		}
   692  	}
   693  
   694  	listTests := []struct {
   695  		test              string
   696  		start             string
   697  		end               string
   698  		labels            string
   699  		fun               func(*restful.Request, *restful.Response)
   700  		pathParams        map[string]string
   701  		expectedMetricReq metricReq
   702  		expectedStatus    int
   703  	}{
   704  		{
   705  			test:  "pod list by ids",
   706  			fun:   api.podListMetrics,
   707  			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
   708  			pathParams: map[string]string{
   709  				"metric-name": "some-metric",
   710  				"pod-id-list": "pod-id-1,pod-id-2,pod-id-3",
   711  			},
   712  			expectedMetricReq: metricReq{
   713  				name: "some-metric",
   714  				keys: []core.HistoricalKey{
   715  					{ObjectType: core.MetricSetTypePod, PodId: "pod-id-1"},
   716  					{ObjectType: core.MetricSetTypePod, PodId: "pod-id-2"},
   717  					{ObjectType: core.MetricSetTypePod, PodId: "pod-id-3"},
   718  				},
   719  				start: nowTime.Add(-10 * time.Second),
   720  			},
   721  		},
   722  		{
   723  			test:  "pod list by names",
   724  			fun:   api.podListMetrics,
   725  			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
   726  			pathParams: map[string]string{
   727  				"metric-name":    "some-metric",
   728  				"namespace-name": "ns1",
   729  				"pod-list":       "pod1,pod2,pod3",
   730  			},
   731  			expectedMetricReq: metricReq{
   732  				name: "some-metric",
   733  				keys: []core.HistoricalKey{
   734  					{ObjectType: core.MetricSetTypePod, NamespaceName: "ns1", PodName: "pod1"},
   735  					{ObjectType: core.MetricSetTypePod, NamespaceName: "ns1", PodName: "pod2"},
   736  					{ObjectType: core.MetricSetTypePod, NamespaceName: "ns1", PodName: "pod3"},
   737  				},
   738  				start: nowTime.Add(-10 * time.Second),
   739  			},
   740  		},
   741  		{
   742  			test: "pod list with labels",
   743  			fun:  api.podListMetrics,
   744  			pathParams: map[string]string{
   745  				"metric-name": "some-metric",
   746  				"pod-id-list": "pod-id-1,pod-id-2,pod-id-3",
   747  			},
   748  			start:  nowTime.Add(-10 * time.Second).Format(time.RFC3339),
   749  			labels: "somelbl:v1,otherlbl:v2.3:4",
   750  			expectedMetricReq: metricReq{
   751  				name: "some-metric",
   752  				keys: []core.HistoricalKey{
   753  					{ObjectType: core.MetricSetTypePod, PodId: "pod-id-1"},
   754  					{ObjectType: core.MetricSetTypePod, PodId: "pod-id-2"},
   755  					{ObjectType: core.MetricSetTypePod, PodId: "pod-id-3"},
   756  				},
   757  				labels: map[string]string{"somelbl": "v1", "otherlbl": "v2.3:4"},
   758  				start:  nowTime.Add(-10 * time.Second),
   759  			},
   760  		},
   761  		{
   762  			test:           "pod list with bad start time",
   763  			fun:            api.podListMetrics,
   764  			start:          "afdsfd",
   765  			expectedStatus: http.StatusBadRequest,
   766  		},
   767  		{
   768  			test:  "pod list with error fetching metrics",
   769  			fun:   api.podListMetrics,
   770  			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
   771  			pathParams: map[string]string{
   772  				"metric-name": "invalid",
   773  			},
   774  			expectedStatus: http.StatusInternalServerError,
   775  		},
   776  		{
   777  			test:           "pod list with no start",
   778  			fun:            api.podListMetrics,
   779  			expectedStatus: http.StatusBadRequest,
   780  		},
   781  		{
   782  			test:           "query with bad labels",
   783  			fun:            api.clusterMetrics,
   784  			start:          nowTime.Add(-10 * time.Second).Format(time.RFC3339),
   785  			labels:         "abc:",
   786  			expectedStatus: http.StatusBadRequest,
   787  		},
   788  	}
   789  
   790  	expectedListVals := types.MetricResultList{
   791  		Items: []types.MetricResult{
   792  			expectedNormalVals,
   793  			expectedNormalVals,
   794  			expectedNormalVals,
   795  		},
   796  	}
   797  
   798  	for _, test := range listTests {
   799  		queryParams := make(url.Values)
   800  		queryParams.Add("start", test.start)
   801  		queryParams.Add("end", test.end)
   802  		queryParams.Add("labels", test.labels)
   803  		u := &url.URL{RawQuery: queryParams.Encode()}
   804  		req := restful.NewRequest(&http.Request{URL: u})
   805  		pathParams := req.PathParameters()
   806  		for k, v := range test.pathParams {
   807  			pathParams[k] = v
   808  		}
   809  		recorder := &fakeRespRecorder{
   810  			data:    new(bytes.Buffer),
   811  			headers: make(http.Header),
   812  		}
   813  		resp := restful.NewResponse(recorder)
   814  
   815  		test.fun(req, resp)
   816  
   817  		if test.expectedStatus != 0 {
   818  			assert.Equal(test.expectedStatus, recorder.status, "for test %q: status should have been an error status", test.test)
   819  		} else {
   820  			if !assert.Equal(http.StatusOK, recorder.status, "for test %q: status should have been OK (200)", test.test) {
   821  				continue
   822  			}
   823  
   824  			actualReq := src.metricRequests[len(src.metricRequests)-1]
   825  			if test.expectedMetricReq.end.IsZero() {
   826  				test.expectedMetricReq.end = nowTime
   827  			}
   828  
   829  			assert.Equal(test.expectedMetricReq, actualReq, "for test %q: expected a different metric request to have been placed", test.test)
   830  
   831  			actualVals := types.MetricResultList{}
   832  			if err := json.Unmarshal(recorder.data.Bytes(), &actualVals); err != nil {
   833  				t.Fatalf("Unexpected error: %v", err)
   834  			}
   835  
   836  			assert.Equal(expectedListVals, actualVals, "for test %q: should have gotten expected JSON", test.test)
   837  		}
   838  	}
   839  }
   840  
   841  func TestFetchAggregations(t *testing.T) {
   842  	api, src := prepApi()
   843  	nowTime := time.Now().UTC().Truncate(time.Second)
   844  	src.nowTime = nowTime
   845  	nowFunc = func() time.Time { return nowTime }
   846  
   847  	tests := []struct {
   848  		test              string
   849  		bucketSize        string
   850  		start             string
   851  		end               string
   852  		labels            string
   853  		fun               func(*restful.Request, *restful.Response)
   854  		pathParams        map[string]string
   855  		expectedMetricReq metricReq
   856  		expectedStatus    int
   857  	}{
   858  		{
   859  			test:  "cluster aggregations",
   860  			fun:   api.clusterAggregations,
   861  			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
   862  			pathParams: map[string]string{
   863  				"metric-name":  "some-metric",
   864  				"aggregations": "count,average",
   865  			},
   866  			expectedMetricReq: metricReq{
   867  				name:  "some-metric",
   868  				start: nowTime.Add(-10 * time.Second),
   869  				keys: []core.HistoricalKey{
   870  					{ObjectType: core.MetricSetTypeCluster},
   871  				},
   872  			},
   873  		},
   874  		{
   875  			test:  "node aggregations",
   876  			fun:   api.nodeAggregations,
   877  			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
   878  			pathParams: map[string]string{
   879  				"metric-name":  "some-metric",
   880  				"node-name":    "node1",
   881  				"aggregations": "count,average",
   882  			},
   883  			expectedMetricReq: metricReq{
   884  				name:  "some-metric",
   885  				start: nowTime.Add(-10 * time.Second),
   886  				keys: []core.HistoricalKey{
   887  					{ObjectType: core.MetricSetTypeNode, NodeName: "node1"},
   888  				},
   889  			},
   890  		},
   891  		{
   892  			test:  "namespace aggregations",
   893  			fun:   api.namespaceAggregations,
   894  			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
   895  			pathParams: map[string]string{
   896  				"metric-name":    "some-metric",
   897  				"namespace-name": "ns1",
   898  				"aggregations":   "count,average",
   899  			},
   900  			expectedMetricReq: metricReq{
   901  				name:  "some-metric",
   902  				start: nowTime.Add(-10 * time.Second),
   903  				keys: []core.HistoricalKey{
   904  					{ObjectType: core.MetricSetTypeNamespace, NamespaceName: "ns1"},
   905  				},
   906  			},
   907  		},
   908  		{
   909  			test:  "pod name aggregations",
   910  			fun:   api.podAggregations,
   911  			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
   912  			pathParams: map[string]string{
   913  				"metric-name":    "some-metric",
   914  				"namespace-name": "ns1",
   915  				"pod-name":       "pod1",
   916  				"aggregations":   "count,average",
   917  			},
   918  			expectedMetricReq: metricReq{
   919  				name:  "some-metric",
   920  				start: nowTime.Add(-10 * time.Second),
   921  				keys: []core.HistoricalKey{
   922  					{ObjectType: core.MetricSetTypePod, NamespaceName: "ns1", PodName: "pod1"},
   923  				},
   924  			},
   925  		},
   926  		{
   927  			test:  "pod id aggregations",
   928  			fun:   api.podAggregations,
   929  			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
   930  			pathParams: map[string]string{
   931  				"metric-name":  "some-metric",
   932  				"pod-id":       "pod-1-id",
   933  				"aggregations": "count,average",
   934  			},
   935  			expectedMetricReq: metricReq{
   936  				name:  "some-metric",
   937  				start: nowTime.Add(-10 * time.Second),
   938  				keys: []core.HistoricalKey{
   939  					{ObjectType: core.MetricSetTypePod, PodId: "pod-1-id"},
   940  				},
   941  			},
   942  		},
   943  		{
   944  			test:  "pod name container aggregations",
   945  			fun:   api.podContainerAggregations,
   946  			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
   947  			pathParams: map[string]string{
   948  				"metric-name":    "some-metric",
   949  				"namespace-name": "ns1",
   950  				"pod-name":       "pod1",
   951  				"container-name": "cont1",
   952  				"aggregations":   "count,average",
   953  			},
   954  			expectedMetricReq: metricReq{
   955  				name:  "some-metric",
   956  				start: nowTime.Add(-10 * time.Second),
   957  				keys: []core.HistoricalKey{
   958  					{ObjectType: core.MetricSetTypePodContainer, NamespaceName: "ns1", PodName: "pod1", ContainerName: "cont1"},
   959  				},
   960  			},
   961  		},
   962  		{
   963  			test:  "pod id container aggregations",
   964  			fun:   api.podContainerAggregations,
   965  			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
   966  			pathParams: map[string]string{
   967  				"metric-name":    "some-metric",
   968  				"pod-id":         "pod-1-id",
   969  				"container-name": "cont1",
   970  				"aggregations":   "count,average",
   971  			},
   972  			expectedMetricReq: metricReq{
   973  				name:  "some-metric",
   974  				start: nowTime.Add(-10 * time.Second),
   975  				keys: []core.HistoricalKey{
   976  					{ObjectType: core.MetricSetTypePodContainer, PodId: "pod-1-id", ContainerName: "cont1"},
   977  				},
   978  			},
   979  		},
   980  		{
   981  			test:  "system container aggregations",
   982  			fun:   api.freeContainerAggregations,
   983  			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
   984  			pathParams: map[string]string{
   985  				"metric-name":    "some-metric",
   986  				"node-name":      "node1",
   987  				"container-name": "cont1",
   988  				"aggregations":   "count,average",
   989  			},
   990  			expectedMetricReq: metricReq{
   991  				name:  "some-metric",
   992  				start: nowTime.Add(-10 * time.Second),
   993  				keys: []core.HistoricalKey{
   994  					{ObjectType: core.MetricSetTypeSystemContainer, NodeName: "node1", ContainerName: "cont1"},
   995  				},
   996  			},
   997  		},
   998  		{
   999  			test: "aggregations with end and bucket",
  1000  			fun:  api.clusterAggregations,
  1001  			pathParams: map[string]string{
  1002  				"metric-name":  "some-metric",
  1003  				"aggregations": "count,average",
  1004  			},
  1005  			start:      nowTime.Add(-10 * time.Second).Format(time.RFC3339),
  1006  			end:        nowTime.Format(time.RFC3339),
  1007  			bucketSize: "20s",
  1008  			expectedMetricReq: metricReq{
  1009  				name: "some-metric",
  1010  				keys: []core.HistoricalKey{
  1011  					{ObjectType: core.MetricSetTypeCluster},
  1012  				},
  1013  				start:      nowTime.Add(-10 * time.Second),
  1014  				end:        nowTime,
  1015  				bucketSize: 20 * time.Second,
  1016  			},
  1017  		},
  1018  		{
  1019  			test: "aggregations with labels",
  1020  			fun:  api.clusterAggregations,
  1021  			pathParams: map[string]string{
  1022  				"metric-name":  "some-metric",
  1023  				"aggregations": "count,average",
  1024  			},
  1025  			labels: "somelbl:v1,otherlbl:v2.3:4",
  1026  			start:  nowTime.Add(-10 * time.Second).Format(time.RFC3339),
  1027  			expectedMetricReq: metricReq{
  1028  				name: "some-metric",
  1029  				keys: []core.HistoricalKey{
  1030  					{ObjectType: core.MetricSetTypeCluster},
  1031  				},
  1032  				start:  nowTime.Add(-10 * time.Second),
  1033  				labels: map[string]string{"somelbl": "v1", "otherlbl": "v2.3:4"},
  1034  			},
  1035  		},
  1036  		{
  1037  			test:           "aggregations with bad start time",
  1038  			fun:            api.clusterAggregations,
  1039  			start:          "afdsfd",
  1040  			expectedStatus: http.StatusBadRequest,
  1041  		},
  1042  		{
  1043  			test:  "aggregations with fetch error",
  1044  			fun:   api.clusterAggregations,
  1045  			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
  1046  			pathParams: map[string]string{
  1047  				"metric-name":  "invalid",
  1048  				"aggregations": "count,average",
  1049  			},
  1050  			expectedStatus: http.StatusInternalServerError,
  1051  		},
  1052  		{
  1053  			test:           "aggregations with no start time",
  1054  			fun:            api.clusterAggregations,
  1055  			expectedStatus: http.StatusBadRequest,
  1056  		},
  1057  	}
  1058  
  1059  	assert := assert.New(t)
  1060  	restful.DefaultResponseMimeType = restful.MIME_JSON
  1061  
  1062  	// doesn't particularly correspond to the query -- we're just using it to
  1063  	// test conversion between internal types
  1064  	countVal := uint64(10)
  1065  	expectedNormalVals := types.MetricAggregationResult{
  1066  		BucketSize: 10 * time.Second,
  1067  		Buckets: []types.MetricAggregationBucket{
  1068  			{
  1069  				Timestamp: src.nowTime.Add(-10 * time.Second),
  1070  				Count:     &countVal,
  1071  			},
  1072  		},
  1073  	}
  1074  
  1075  	aggList := []core.AggregationType{
  1076  		core.AggregationTypeCount,
  1077  		core.AggregationTypeAverage,
  1078  	}
  1079  
  1080  	for _, test := range tests {
  1081  		queryParams := make(url.Values)
  1082  		queryParams.Add("start", test.start)
  1083  		queryParams.Add("end", test.end)
  1084  		queryParams.Add("bucket", test.bucketSize)
  1085  		queryParams.Add("labels", test.labels)
  1086  		u := &url.URL{RawQuery: queryParams.Encode()}
  1087  		req := restful.NewRequest(&http.Request{URL: u})
  1088  		pathParams := req.PathParameters()
  1089  		for k, v := range test.pathParams {
  1090  			pathParams[k] = v
  1091  		}
  1092  		recorder := &fakeRespRecorder{
  1093  			data:    new(bytes.Buffer),
  1094  			headers: make(http.Header),
  1095  		}
  1096  		resp := restful.NewResponse(recorder)
  1097  
  1098  		test.fun(req, resp)
  1099  
  1100  		if test.expectedStatus != 0 {
  1101  			assert.Equal(test.expectedStatus, recorder.status, "for test %q: status should have been an error status", test.test)
  1102  		} else {
  1103  			if !assert.Equal(http.StatusOK, recorder.status, "for test %q: status should have been OK (200)", test.test) {
  1104  				continue
  1105  			}
  1106  
  1107  			actualReq := src.metricRequests[len(src.metricRequests)-1]
  1108  			if test.expectedMetricReq.end.IsZero() {
  1109  				test.expectedMetricReq.end = nowTime
  1110  			}
  1111  
  1112  			test.expectedMetricReq.aggregations = aggList
  1113  			assert.Equal(test.expectedMetricReq, actualReq, "for test %q: expected a different metric request to have been placed", test.test)
  1114  
  1115  			actualVals := types.MetricAggregationResult{}
  1116  			if err := json.Unmarshal(recorder.data.Bytes(), &actualVals); err != nil {
  1117  				t.Fatalf("Unexpected error: %v", err)
  1118  			}
  1119  
  1120  			assert.Equal(expectedNormalVals, actualVals, "for test %q: should have gotten expected JSON", test.test)
  1121  		}
  1122  	}
  1123  
  1124  	listTests := []struct {
  1125  		test              string
  1126  		start             string
  1127  		labels            string
  1128  		end               string
  1129  		fun               func(*restful.Request, *restful.Response)
  1130  		pathParams        map[string]string
  1131  		expectedMetricReq metricReq
  1132  		expectedStatus    int
  1133  	}{
  1134  		{
  1135  			test:  "pod id list aggregations",
  1136  			fun:   api.podListAggregations,
  1137  			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
  1138  			pathParams: map[string]string{
  1139  				"metric-name":  "some-metric",
  1140  				"pod-id-list":  "pod-id-1,pod-id-2,pod-id-3",
  1141  				"aggregations": "count,average",
  1142  			},
  1143  			expectedMetricReq: metricReq{
  1144  				name: "some-metric",
  1145  				keys: []core.HistoricalKey{
  1146  					{ObjectType: core.MetricSetTypePod, PodId: "pod-id-1"},
  1147  					{ObjectType: core.MetricSetTypePod, PodId: "pod-id-2"},
  1148  					{ObjectType: core.MetricSetTypePod, PodId: "pod-id-3"},
  1149  				},
  1150  				start: nowTime.Add(-10 * time.Second),
  1151  			},
  1152  		},
  1153  		{
  1154  			test:  "pod name list aggregations",
  1155  			fun:   api.podListAggregations,
  1156  			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
  1157  			pathParams: map[string]string{
  1158  				"metric-name":    "some-metric",
  1159  				"namespace-name": "ns1",
  1160  				"pod-list":       "pod1,pod2,pod3",
  1161  				"aggregations":   "count,average",
  1162  			},
  1163  			expectedMetricReq: metricReq{
  1164  				name: "some-metric",
  1165  				keys: []core.HistoricalKey{
  1166  					{ObjectType: core.MetricSetTypePod, NamespaceName: "ns1", PodName: "pod1"},
  1167  					{ObjectType: core.MetricSetTypePod, NamespaceName: "ns1", PodName: "pod2"},
  1168  					{ObjectType: core.MetricSetTypePod, NamespaceName: "ns1", PodName: "pod3"},
  1169  				},
  1170  				start: nowTime.Add(-10 * time.Second),
  1171  			},
  1172  		},
  1173  		{
  1174  			test:   "pod list aggregations with labels",
  1175  			fun:    api.podListAggregations,
  1176  			start:  nowTime.Add(-10 * time.Second).Format(time.RFC3339),
  1177  			labels: "somelbl:v1,otherlbl:v2.3:4",
  1178  			pathParams: map[string]string{
  1179  				"metric-name":    "some-metric",
  1180  				"namespace-name": "ns1",
  1181  				"pod-list":       "pod1,pod2,pod3",
  1182  				"aggregations":   "count,average",
  1183  			},
  1184  			expectedMetricReq: metricReq{
  1185  				name: "some-metric",
  1186  				keys: []core.HistoricalKey{
  1187  					{ObjectType: core.MetricSetTypePod, NamespaceName: "ns1", PodName: "pod1"},
  1188  					{ObjectType: core.MetricSetTypePod, NamespaceName: "ns1", PodName: "pod2"},
  1189  					{ObjectType: core.MetricSetTypePod, NamespaceName: "ns1", PodName: "pod3"},
  1190  				},
  1191  				start:  nowTime.Add(-10 * time.Second),
  1192  				labels: map[string]string{"somelbl": "v1", "otherlbl": "v2.3:4"},
  1193  			},
  1194  		},
  1195  		{
  1196  			test:           "pod list aggregations with bad start time",
  1197  			fun:            api.podListAggregations,
  1198  			start:          "afdsfd",
  1199  			expectedStatus: http.StatusBadRequest,
  1200  		},
  1201  		{
  1202  			test:  "pod list aggregations with fetch error",
  1203  			fun:   api.podListAggregations,
  1204  			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
  1205  			pathParams: map[string]string{
  1206  				"metric-name":  "invalid",
  1207  				"aggregations": "count,average",
  1208  			},
  1209  			expectedStatus: http.StatusInternalServerError,
  1210  		},
  1211  		{
  1212  			test:           "pod list aggregations with no start time",
  1213  			fun:            api.podListAggregations,
  1214  			expectedStatus: http.StatusBadRequest,
  1215  		},
  1216  	}
  1217  
  1218  	expectedListVals := types.MetricAggregationResultList{
  1219  		Items: []types.MetricAggregationResult{
  1220  			expectedNormalVals,
  1221  			expectedNormalVals,
  1222  			expectedNormalVals,
  1223  		},
  1224  	}
  1225  
  1226  	for _, test := range listTests {
  1227  		queryParams := make(url.Values)
  1228  		queryParams.Add("start", test.start)
  1229  		queryParams.Add("end", test.end)
  1230  		queryParams.Add("labels", test.labels)
  1231  		u := &url.URL{RawQuery: queryParams.Encode()}
  1232  		req := restful.NewRequest(&http.Request{URL: u})
  1233  		pathParams := req.PathParameters()
  1234  		for k, v := range test.pathParams {
  1235  			pathParams[k] = v
  1236  		}
  1237  		recorder := &fakeRespRecorder{
  1238  			data:    new(bytes.Buffer),
  1239  			headers: make(http.Header),
  1240  		}
  1241  		resp := restful.NewResponse(recorder)
  1242  
  1243  		test.fun(req, resp)
  1244  
  1245  		if test.expectedStatus != 0 {
  1246  			assert.Equal(test.expectedStatus, recorder.status, "for test %q: status should have been an error status", test.test)
  1247  		} else {
  1248  			if !assert.Equal(http.StatusOK, recorder.status, "for test %q: status should have been OK (200)", test.test) {
  1249  				continue
  1250  			}
  1251  
  1252  			actualReq := src.metricRequests[len(src.metricRequests)-1]
  1253  			if test.expectedMetricReq.end.IsZero() {
  1254  				test.expectedMetricReq.end = nowTime
  1255  			}
  1256  			test.expectedMetricReq.aggregations = aggList
  1257  			assert.Equal(test.expectedMetricReq, actualReq, "for test %q: expected a different metric request to have been placed", test.test)
  1258  
  1259  			actualVals := types.MetricAggregationResultList{}
  1260  			if err := json.Unmarshal(recorder.data.Bytes(), &actualVals); err != nil {
  1261  				t.Fatalf("Unexpected error: %v", err)
  1262  			}
  1263  
  1264  			assert.Equal(expectedListVals, actualVals, "for test %q: should have gotten expected JSON", test.test)
  1265  		}
  1266  	}
  1267  }
  1268  
  1269  func TestGetBucketSize(t *testing.T) {
  1270  	tests := []struct {
  1271  		sizeParam        string
  1272  		expectedDuration time.Duration
  1273  		expectedError    bool
  1274  	}{
  1275  		{
  1276  			// empty duration
  1277  			sizeParam:        "",
  1278  			expectedDuration: 0,
  1279  		},
  1280  		{
  1281  			// no units
  1282  			sizeParam:     "1",
  1283  			expectedError: true,
  1284  		},
  1285  		{
  1286  			// unknown unit
  1287  			sizeParam:     "1g",
  1288  			expectedError: true,
  1289  		},
  1290  		{
  1291  			// invalid unit (looks like ms)
  1292  			sizeParam:     "10gs",
  1293  			expectedError: true,
  1294  		},
  1295  		{
  1296  			// NaN
  1297  			sizeParam:     "abch",
  1298  			expectedError: true,
  1299  		},
  1300  		{
  1301  			sizeParam:        "5ms",
  1302  			expectedDuration: 5 * time.Millisecond,
  1303  		},
  1304  		{
  1305  			sizeParam:        "10s",
  1306  			expectedDuration: 10 * time.Second,
  1307  		},
  1308  		{
  1309  			sizeParam:        "15m",
  1310  			expectedDuration: 15 * time.Minute,
  1311  		},
  1312  		{
  1313  			sizeParam:        "20h",
  1314  			expectedDuration: 20 * time.Hour,
  1315  		},
  1316  		{
  1317  			sizeParam:        "25d",
  1318  			expectedDuration: 600 * time.Hour,
  1319  		},
  1320  	}
  1321  
  1322  	assert := assert.New(t)
  1323  	for _, test := range tests {
  1324  		u, err := url.Parse(fmt.Sprintf("/foo?bucket=%s", test.sizeParam))
  1325  		if err != nil {
  1326  			t.Fatalf("Unexpected error: %v", err)
  1327  		}
  1328  		req := restful.NewRequest(&http.Request{URL: u})
  1329  		res, err := getBucketSize(req)
  1330  		if test.expectedError {
  1331  			assert.Error(err, fmt.Sprintf("%q should have been an invalid value", test.sizeParam))
  1332  		} else if assert.NoError(err, fmt.Sprintf("%q should have been a valid value", test.sizeParam)) {
  1333  			assert.Equal(test.expectedDuration, res, fmt.Sprintf("%q should have given the correct duration", test.sizeParam))
  1334  		}
  1335  	}
  1336  }
  1337  
  1338  func TestGetAggregations(t *testing.T) {
  1339  	assert := assert.New(t)
  1340  
  1341  	validAggregations := "average,max"
  1342  	invalidAggregations := "max,non-existant"
  1343  
  1344  	req := restful.NewRequest(&http.Request{})
  1345  	pathParams := req.PathParameters()
  1346  	pathParams["aggregations"] = validAggregations
  1347  
  1348  	validRes, validErr := getAggregations(req)
  1349  	if assert.NoError(validErr, "expected valid list to not produce an error") {
  1350  		assert.Equal([]core.AggregationType{core.AggregationTypeAverage, core.AggregationTypeMaximum}, validRes, "expected valid list to be properly split and converted to AggregationType values")
  1351  	}
  1352  
  1353  	pathParams["aggregations"] = invalidAggregations
  1354  	_, invalidErr := getAggregations(req)
  1355  	assert.Error(invalidErr, "expected list with unknown aggregations to produce an error")
  1356  }
  1357  
  1358  func TestExportMetricValue(t *testing.T) {
  1359  	assert := assert.New(t)
  1360  
  1361  	assert.Nil(exportMetricValue(nil), "a nil input value should yield a nil output value")
  1362  
  1363  	intVal := &core.MetricValue{
  1364  		IntValue:  33,
  1365  		ValueType: core.ValueInt64,
  1366  	}
  1367  	outputIntVal := exportMetricValue(intVal)
  1368  	if assert.NotNil(outputIntVal, "a non-nil input should yield a non-nil output") && assert.NotNil(outputIntVal.IntValue, "an int-valued input should yield an output with the IntValue set") {
  1369  		assert.Equal(intVal.IntValue, *outputIntVal.IntValue, "the input int value should be the same as the output int value")
  1370  	}
  1371  
  1372  	floatVal := &core.MetricValue{
  1373  		FloatValue: 66.0,
  1374  		ValueType:  core.ValueFloat,
  1375  	}
  1376  	outputFloatVal := exportMetricValue(floatVal)
  1377  	if assert.NotNil(outputFloatVal, "a non-nil input should yield a non-nil output") && assert.NotNil(outputFloatVal.FloatValue, "an float-valued input should yield an output with the FloatValue set") {
  1378  		assert.Equal(float64(floatVal.FloatValue), *outputFloatVal.FloatValue, "the input float value should be the same as the output float value (as a float64)")
  1379  	}
  1380  }
  1381  
  1382  func TestExtractMetricValue(t *testing.T) {
  1383  	assert := assert.New(t)
  1384  
  1385  	aggregationVal := &core.AggregationValue{
  1386  		Aggregations: map[core.AggregationType]core.MetricValue{
  1387  			core.AggregationTypeAverage: {
  1388  				FloatValue: 66.0,
  1389  				ValueType:  core.ValueFloat,
  1390  			},
  1391  		},
  1392  	}
  1393  
  1394  	avgRes := extractMetricValue(aggregationVal, core.AggregationTypeAverage)
  1395  	if assert.NotNil(avgRes, "a present aggregation should yield a non-nil output") && assert.NotNil(avgRes.FloatValue, "the output float value should be set when the input aggregation value is of the float type") {
  1396  		assert.Equal(66.0, *avgRes.FloatValue, "the output float value should be the same as the aggregation's float value")
  1397  	}
  1398  
  1399  	maxRes := extractMetricValue(aggregationVal, core.AggregationTypeMaximum)
  1400  	assert.Nil(maxRes, "a non-present aggregation should yield a nil output")
  1401  }
  1402  
  1403  func TestExportTimestampedAggregationValue(t *testing.T) {
  1404  	assert := assert.New(t)
  1405  	require := require.New(t)
  1406  	nowTime := time.Now().UTC()
  1407  
  1408  	count10 := uint64(10)
  1409  	count11 := uint64(11)
  1410  	values := []core.TimestampedAggregationValue{
  1411  		{
  1412  			Timestamp:  nowTime.Add(-20 * time.Second),
  1413  			BucketSize: 10 * time.Second,
  1414  			AggregationValue: core.AggregationValue{
  1415  				Count: &count10,
  1416  				Aggregations: map[core.AggregationType]core.MetricValue{
  1417  					core.AggregationTypeAverage: {
  1418  						FloatValue: 66.0,
  1419  						ValueType:  core.ValueFloat,
  1420  					},
  1421  					core.AggregationTypePercentile50: {
  1422  						IntValue:  44,
  1423  						ValueType: core.ValueInt64,
  1424  					},
  1425  				},
  1426  			},
  1427  		},
  1428  		{
  1429  			Timestamp:  nowTime.Add(-10 * time.Second),
  1430  			BucketSize: 10 * time.Second,
  1431  			AggregationValue: core.AggregationValue{
  1432  				Count: &count11,
  1433  				Aggregations: map[core.AggregationType]core.MetricValue{
  1434  					core.AggregationTypeAverage: {
  1435  						FloatValue: 88.0,
  1436  						ValueType:  core.ValueFloat,
  1437  					},
  1438  					core.AggregationTypePercentile50: {
  1439  						IntValue:  55,
  1440  						ValueType: core.ValueInt64,
  1441  					},
  1442  				},
  1443  			},
  1444  		},
  1445  	}
  1446  
  1447  	res := exportTimestampedAggregationValue(values)
  1448  
  1449  	assert.Equal(10*time.Second, res.BucketSize, "the output bucket size should be 10s")
  1450  	require.Equal(len(values), len(res.Buckets), "there should be an output bucket for every input value")
  1451  
  1452  	for i, bucket := range res.Buckets {
  1453  		inputVal := values[i]
  1454  
  1455  		assert.Equal(inputVal.Count, bucket.Count, "the output bucket should have the same sample count as the input value")
  1456  
  1457  		if assert.NotNil(bucket.Average, "the output bucket should have the average set") {
  1458  			metricVal := inputVal.Aggregations[core.AggregationTypeAverage]
  1459  			assert.Equal(exportMetricValue(&metricVal), bucket.Average, "the output bucket should have an average value equal to that of the input value")
  1460  		}
  1461  
  1462  		percVal, ok := bucket.Percentiles["50"]
  1463  		if assert.True(ok, "the output bucket should have the 50th percentile set") {
  1464  			metricVal := inputVal.Aggregations[core.AggregationTypePercentile50]
  1465  			assert.Equal(exportMetricValue(&metricVal), &percVal, "the output bucket should have a 50th-percentile value equal to that of the input value")
  1466  		}
  1467  	}
  1468  }
  1469  
  1470  func TestGetLabels(t *testing.T) {
  1471  	assert := assert.New(t)
  1472  
  1473  	tests := []struct {
  1474  		test        string
  1475  		input       string
  1476  		outputVal   map[string]string
  1477  		outputError bool
  1478  	}{
  1479  		{
  1480  			test:      "working labels",
  1481  			input:     "k1:v1,k2:v2.3:4+5",
  1482  			outputVal: map[string]string{"k1": "v1", "k2": "v2.3:4+5"},
  1483  		},
  1484  		{
  1485  			test:        "bad label (no separator)",
  1486  			input:       "k1,k2:v2",
  1487  			outputError: true,
  1488  		},
  1489  		{
  1490  			test:        "bad label (no key)",
  1491  			input:       "k1:v1,:v2",
  1492  			outputError: true,
  1493  		},
  1494  		{
  1495  			test:        "bad label (no value)",
  1496  			input:       "k1:v1,k1:",
  1497  			outputError: true,
  1498  		},
  1499  		{
  1500  			test:      "empty",
  1501  			input:     "",
  1502  			outputVal: nil,
  1503  		},
  1504  	}
  1505  
  1506  	for _, test := range tests {
  1507  		queryParams := make(url.Values)
  1508  		queryParams.Add("labels", test.input)
  1509  		u := &url.URL{RawQuery: queryParams.Encode()}
  1510  		req := restful.NewRequest(&http.Request{URL: u})
  1511  		res, err := getLabels(req)
  1512  		if test.outputError && !assert.Error(err, "test %q should have yielded an error", test.test) {
  1513  			continue
  1514  		} else if !test.outputError && !assert.NoError(err, "test %q should not have yielded an error", test.test) {
  1515  			continue
  1516  		}
  1517  
  1518  		assert.Equal(test.outputVal, res, "test %q should have output the correct label map", test.test)
  1519  	}
  1520  }