Beispiel #1
0
func TestMsgPack(t *testing.T) {

	// register msg pack entity
	restful.RegisterEntityAccessor(MIME_MSGPACK, NewEntityAccessorMsgPack())
	type Tool struct {
		Name   string
		Vendor string
	}

	// Write
	httpWriter := httptest.NewRecorder()
	mpack := &Tool{Name: "json", Vendor: "apple"}
	resp := restful.NewResponse(httpWriter)
	resp.SetRequestAccepts("application/x-msgpack,*/*;q=0.8")

	err := resp.WriteEntity(mpack)
	if err != nil {
		t.Errorf("err %v", err)
	}

	// Read
	bodyReader := bytes.NewReader(httpWriter.Body.Bytes())
	httpRequest, _ := http.NewRequest("GET", "/test", bodyReader)
	httpRequest.Header.Set("Content-Type", MIME_MSGPACK)
	request := restful.NewRequest(httpRequest)
	readMsgPack := new(Tool)
	err = request.ReadEntity(&readMsgPack)
	if err != nil {
		t.Errorf("err %v", err)
	}
	if equal := reflect.DeepEqual(mpack, readMsgPack); !equal {
		t.Fatalf("should not be error")
	}
}
func TestGetLabels(t *testing.T) {
	assert := assert.New(t)

	tests := []struct {
		test        string
		input       string
		outputVal   map[string]string
		outputError bool
	}{
		{
			test:      "working labels",
			input:     "k1:v1,k2:v2.3:4+5",
			outputVal: map[string]string{"k1": "v1", "k2": "v2.3:4+5"},
		},
		{
			test:        "bad label (no separator)",
			input:       "k1,k2:v2",
			outputError: true,
		},
		{
			test:        "bad label (no key)",
			input:       "k1:v1,:v2",
			outputError: true,
		},
		{
			test:        "bad label (no value)",
			input:       "k1:v1,k1:",
			outputError: true,
		},
		{
			test:      "empty",
			input:     "",
			outputVal: nil,
		},
	}

	for _, test := range tests {
		queryParams := make(url.Values)
		queryParams.Add("labels", test.input)
		u := &url.URL{RawQuery: queryParams.Encode()}
		req := restful.NewRequest(&http.Request{URL: u})
		res, err := getLabels(req)
		if test.outputError && !assert.Error(err, "test %q should have yielded an error", test.test) {
			continue
		} else if !test.outputError && !assert.NoError(err, "test %q should not have yielded an error", test.test) {
			continue
		}

		assert.Equal(test.outputVal, res, "test %q should have output the correct label map", test.test)
	}
}
func TestGetAggregations(t *testing.T) {
	assert := assert.New(t)

	validAggregations := "average,max"
	invalidAggregations := "max,non-existant"

	req := restful.NewRequest(&http.Request{})
	pathParams := req.PathParameters()
	pathParams["aggregations"] = validAggregations

	validRes, validErr := getAggregations(req)
	if assert.NoError(validErr, "expected valid list to not produce an error") {
		assert.Equal([]core.AggregationType{core.AggregationTypeAverage, core.AggregationTypeMaximum}, validRes, "expected valid list to be properly split and converted to AggregationType values")
	}

	pathParams["aggregations"] = invalidAggregations
	_, invalidErr := getAggregations(req)
	assert.Error(invalidErr, "expected list with unknown aggregations to produce an error")
}
func TestFetchAggregations(t *testing.T) {
	api, src := prepApi()
	nowTime := time.Now().UTC().Truncate(time.Second)
	src.nowTime = nowTime
	nowFunc = func() time.Time { return nowTime }

	tests := []struct {
		test              string
		bucketSize        string
		start             string
		end               string
		labels            string
		fun               func(*restful.Request, *restful.Response)
		pathParams        map[string]string
		expectedMetricReq metricReq
		expectedStatus    int
	}{
		{
			test:  "cluster aggregations",
			fun:   api.clusterAggregations,
			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
			pathParams: map[string]string{
				"metric-name":  "some-metric",
				"aggregations": "count,average",
			},
			expectedMetricReq: metricReq{
				name:  "some-metric",
				start: nowTime.Add(-10 * time.Second),
				keys: []core.HistoricalKey{
					{ObjectType: core.MetricSetTypeCluster},
				},
			},
		},
		{
			test:  "node aggregations",
			fun:   api.nodeAggregations,
			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
			pathParams: map[string]string{
				"metric-name":  "some-metric",
				"node-name":    "node1",
				"aggregations": "count,average",
			},
			expectedMetricReq: metricReq{
				name:  "some-metric",
				start: nowTime.Add(-10 * time.Second),
				keys: []core.HistoricalKey{
					{ObjectType: core.MetricSetTypeNode, NodeName: "node1"},
				},
			},
		},
		{
			test:  "namespace aggregations",
			fun:   api.namespaceAggregations,
			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
			pathParams: map[string]string{
				"metric-name":    "some-metric",
				"namespace-name": "ns1",
				"aggregations":   "count,average",
			},
			expectedMetricReq: metricReq{
				name:  "some-metric",
				start: nowTime.Add(-10 * time.Second),
				keys: []core.HistoricalKey{
					{ObjectType: core.MetricSetTypeNamespace, NamespaceName: "ns1"},
				},
			},
		},
		{
			test:  "pod name aggregations",
			fun:   api.podAggregations,
			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
			pathParams: map[string]string{
				"metric-name":    "some-metric",
				"namespace-name": "ns1",
				"pod-name":       "pod1",
				"aggregations":   "count,average",
			},
			expectedMetricReq: metricReq{
				name:  "some-metric",
				start: nowTime.Add(-10 * time.Second),
				keys: []core.HistoricalKey{
					{ObjectType: core.MetricSetTypePod, NamespaceName: "ns1", PodName: "pod1"},
				},
			},
		},
		{
			test:  "pod id aggregations",
			fun:   api.podAggregations,
			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
			pathParams: map[string]string{
				"metric-name":  "some-metric",
				"pod-id":       "pod-1-id",
				"aggregations": "count,average",
			},
			expectedMetricReq: metricReq{
				name:  "some-metric",
				start: nowTime.Add(-10 * time.Second),
				keys: []core.HistoricalKey{
					{ObjectType: core.MetricSetTypePod, PodId: "pod-1-id"},
				},
			},
		},
		{
			test:  "pod name container aggregations",
			fun:   api.podContainerAggregations,
			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
			pathParams: map[string]string{
				"metric-name":    "some-metric",
				"namespace-name": "ns1",
				"pod-name":       "pod1",
				"container-name": "cont1",
				"aggregations":   "count,average",
			},
			expectedMetricReq: metricReq{
				name:  "some-metric",
				start: nowTime.Add(-10 * time.Second),
				keys: []core.HistoricalKey{
					{ObjectType: core.MetricSetTypePodContainer, NamespaceName: "ns1", PodName: "pod1", ContainerName: "cont1"},
				},
			},
		},
		{
			test:  "pod id container aggregations",
			fun:   api.podContainerAggregations,
			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
			pathParams: map[string]string{
				"metric-name":    "some-metric",
				"pod-id":         "pod-1-id",
				"container-name": "cont1",
				"aggregations":   "count,average",
			},
			expectedMetricReq: metricReq{
				name:  "some-metric",
				start: nowTime.Add(-10 * time.Second),
				keys: []core.HistoricalKey{
					{ObjectType: core.MetricSetTypePodContainer, PodId: "pod-1-id", ContainerName: "cont1"},
				},
			},
		},
		{
			test:  "system container aggregations",
			fun:   api.freeContainerAggregations,
			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
			pathParams: map[string]string{
				"metric-name":    "some-metric",
				"node-name":      "node1",
				"container-name": "cont1",
				"aggregations":   "count,average",
			},
			expectedMetricReq: metricReq{
				name:  "some-metric",
				start: nowTime.Add(-10 * time.Second),
				keys: []core.HistoricalKey{
					{ObjectType: core.MetricSetTypeSystemContainer, NodeName: "node1", ContainerName: "cont1"},
				},
			},
		},
		{
			test: "aggregations with end and bucket",
			fun:  api.clusterAggregations,
			pathParams: map[string]string{
				"metric-name":  "some-metric",
				"aggregations": "count,average",
			},
			start:      nowTime.Add(-10 * time.Second).Format(time.RFC3339),
			end:        nowTime.Format(time.RFC3339),
			bucketSize: "20s",
			expectedMetricReq: metricReq{
				name: "some-metric",
				keys: []core.HistoricalKey{
					{ObjectType: core.MetricSetTypeCluster},
				},
				start:      nowTime.Add(-10 * time.Second),
				end:        nowTime,
				bucketSize: 20 * time.Second,
			},
		},
		{
			test: "aggregations with labels",
			fun:  api.clusterAggregations,
			pathParams: map[string]string{
				"metric-name":  "some-metric",
				"aggregations": "count,average",
			},
			labels: "somelbl:v1,otherlbl:v2.3:4",
			start:  nowTime.Add(-10 * time.Second).Format(time.RFC3339),
			expectedMetricReq: metricReq{
				name: "some-metric",
				keys: []core.HistoricalKey{
					{ObjectType: core.MetricSetTypeCluster},
				},
				start:  nowTime.Add(-10 * time.Second),
				labels: map[string]string{"somelbl": "v1", "otherlbl": "v2.3:4"},
			},
		},
		{
			test:           "aggregations with bad start time",
			fun:            api.clusterAggregations,
			start:          "afdsfd",
			expectedStatus: http.StatusBadRequest,
		},
		{
			test:  "aggregations with fetch error",
			fun:   api.clusterAggregations,
			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
			pathParams: map[string]string{
				"metric-name":  "invalid",
				"aggregations": "count,average",
			},
			expectedStatus: http.StatusInternalServerError,
		},
		{
			test:           "aggregations with no start time",
			fun:            api.clusterAggregations,
			expectedStatus: http.StatusBadRequest,
		},
	}

	assert := assert.New(t)
	restful.DefaultResponseMimeType = restful.MIME_JSON

	// doesn't particularly correspond to the query -- we're just using it to
	// test conversion between internal types
	countVal := uint64(10)
	expectedNormalVals := types.MetricAggregationResult{
		BucketSize: 10 * time.Second,
		Buckets: []types.MetricAggregationBucket{
			{
				Timestamp: src.nowTime.Add(-10 * time.Second),
				Count:     &countVal,
			},
		},
	}

	aggList := []core.AggregationType{
		core.AggregationTypeCount,
		core.AggregationTypeAverage,
	}

	for _, test := range tests {
		queryParams := make(url.Values)
		queryParams.Add("start", test.start)
		queryParams.Add("end", test.end)
		queryParams.Add("bucket", test.bucketSize)
		queryParams.Add("labels", test.labels)
		u := &url.URL{RawQuery: queryParams.Encode()}
		req := restful.NewRequest(&http.Request{URL: u})
		pathParams := req.PathParameters()
		for k, v := range test.pathParams {
			pathParams[k] = v
		}
		recorder := &fakeRespRecorder{
			data:    new(bytes.Buffer),
			headers: make(http.Header),
		}
		resp := restful.NewResponse(recorder)

		test.fun(req, resp)

		if test.expectedStatus != 0 {
			assert.Equal(test.expectedStatus, recorder.status, "for test %q: status should have been an error status", test.test)
		} else {
			if !assert.Equal(http.StatusOK, recorder.status, "for test %q: status should have been OK (200)", test.test) {
				continue
			}

			actualReq := src.metricRequests[len(src.metricRequests)-1]
			if test.expectedMetricReq.end.IsZero() {
				test.expectedMetricReq.end = nowTime
			}

			test.expectedMetricReq.aggregations = aggList
			assert.Equal(test.expectedMetricReq, actualReq, "for test %q: expected a different metric request to have been placed", test.test)

			actualVals := types.MetricAggregationResult{}
			if err := json.Unmarshal(recorder.data.Bytes(), &actualVals); err != nil {
				t.Fatalf("Unexpected error: %v", err)
			}

			assert.Equal(expectedNormalVals, actualVals, "for test %q: should have gotten expected JSON", test.test)
		}
	}

	listTests := []struct {
		test              string
		start             string
		labels            string
		end               string
		fun               func(*restful.Request, *restful.Response)
		pathParams        map[string]string
		expectedMetricReq metricReq
		expectedStatus    int
	}{
		{
			test:  "pod id list aggregations",
			fun:   api.podListAggregations,
			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
			pathParams: map[string]string{
				"metric-name":  "some-metric",
				"pod-id-list":  "pod-id-1,pod-id-2,pod-id-3",
				"aggregations": "count,average",
			},
			expectedMetricReq: metricReq{
				name: "some-metric",
				keys: []core.HistoricalKey{
					{ObjectType: core.MetricSetTypePod, PodId: "pod-id-1"},
					{ObjectType: core.MetricSetTypePod, PodId: "pod-id-2"},
					{ObjectType: core.MetricSetTypePod, PodId: "pod-id-3"},
				},
				start: nowTime.Add(-10 * time.Second),
			},
		},
		{
			test:  "pod name list aggregations",
			fun:   api.podListAggregations,
			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
			pathParams: map[string]string{
				"metric-name":    "some-metric",
				"namespace-name": "ns1",
				"pod-list":       "pod1,pod2,pod3",
				"aggregations":   "count,average",
			},
			expectedMetricReq: metricReq{
				name: "some-metric",
				keys: []core.HistoricalKey{
					{ObjectType: core.MetricSetTypePod, NamespaceName: "ns1", PodName: "pod1"},
					{ObjectType: core.MetricSetTypePod, NamespaceName: "ns1", PodName: "pod2"},
					{ObjectType: core.MetricSetTypePod, NamespaceName: "ns1", PodName: "pod3"},
				},
				start: nowTime.Add(-10 * time.Second),
			},
		},
		{
			test:   "pod list aggregations with labels",
			fun:    api.podListAggregations,
			start:  nowTime.Add(-10 * time.Second).Format(time.RFC3339),
			labels: "somelbl:v1,otherlbl:v2.3:4",
			pathParams: map[string]string{
				"metric-name":    "some-metric",
				"namespace-name": "ns1",
				"pod-list":       "pod1,pod2,pod3",
				"aggregations":   "count,average",
			},
			expectedMetricReq: metricReq{
				name: "some-metric",
				keys: []core.HistoricalKey{
					{ObjectType: core.MetricSetTypePod, NamespaceName: "ns1", PodName: "pod1"},
					{ObjectType: core.MetricSetTypePod, NamespaceName: "ns1", PodName: "pod2"},
					{ObjectType: core.MetricSetTypePod, NamespaceName: "ns1", PodName: "pod3"},
				},
				start:  nowTime.Add(-10 * time.Second),
				labels: map[string]string{"somelbl": "v1", "otherlbl": "v2.3:4"},
			},
		},
		{
			test:           "pod list aggregations with bad start time",
			fun:            api.podListAggregations,
			start:          "afdsfd",
			expectedStatus: http.StatusBadRequest,
		},
		{
			test:  "pod list aggregations with fetch error",
			fun:   api.podListAggregations,
			start: nowTime.Add(-10 * time.Second).Format(time.RFC3339),
			pathParams: map[string]string{
				"metric-name":  "invalid",
				"aggregations": "count,average",
			},
			expectedStatus: http.StatusInternalServerError,
		},
		{
			test:           "pod list aggregations with no start time",
			fun:            api.podListAggregations,
			expectedStatus: http.StatusBadRequest,
		},
	}

	expectedListVals := types.MetricAggregationResultList{
		Items: []types.MetricAggregationResult{
			expectedNormalVals,
			expectedNormalVals,
			expectedNormalVals,
		},
	}

	for _, test := range listTests {
		queryParams := make(url.Values)
		queryParams.Add("start", test.start)
		queryParams.Add("end", test.end)
		queryParams.Add("labels", test.labels)
		u := &url.URL{RawQuery: queryParams.Encode()}
		req := restful.NewRequest(&http.Request{URL: u})
		pathParams := req.PathParameters()
		for k, v := range test.pathParams {
			pathParams[k] = v
		}
		recorder := &fakeRespRecorder{
			data:    new(bytes.Buffer),
			headers: make(http.Header),
		}
		resp := restful.NewResponse(recorder)

		test.fun(req, resp)

		if test.expectedStatus != 0 {
			assert.Equal(test.expectedStatus, recorder.status, "for test %q: status should have been an error status", test.test)
		} else {
			if !assert.Equal(http.StatusOK, recorder.status, "for test %q: status should have been OK (200)", test.test) {
				continue
			}

			actualReq := src.metricRequests[len(src.metricRequests)-1]
			if test.expectedMetricReq.end.IsZero() {
				test.expectedMetricReq.end = nowTime
			}
			test.expectedMetricReq.aggregations = aggList
			assert.Equal(test.expectedMetricReq, actualReq, "for test %q: expected a different metric request to have been placed", test.test)

			actualVals := types.MetricAggregationResultList{}
			if err := json.Unmarshal(recorder.data.Bytes(), &actualVals); err != nil {
				t.Fatalf("Unexpected error: %v", err)
			}

			assert.Equal(expectedListVals, actualVals, "for test %q: should have gotten expected JSON", test.test)
		}
	}
}
func TestListObjects(t *testing.T) {
	api, src := prepApi()

	src.nodes = []string{"node1", "node2"}
	src.namespaces = []string{"ns1", "ns2"}
	src.podsForNamespace = map[string][]string{
		"ns1": {"pod1", "pod2"},
	}
	src.containersForNode = map[string][]string{
		"node1": {"x/y/z", "a/b/c"},
	}

	tests := []struct {
		name          string
		fun           func(request *restful.Request, response *restful.Response)
		pathParams    map[string]string
		expectedNames []string
	}{
		{
			name:          "nodes",
			fun:           api.nodeList,
			expectedNames: []string{"node1", "node2"},
		},
		{
			name:          "namespaces",
			fun:           api.namespaceList,
			expectedNames: []string{"ns1", "ns2"},
		},
		{
			name:          "pods in namespace",
			fun:           api.namespacePodList,
			pathParams:    map[string]string{"namespace-name": "ns1"},
			expectedNames: []string{"pod1", "pod2"},
		},
		{
			name: "free containers on node",
			fun:  api.nodeSystemContainerList,
			pathParams: map[string]string{
				"node-name": "node1",
			},
			expectedNames: []string{"x/y/z", "a/b/c"},
		},
	}

	assert := assert.New(t)
	restful.DefaultResponseMimeType = restful.MIME_JSON

	for _, test := range tests {
		req := restful.NewRequest(&http.Request{})
		pathParams := req.PathParameters()
		for k, v := range test.pathParams {
			pathParams[k] = v
		}
		recorder := &fakeRespRecorder{
			data:    new(bytes.Buffer),
			headers: make(http.Header),
		}
		resp := restful.NewResponse(recorder)

		test.fun(req, resp)

		actualNames := []string{}
		if err := json.Unmarshal(recorder.data.Bytes(), &actualNames); err != nil {
			t.Fatalf("Unexpected error: %v", err)
		}

		assert.Equal(http.StatusOK, recorder.status, "status should have been OK (200)")
		assert.Equal(test.expectedNames, actualNames, "should have gotten expected JSON")
	}

	for _, test := range tests {
		if len(test.pathParams) == 0 {
			// don't test tests with no parameters for invalid parameters
			continue
		}
		req := restful.NewRequest(&http.Request{})
		pathParams := req.PathParameters()
		for k := range test.pathParams {
			pathParams[k] = "some-other-value"
		}
		recorder := &fakeRespRecorder{
			data:    new(bytes.Buffer),
			headers: make(http.Header),
		}
		resp := restful.NewResponse(recorder)

		test.fun(req, resp)

		assert.Equal(http.StatusInternalServerError, recorder.status, "status should have been InternalServerError (500)")
	}
}
func TestAvailableMetrics(t *testing.T) {
	api, src := prepApi()

	src.metricNames = map[core.HistoricalKey][]string{
		core.HistoricalKey{
			ObjectType: core.MetricSetTypeCluster,
		}: {"cm1", "cm2"},

		core.HistoricalKey{
			ObjectType: core.MetricSetTypeNode,
			NodeName:   "somenode1",
		}: {"nm1", "nm2"},

		core.HistoricalKey{
			ObjectType:    core.MetricSetTypeNamespace,
			NamespaceName: "somens1",
		}: {"nsm1", "nsm2"},

		core.HistoricalKey{
			ObjectType:    core.MetricSetTypePod,
			NamespaceName: "somens1",
			PodName:       "somepod1",
		}: {"pm1", "pm2"},

		core.HistoricalKey{
			ObjectType:    core.MetricSetTypePodContainer,
			NamespaceName: "somens1",
			PodName:       "somepod1",
			ContainerName: "somecont1",
		}: {"pcm1", "pcm2"},

		core.HistoricalKey{
			ObjectType:    core.MetricSetTypeSystemContainer,
			NodeName:      "somenode1",
			ContainerName: "somecont1",
		}: {"ncm1", "ncm2"},
	}

	tests := []struct {
		name          string
		fun           func(request *restful.Request, response *restful.Response)
		pathParams    map[string]string
		expectedNames []string
	}{
		{
			name:          "cluster metrics",
			fun:           api.availableClusterMetrics,
			pathParams:    map[string]string{},
			expectedNames: []string{"cm1", "cm2"},
		},
		{
			name:          "node metrics",
			fun:           api.availableNodeMetrics,
			pathParams:    map[string]string{"node-name": "somenode1"},
			expectedNames: []string{"nm1", "nm2"},
		},
		{
			name:          "namespace metrics",
			fun:           api.availableNamespaceMetrics,
			pathParams:    map[string]string{"namespace-name": "somens1"},
			expectedNames: []string{"nsm1", "nsm2"},
		},
		{
			name: "pod metrics",
			fun:  api.availablePodMetrics,
			pathParams: map[string]string{
				"namespace-name": "somens1",
				"pod-name":       "somepod1",
			},
			expectedNames: []string{"pm1", "pm2"},
		},
		{
			name: "pod container metrics",
			fun:  api.availablePodContainerMetrics,
			pathParams: map[string]string{
				"namespace-name": "somens1",
				"pod-name":       "somepod1",
				"container-name": "somecont1",
			},
			expectedNames: []string{"pcm1", "pcm2"},
		},
		{
			name: "free container metrics",
			fun:  api.availableFreeContainerMetrics,
			pathParams: map[string]string{
				"node-name":      "somenode1",
				"container-name": "somecont1",
			},
			expectedNames: []string{"ncm1", "ncm2"},
		},
	}

	assert := assert.New(t)
	restful.DefaultResponseMimeType = restful.MIME_JSON

	for _, test := range tests {
		req := restful.NewRequest(&http.Request{})
		pathParams := req.PathParameters()
		for k, v := range test.pathParams {
			pathParams[k] = v
		}
		recorder := &fakeRespRecorder{
			data:    new(bytes.Buffer),
			headers: make(http.Header),
		}
		resp := restful.NewResponse(recorder)

		test.fun(req, resp)

		actualNames := []string{}
		if err := json.Unmarshal(recorder.data.Bytes(), &actualNames); err != nil {
			t.Fatalf("Unexpected error: %v", err)
		}

		assert.Equal(http.StatusOK, recorder.status, "status should have been OK (200)")
		assert.Equal(test.expectedNames, actualNames, "should have gotten expected JSON")
	}

	for _, test := range tests {
		if len(test.pathParams) == 0 {
			// don't test tests with no parameters for invalid parameters
			continue
		}
		req := restful.NewRequest(&http.Request{})
		pathParams := req.PathParameters()
		for k := range test.pathParams {
			pathParams[k] = "some-other-value"
		}
		recorder := &fakeRespRecorder{
			data:    new(bytes.Buffer),
			headers: make(http.Header),
		}
		resp := restful.NewResponse(recorder)

		test.fun(req, resp)

		assert.Equal(http.StatusInternalServerError, recorder.status, "status should have been InternalServerError (500)")
	}
}
func TestGetBucketSize(t *testing.T) {
	tests := []struct {
		sizeParam        string
		expectedDuration time.Duration
		expectedError    bool
	}{
		{
			// empty duration
			sizeParam:        "",
			expectedDuration: 0,
		},
		{
			// no units
			sizeParam:     "1",
			expectedError: true,
		},
		{
			// unknown unit
			sizeParam:     "1g",
			expectedError: true,
		},
		{
			// invalid unit (looks like ms)
			sizeParam:     "10gs",
			expectedError: true,
		},
		{
			// NaN
			sizeParam:     "abch",
			expectedError: true,
		},
		{
			sizeParam:        "5ms",
			expectedDuration: 5 * time.Millisecond,
		},
		{
			sizeParam:        "10s",
			expectedDuration: 10 * time.Second,
		},
		{
			sizeParam:        "15m",
			expectedDuration: 15 * time.Minute,
		},
		{
			sizeParam:        "20h",
			expectedDuration: 20 * time.Hour,
		},
		{
			sizeParam:        "25d",
			expectedDuration: 600 * time.Hour,
		},
	}

	assert := assert.New(t)
	for _, test := range tests {
		u, err := url.Parse(fmt.Sprintf("/foo?bucket=%s", test.sizeParam))
		if err != nil {
			t.Fatalf("Unexpected error: %v", err)
		}
		req := restful.NewRequest(&http.Request{URL: u})
		res, err := getBucketSize(req)
		if test.expectedError {
			assert.Error(err, fmt.Sprintf("%q should have been an invalid value", test.sizeParam))
		} else if assert.NoError(err, fmt.Sprintf("%q should have been a valid value", test.sizeParam)) {
			assert.Equal(test.expectedDuration, res, fmt.Sprintf("%q should have given the correct duration", test.sizeParam))
		}
	}
}