github.com/galamsiva2020/kubernetes-heapster-monitoring@v0.0.0-20210823134957-3c1baa7c1e70/metrics/api/v1/historical_handlers.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  	"fmt"
    19  	"net/http"
    20  	"strconv"
    21  	"strings"
    22  	"time"
    23  
    24  	restful "github.com/emicklei/go-restful"
    25  
    26  	"k8s.io/heapster/metrics/api/v1/types"
    27  	"k8s.io/heapster/metrics/core"
    28  	"k8s.io/heapster/metrics/util/metrics"
    29  )
    30  
    31  // HistoricalApi wraps the standard API to overload the fetchers to use the
    32  // one of the persistent storage sinks instead of the in-memory metrics sink
    33  type HistoricalApi struct {
    34  	*Api
    35  }
    36  
    37  // metricsAggregationFetcher represents the capability to fetch aggregations for metrics
    38  type metricsAggregationFetcher interface {
    39  	clusterAggregations(request *restful.Request, response *restful.Response)
    40  	nodeAggregations(request *restful.Request, response *restful.Response)
    41  	namespaceAggregations(request *restful.Request, response *restful.Response)
    42  	podAggregations(request *restful.Request, response *restful.Response)
    43  	podContainerAggregations(request *restful.Request, response *restful.Response)
    44  	freeContainerAggregations(request *restful.Request, response *restful.Response)
    45  	podListAggregations(request *restful.Request, response *restful.Response)
    46  
    47  	isRunningInKubernetes() bool
    48  }
    49  
    50  // addAggregationRoutes adds routes to a webservice which point to a metricsAggregationFetcher's methods
    51  func addAggregationRoutes(a metricsAggregationFetcher, ws *restful.WebService) {
    52  	// The /metrics-aggregated/{aggregations}/{metric-name} endpoint exposes some aggregations for the Cluster entity of the historical API.
    53  	ws.Route(ws.GET("/metrics-aggregated/{aggregations}/{metric-name:*}").
    54  		To(metrics.InstrumentRouteFunc("clusterMetrics", a.clusterAggregations)).
    55  		Doc("Export some cluster-level metric aggregations").
    56  		Operation("clusterAggregations").
    57  		Param(ws.PathParameter("aggregations", "A comma-separated list of requested aggregations").DataType("string")).
    58  		Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")).
    59  		Param(ws.QueryParameter("start", "Start time for requested metric").DataType("string")).
    60  		Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")).
    61  		Param(ws.QueryParameter("labels", "A comma-separated list of key:values pairs to use to search for a labeled metric").DataType("string")).
    62  		Writes(types.MetricAggregationResult{}))
    63  
    64  	// The /nodes/{node-name}/metrics-aggregated/{aggregations}/{metric-name} endpoint exposes some aggregations for a Node entity of the historical API.
    65  	// The {node-name} parameter is the hostname of a specific node.
    66  	ws.Route(ws.GET("/nodes/{node-name}/metrics-aggregated/{aggregations}/{metric-name:*}").
    67  		To(metrics.InstrumentRouteFunc("nodeMetrics", a.nodeAggregations)).
    68  		Doc("Export a node-level metric").
    69  		Operation("nodeAggregations").
    70  		Param(ws.PathParameter("node-name", "The name of the node to lookup").DataType("string")).
    71  		Param(ws.PathParameter("aggregations", "A comma-separated list of requested aggregations").DataType("string")).
    72  		Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")).
    73  		Param(ws.QueryParameter("start", "Start time for requested metric").DataType("string")).
    74  		Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")).
    75  		Param(ws.QueryParameter("labels", "A comma-separated list of key:values pairs to use to search for a labeled metric").DataType("string")).
    76  		Writes(types.MetricAggregationResult{}))
    77  
    78  	if a.isRunningInKubernetes() {
    79  		// The /namespaces/{namespace-name}/metrics-aggregated/{aggregations}/{metric-name} endpoint exposes some aggregations
    80  		// for a Namespace entity of the historical API.
    81  		ws.Route(ws.GET("/namespaces/{namespace-name}/metrics-aggregated/{aggregations}/{metric-name:*}").
    82  			To(metrics.InstrumentRouteFunc("namespaceMetrics", a.namespaceAggregations)).
    83  			Doc("Export some namespace-level metric aggregations").
    84  			Operation("namespaceAggregations").
    85  			Param(ws.PathParameter("namespace-name", "The name of the namespace to lookup").DataType("string")).
    86  			Param(ws.PathParameter("aggregations", "A comma-separated list of requested aggregations").DataType("string")).
    87  			Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")).
    88  			Param(ws.QueryParameter("start", "Start time for requested metrics").DataType("string")).
    89  			Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")).
    90  			Param(ws.QueryParameter("labels", "A comma-separated list of key:values pairs to use to search for a labeled metric").DataType("string")).
    91  			Writes(types.MetricAggregationResult{}))
    92  
    93  		// The /namespaces/{namespace-name}/pods/{pod-name}/metrics-aggregated/{aggregations}/{metric-name} endpoint exposes
    94  		// some aggregations for a Pod entity of the historical API.
    95  		ws.Route(ws.GET("/namespaces/{namespace-name}/pods/{pod-name}/metrics-aggregated/{aggregations}/{metric-name:*}").
    96  			To(metrics.InstrumentRouteFunc("podMetrics", a.podAggregations)).
    97  			Doc("Export some pod-level metric aggregations").
    98  			Operation("podAggregations").
    99  			Param(ws.PathParameter("namespace-name", "The name of the namespace to lookup").DataType("string")).
   100  			Param(ws.PathParameter("pod-name", "The name of the pod to lookup").DataType("string")).
   101  			Param(ws.PathParameter("aggregations", "A comma-separated list of requested aggregations").DataType("string")).
   102  			Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")).
   103  			Param(ws.QueryParameter("start", "Start time for requested metrics").DataType("string")).
   104  			Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")).
   105  			Param(ws.QueryParameter("labels", "A comma-separated list of key:values pairs to use to search for a labeled metric").DataType("string")).
   106  			Writes(types.MetricAggregationResult{}))
   107  
   108  		// The /namespaces/{namespace-name}/pods/{pod-name}/containers/{container-name}/metrics-aggregated/{aggregations}/{metric-name} endpoint exposes
   109  		// some aggregations for a Container entity of the historical API.
   110  		ws.Route(ws.GET("/namespaces/{namespace-name}/pods/{pod-name}/containers/{container-name}/metrics-aggregated/{aggregations}/{metric-name:*}").
   111  			To(metrics.InstrumentRouteFunc("podContainerMetrics", a.podContainerAggregations)).
   112  			Doc("Export some aggregations for a Pod Container").
   113  			Operation("podContainerAggregations").
   114  			Param(ws.PathParameter("namespace-name", "The name of the namespace to use").DataType("string")).
   115  			Param(ws.PathParameter("pod-name", "The name of the pod to use").DataType("string")).
   116  			Param(ws.PathParameter("container-name", "The name of the namespace to use").DataType("string")).
   117  			Param(ws.PathParameter("aggregations", "A comma-separated list of requested aggregations").DataType("string")).
   118  			Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")).
   119  			Param(ws.QueryParameter("start", "Start time for requested metrics").DataType("string")).
   120  			Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")).
   121  			Param(ws.QueryParameter("labels", "A comma-separated list of key:values pairs to use to search for a labeled metric").DataType("string")).
   122  			Writes(types.MetricAggregationResult{}))
   123  
   124  		// The /pod-id/{pod-id}/metrics-aggregated/{aggregations}/{metric-name} endpoint exposes
   125  		// some aggregations for a Pod entity of the historical API.
   126  		ws.Route(ws.GET("/pod-id/{pod-id}/metrics-aggregated/{aggregations}/{metric-name:*}").
   127  			To(metrics.InstrumentRouteFunc("podMetrics", a.podAggregations)).
   128  			Doc("Export some pod-level metric aggregations").
   129  			Operation("podAggregations").
   130  			Param(ws.PathParameter("pod-id", "The UID of the pod to lookup").DataType("string")).
   131  			Param(ws.PathParameter("aggregations", "A comma-separated list of requested aggregations").DataType("string")).
   132  			Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")).
   133  			Param(ws.QueryParameter("start", "Start time for requested metrics").DataType("string")).
   134  			Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")).
   135  			Param(ws.QueryParameter("labels", "A comma-separated list of key:values pairs to use to search for a labeled metric").DataType("string")).
   136  			Writes(types.MetricAggregationResult{}))
   137  
   138  		// The /pod-id/{pod-id}/containers/{container-name}/metrics-aggregated/{aggregations}/{metric-name} endpoint exposes
   139  		// some aggregations for a Container entity of the historical API.
   140  		ws.Route(ws.GET("/pod-id/{pod-id}/containers/{container-name}/metrics-aggregated/{aggregations}/{metric-name:*}").
   141  			To(metrics.InstrumentRouteFunc("podContainerMetrics", a.podContainerAggregations)).
   142  			Doc("Export some aggregations for a Pod Container").
   143  			Operation("podContainerAggregations").
   144  			Param(ws.PathParameter("pod-id", "The name of the pod to use").DataType("string")).
   145  			Param(ws.PathParameter("container-name", "The name of the namespace to use").DataType("string")).
   146  			Param(ws.PathParameter("aggregations", "A comma-separated list of requested aggregations").DataType("string")).
   147  			Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")).
   148  			Param(ws.QueryParameter("start", "Start time for requested metrics").DataType("string")).
   149  			Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")).
   150  			Param(ws.QueryParameter("labels", "A comma-separated list of key:values pairs to use to search for a labeled metric").DataType("string")).
   151  			Writes(types.MetricAggregationResult{}))
   152  	}
   153  
   154  	// The /nodes/{node-name}/freecontainers/{container-name}/metrics-aggregated/{aggregations}/{metric-name} endpoint exposes
   155  	// some aggregations for a free Container entity of the historical API.
   156  	ws.Route(ws.GET("/nodes/{node-name}/freecontainers/{container-name}/metrics-aggregated/{aggregations}/{metric-name:*}").
   157  		To(metrics.InstrumentRouteFunc("freeContainerMetrics", a.freeContainerAggregations)).
   158  		Doc("Export a contsome iner-level metric aggregations for a free container").
   159  		Operation("freeContainerAggregations").
   160  		Param(ws.PathParameter("node-name", "The name of the node to use").DataType("string")).
   161  		Param(ws.PathParameter("container-name", "The name of the container to use").DataType("string")).
   162  		Param(ws.PathParameter("aggregations", "A comma-separated list of requested aggregations").DataType("string")).
   163  		Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")).
   164  		Param(ws.QueryParameter("start", "Start time for requested metrics").DataType("string")).
   165  		Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")).
   166  		Param(ws.QueryParameter("labels", "A comma-separated list of key:values pairs to use to search for a labeled metric").DataType("string")).
   167  		Writes(types.MetricAggregationResult{}))
   168  
   169  	if a.isRunningInKubernetes() {
   170  		// The /namespaces/{namespace-name}/pod-list/{pod-list}/metrics-aggregated/{aggregations}/{metric-name} endpoint exposes
   171  		// metrics for a list of pods of the historical API.
   172  		ws.Route(ws.GET("/namespaces/{namespace-name}/pod-list/{pod-list}/metrics-aggregated/{aggregations}/{metric-name:*}").
   173  			To(metrics.InstrumentRouteFunc("podListAggregations", a.podListAggregations)).
   174  			Doc("Export some aggregations for all pods from the given list").
   175  			Operation("podListAggregations").
   176  			Param(ws.PathParameter("namespace-name", "The name of the namespace to lookup").DataType("string")).
   177  			Param(ws.PathParameter("pod-list", "Comma separated list of pod names to lookup").DataType("string")).
   178  			Param(ws.PathParameter("aggregations", "A comma-separated list of requested aggregations").DataType("string")).
   179  			Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")).
   180  			Param(ws.QueryParameter("start", "Start time for requested metrics").DataType("string")).
   181  			Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")).
   182  			Param(ws.QueryParameter("labels", "A comma-separated list of key:values pairs to use to search for a labeled metric").DataType("string")).
   183  			Writes(types.MetricAggregationResultList{}))
   184  
   185  		// The /pod-id-list/{pod-id-list}/metrics-aggregated/{aggregations}/{metric-name} endpoint exposes
   186  		// metrics for a list of pod ids of the historical API.
   187  		ws.Route(ws.GET("/pod-id-list/{pod-id-list}/metrics-aggregated/{aggregations}/{metric-name:*}").
   188  			To(metrics.InstrumentRouteFunc("podListAggregations", a.podListAggregations)).
   189  			Doc("Export an aggregation for all pods from the given list").
   190  			Operation("podListAggregations").
   191  			Param(ws.PathParameter("pod-id-list", "Comma separated list of pod UIDs to lookup").DataType("string")).
   192  			Param(ws.PathParameter("aggregations", "A comma-separated list of requested aggregations").DataType("string")).
   193  			Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")).
   194  			Param(ws.QueryParameter("start", "Start time for requested metrics").DataType("string")).
   195  			Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")).
   196  			Param(ws.QueryParameter("labels", "A comma-separated list of key:values pairs to use to search for a labeled metric").DataType("string")).
   197  			Writes(types.MetricAggregationResultList{}))
   198  	}
   199  }
   200  
   201  // RegisterHistorical registers the Historical API endpoints.  It will register the same endpoints
   202  // as those in the model API, plus endpoints for aggregation retrieval, and endpoints to retrieve pod
   203  // metrics by using the pod id.
   204  func (normalApi *Api) RegisterHistorical(container *restful.Container) {
   205  	ws := new(restful.WebService)
   206  	ws.Path("/api/v1/historical").
   207  		Doc("Root endpoint of the historical access API").
   208  		Consumes("*/*").
   209  		Produces(restful.MIME_JSON)
   210  
   211  	a := &HistoricalApi{normalApi}
   212  	addClusterMetricsRoutes(a, ws)
   213  	addAggregationRoutes(a, ws)
   214  
   215  	// register the endpoint for fetching raw metrics based on pod id
   216  	if a.isRunningInKubernetes() {
   217  		// The /pod-id/{pod-id}/metrics-aggregated/{aggregations}/{metric-name} endpoint exposes
   218  		// some aggregations for a Pod entity of the historical API.
   219  		ws.Route(ws.GET("/pod-id/{pod-id}/metrics/{metric-name:*}").
   220  			To(metrics.InstrumentRouteFunc("podMetrics", a.podMetrics)).
   221  			Doc("Export some pod-level metric aggregations").
   222  			Operation("podAggregations").
   223  			Param(ws.PathParameter("pod-id", "The UID of the pod to lookup").DataType("string")).
   224  			Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")).
   225  			Param(ws.QueryParameter("start", "Start time for requested metrics").DataType("string")).
   226  			Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")).
   227  			Writes(types.MetricResult{}))
   228  
   229  		// The /pod-id/{pod-id}/containers/{container-name}/metrics-aggregated/{aggregations}/{metric-name} endpoint exposes
   230  		// some aggregations for a Container entity of the historical API.
   231  		ws.Route(ws.GET("/pod-id/{pod-id}/containers/{container-name}/metrics/{metric-name:*}").
   232  			To(metrics.InstrumentRouteFunc("podContainerMetrics", a.podContainerMetrics)).
   233  			Doc("Export some aggregations for a Pod Container").
   234  			Operation("podContainerAggregations").
   235  			Param(ws.PathParameter("pod-id", "The uid of the pod to use").DataType("string")).
   236  			Param(ws.PathParameter("container-name", "The name of the namespace to use").DataType("string")).
   237  			Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")).
   238  			Param(ws.QueryParameter("start", "Start time for requested metrics").DataType("string")).
   239  			Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")).
   240  			Writes(types.MetricResult{}))
   241  
   242  		// The /pod-id-list/{pod-id-list}/metrics-aggregated/{aggregations}/{metric-name} endpoint exposes
   243  		// metrics for a list of pod ids of the historical API.
   244  		ws.Route(ws.GET("/pod-id-list/{pod-id-list}/metrics/{metric-name:*}").
   245  			To(metrics.InstrumentRouteFunc("podListAggregations", a.podListMetrics)).
   246  			Doc("Export an aggregation for all pods from the given list").
   247  			Operation("podListAggregations").
   248  			Param(ws.PathParameter("pod-id-list", "Comma separated list of pod UIDs to lookup").DataType("string")).
   249  			Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")).
   250  			Param(ws.QueryParameter("start", "Start time for requested metrics").DataType("string")).
   251  			Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")).
   252  			Writes(types.MetricResultList{}))
   253  	}
   254  
   255  	container.Add(ws)
   256  }
   257  
   258  // availableClusterMetrics returns a list of available cluster metric names.
   259  func (a *HistoricalApi) availableClusterMetrics(request *restful.Request, response *restful.Response) {
   260  	key := core.HistoricalKey{ObjectType: core.MetricSetTypeCluster}
   261  	a.processMetricNamesRequest(key, response)
   262  }
   263  
   264  // availableNodeMetrics returns a list of available node metric names.
   265  func (a *HistoricalApi) availableNodeMetrics(request *restful.Request, response *restful.Response) {
   266  	key := core.HistoricalKey{
   267  		ObjectType: core.MetricSetTypeNode,
   268  		NodeName:   request.PathParameter("node-name"),
   269  	}
   270  	a.processMetricNamesRequest(key, response)
   271  }
   272  
   273  // availableNamespaceMetrics returns a list of available namespace metric names.
   274  func (a *HistoricalApi) availableNamespaceMetrics(request *restful.Request, response *restful.Response) {
   275  	key := core.HistoricalKey{
   276  		ObjectType:    core.MetricSetTypeNamespace,
   277  		NamespaceName: request.PathParameter("namespace-name"),
   278  	}
   279  	a.processMetricNamesRequest(key, response)
   280  }
   281  
   282  // availablePodMetrics returns a list of available pod metric names.
   283  func (a *HistoricalApi) availablePodMetrics(request *restful.Request, response *restful.Response) {
   284  	key := core.HistoricalKey{
   285  		ObjectType:    core.MetricSetTypePod,
   286  		NamespaceName: request.PathParameter("namespace-name"),
   287  		PodName:       request.PathParameter("pod-name"),
   288  	}
   289  	a.processMetricNamesRequest(key, response)
   290  }
   291  
   292  // availablePodContainerMetrics returns a list of available pod container metric names.
   293  func (a *HistoricalApi) availablePodContainerMetrics(request *restful.Request, response *restful.Response) {
   294  	key := core.HistoricalKey{
   295  		ObjectType:    core.MetricSetTypePodContainer,
   296  		NamespaceName: request.PathParameter("namespace-name"),
   297  		PodName:       request.PathParameter("pod-name"),
   298  		ContainerName: request.PathParameter("container-name"),
   299  	}
   300  	a.processMetricNamesRequest(key, response)
   301  }
   302  
   303  // availableFreeContainerMetrics returns a list of available pod metric names.
   304  func (a *HistoricalApi) availableFreeContainerMetrics(request *restful.Request, response *restful.Response) {
   305  	key := core.HistoricalKey{
   306  		ObjectType:    core.MetricSetTypeSystemContainer,
   307  		NodeName:      request.PathParameter("node-name"),
   308  		ContainerName: request.PathParameter("container-name"),
   309  	}
   310  	a.processMetricNamesRequest(key, response)
   311  }
   312  
   313  // nodeList lists all nodes for which we have metrics
   314  func (a *HistoricalApi) nodeList(request *restful.Request, response *restful.Response) {
   315  	if resp, err := a.historicalSource.GetNodes(); err != nil {
   316  		response.WriteError(http.StatusInternalServerError, err)
   317  	} else {
   318  		response.WriteEntity(resp)
   319  	}
   320  }
   321  
   322  // namespaceList lists all namespaces for which we have metrics
   323  func (a *HistoricalApi) namespaceList(request *restful.Request, response *restful.Response) {
   324  	if resp, err := a.historicalSource.GetNamespaces(); err != nil {
   325  		response.WriteError(http.StatusInternalServerError, err)
   326  	} else {
   327  		response.WriteEntity(resp)
   328  	}
   329  }
   330  
   331  // namespacePodList lists all pods for which we have metrics in a particular namespace
   332  func (a *HistoricalApi) namespacePodList(request *restful.Request, response *restful.Response) {
   333  	if resp, err := a.historicalSource.GetPodsFromNamespace(request.PathParameter("namespace-name")); err != nil {
   334  		response.WriteError(http.StatusInternalServerError, err)
   335  	} else {
   336  		response.WriteEntity(resp)
   337  	}
   338  }
   339  
   340  // nodeSystemContainerList lists all system containers on a node for which we have metrics
   341  func (a *HistoricalApi) nodeSystemContainerList(request *restful.Request, response *restful.Response) {
   342  	if resp, err := a.historicalSource.GetSystemContainersFromNode(request.PathParameter("node-name")); err != nil {
   343  		response.WriteError(http.StatusInternalServerError, err)
   344  	} else {
   345  		response.WriteEntity(resp)
   346  	}
   347  }
   348  
   349  // clusterMetrics returns a metric timeseries for a metric of the Cluster entity.
   350  func (a *HistoricalApi) clusterMetrics(request *restful.Request, response *restful.Response) {
   351  	key := core.HistoricalKey{ObjectType: core.MetricSetTypeCluster}
   352  	a.processMetricRequest(key, request, response)
   353  }
   354  
   355  // nodeMetrics returns a metric timeseries for a metric of the Node entity.
   356  func (a *HistoricalApi) nodeMetrics(request *restful.Request, response *restful.Response) {
   357  	key := core.HistoricalKey{
   358  		ObjectType: core.MetricSetTypeNode,
   359  		NodeName:   request.PathParameter("node-name"),
   360  	}
   361  	a.processMetricRequest(key, request, response)
   362  }
   363  
   364  // namespaceMetrics returns a metric timeseries for a metric of the Namespace entity.
   365  func (a *HistoricalApi) namespaceMetrics(request *restful.Request, response *restful.Response) {
   366  	key := core.HistoricalKey{
   367  		ObjectType:    core.MetricSetTypeNamespace,
   368  		NamespaceName: request.PathParameter("namespace-name"),
   369  	}
   370  	a.processMetricRequest(key, request, response)
   371  }
   372  
   373  // podMetrics returns a metric timeseries for a metric of the Pod entity.
   374  func (a *HistoricalApi) podMetrics(request *restful.Request, response *restful.Response) {
   375  	var key core.HistoricalKey
   376  	if request.PathParameter("pod-id") != "" {
   377  		key = core.HistoricalKey{
   378  			ObjectType: core.MetricSetTypePod,
   379  			PodId:      request.PathParameter("pod-id"),
   380  		}
   381  	} else {
   382  		key = core.HistoricalKey{
   383  			ObjectType:    core.MetricSetTypePod,
   384  			NamespaceName: request.PathParameter("namespace-name"),
   385  			PodName:       request.PathParameter("pod-name"),
   386  		}
   387  	}
   388  	a.processMetricRequest(key, request, response)
   389  }
   390  
   391  // freeContainerMetrics returns a metric timeseries for a metric of the Container entity.
   392  // freeContainerMetrics addresses only free containers.
   393  func (a *HistoricalApi) freeContainerMetrics(request *restful.Request, response *restful.Response) {
   394  	key := core.HistoricalKey{
   395  		ObjectType:    core.MetricSetTypeSystemContainer,
   396  		NodeName:      request.PathParameter("node-name"),
   397  		ContainerName: request.PathParameter("container-name"),
   398  	}
   399  	a.processMetricRequest(key, request, response)
   400  }
   401  
   402  // podListMetrics returns a list of metric timeseries for each for the listed nodes
   403  func (a *HistoricalApi) podListMetrics(request *restful.Request, response *restful.Response) {
   404  	start, end, err := getStartEndTimeHistorical(request)
   405  	if err != nil {
   406  		response.WriteError(http.StatusBadRequest, err)
   407  		return
   408  	}
   409  
   410  	keys := []core.HistoricalKey{}
   411  	if request.PathParameter("pod-id-list") != "" {
   412  		for _, podId := range strings.Split(request.PathParameter("pod-id-list"), ",") {
   413  			key := core.HistoricalKey{
   414  				ObjectType: core.MetricSetTypePod,
   415  				PodId:      podId,
   416  			}
   417  			keys = append(keys, key)
   418  		}
   419  	} else {
   420  		for _, podName := range strings.Split(request.PathParameter("pod-list"), ",") {
   421  			key := core.HistoricalKey{
   422  				ObjectType:    core.MetricSetTypePod,
   423  				NamespaceName: request.PathParameter("namespace-name"),
   424  				PodName:       podName,
   425  			}
   426  			keys = append(keys, key)
   427  		}
   428  	}
   429  
   430  	labels, err := getLabels(request)
   431  	if err != nil {
   432  		response.WriteError(http.StatusBadRequest, err)
   433  		return
   434  	}
   435  
   436  	metricName := request.PathParameter("metric-name")
   437  	convertedMetricName := convertMetricName(metricName)
   438  
   439  	var metrics map[core.HistoricalKey][]core.TimestampedMetricValue
   440  	if labels != nil {
   441  		metrics, err = a.historicalSource.GetLabeledMetric(convertedMetricName, labels, keys, start, end)
   442  	} else {
   443  		metrics, err = a.historicalSource.GetMetric(convertedMetricName, keys, start, end)
   444  	}
   445  
   446  	if err != nil {
   447  		response.WriteError(http.StatusInternalServerError, err)
   448  		return
   449  	}
   450  
   451  	result := types.MetricResultList{
   452  		Items: make([]types.MetricResult, 0, len(keys)),
   453  	}
   454  	for _, key := range keys {
   455  		result.Items = append(result.Items, exportTimestampedMetricValue(metrics[key]))
   456  	}
   457  	response.PrettyPrint(false)
   458  	response.WriteEntity(result)
   459  }
   460  
   461  // podContainerMetrics returns a metric timeseries for a metric of a Pod Container entity.
   462  func (a *HistoricalApi) podContainerMetrics(request *restful.Request, response *restful.Response) {
   463  	var key core.HistoricalKey
   464  	if request.PathParameter("pod-id") != "" {
   465  		key = core.HistoricalKey{
   466  			ObjectType:    core.MetricSetTypePodContainer,
   467  			PodId:         request.PathParameter("pod-id"),
   468  			ContainerName: request.PathParameter("container-name"),
   469  		}
   470  	} else {
   471  		key = core.HistoricalKey{
   472  			ObjectType:    core.MetricSetTypePodContainer,
   473  			NamespaceName: request.PathParameter("namespace-name"),
   474  			PodName:       request.PathParameter("pod-name"),
   475  			ContainerName: request.PathParameter("container-name"),
   476  		}
   477  	}
   478  	a.processMetricRequest(key, request, response)
   479  }
   480  
   481  // clusterAggregations returns a metric timeseries for a metric of the Cluster entity.
   482  func (a *HistoricalApi) clusterAggregations(request *restful.Request, response *restful.Response) {
   483  	key := core.HistoricalKey{ObjectType: core.MetricSetTypeCluster}
   484  	a.processAggregationRequest(key, request, response)
   485  }
   486  
   487  // nodeAggregations returns a metric timeseries for a metric of the Node entity.
   488  func (a *HistoricalApi) nodeAggregations(request *restful.Request, response *restful.Response) {
   489  	key := core.HistoricalKey{
   490  		ObjectType: core.MetricSetTypeNode,
   491  		NodeName:   request.PathParameter("node-name"),
   492  	}
   493  	a.processAggregationRequest(key, request, response)
   494  }
   495  
   496  // namespaceAggregations returns a metric timeseries for a metric of the Namespace entity.
   497  func (a *HistoricalApi) namespaceAggregations(request *restful.Request, response *restful.Response) {
   498  	key := core.HistoricalKey{
   499  		ObjectType:    core.MetricSetTypeNamespace,
   500  		NamespaceName: request.PathParameter("namespace-name"),
   501  	}
   502  	a.processAggregationRequest(key, request, response)
   503  }
   504  
   505  // podAggregations returns a metric timeseries for a metric of the Pod entity.
   506  func (a *HistoricalApi) podAggregations(request *restful.Request, response *restful.Response) {
   507  	var key core.HistoricalKey
   508  	if request.PathParameter("pod-id") != "" {
   509  		key = core.HistoricalKey{
   510  			ObjectType: core.MetricSetTypePod,
   511  			PodId:      request.PathParameter("pod-id"),
   512  		}
   513  	} else {
   514  		key = core.HistoricalKey{
   515  			ObjectType:    core.MetricSetTypePod,
   516  			NamespaceName: request.PathParameter("namespace-name"),
   517  			PodName:       request.PathParameter("pod-name"),
   518  		}
   519  	}
   520  	a.processAggregationRequest(key, request, response)
   521  }
   522  
   523  // podContainerAggregations returns a metric timeseries for a metric of a Pod Container entity.
   524  func (a *HistoricalApi) podContainerAggregations(request *restful.Request, response *restful.Response) {
   525  	var key core.HistoricalKey
   526  	if request.PathParameter("pod-id") != "" {
   527  		key = core.HistoricalKey{
   528  			ObjectType:    core.MetricSetTypePodContainer,
   529  			PodId:         request.PathParameter("pod-id"),
   530  			ContainerName: request.PathParameter("container-name"),
   531  		}
   532  	} else {
   533  		key = core.HistoricalKey{
   534  			ObjectType:    core.MetricSetTypePodContainer,
   535  			NamespaceName: request.PathParameter("namespace-name"),
   536  			PodName:       request.PathParameter("pod-name"),
   537  			ContainerName: request.PathParameter("container-name"),
   538  		}
   539  	}
   540  	a.processAggregationRequest(key, request, response)
   541  }
   542  
   543  // freeContainerAggregations returns a metric timeseries for a metric of the Container entity.
   544  // freeContainerAggregations addresses only free containers.
   545  func (a *HistoricalApi) freeContainerAggregations(request *restful.Request, response *restful.Response) {
   546  	key := core.HistoricalKey{
   547  		ObjectType:    core.MetricSetTypeSystemContainer,
   548  		NodeName:      request.PathParameter("node-name"),
   549  		ContainerName: request.PathParameter("container-name"),
   550  	}
   551  	a.processAggregationRequest(key, request, response)
   552  }
   553  
   554  // podListAggregations returns a list of metric timeseries for the specified pods.
   555  func (a *HistoricalApi) podListAggregations(request *restful.Request, response *restful.Response) {
   556  	start, end, err := getStartEndTimeHistorical(request)
   557  	if err != nil {
   558  		response.WriteError(http.StatusBadRequest, err)
   559  		return
   560  	}
   561  	bucketSize, err := getBucketSize(request)
   562  	if err != nil {
   563  		response.WriteError(http.StatusBadRequest, err)
   564  		return
   565  	}
   566  	aggregations, err := getAggregations(request)
   567  	if err != nil {
   568  		response.WriteError(http.StatusBadRequest, err)
   569  		return
   570  	}
   571  	labels, err := getLabels(request)
   572  	if err != nil {
   573  		response.WriteError(http.StatusBadRequest, err)
   574  		return
   575  	}
   576  	keys := []core.HistoricalKey{}
   577  	if request.PathParameter("pod-id-list") != "" {
   578  		for _, podId := range strings.Split(request.PathParameter("pod-id-list"), ",") {
   579  			key := core.HistoricalKey{
   580  				ObjectType: core.MetricSetTypePod,
   581  				PodId:      podId,
   582  			}
   583  			keys = append(keys, key)
   584  		}
   585  	} else {
   586  		for _, podName := range strings.Split(request.PathParameter("pod-list"), ",") {
   587  			key := core.HistoricalKey{
   588  				ObjectType:    core.MetricSetTypePod,
   589  				NamespaceName: request.PathParameter("namespace-name"),
   590  				PodName:       podName,
   591  			}
   592  			keys = append(keys, key)
   593  		}
   594  	}
   595  	metricName := request.PathParameter("metric-name")
   596  	convertedMetricName := convertMetricName(metricName)
   597  	var metrics map[core.HistoricalKey][]core.TimestampedAggregationValue
   598  	if labels != nil {
   599  		metrics, err = a.historicalSource.GetLabeledAggregation(convertedMetricName, labels, aggregations, keys, start, end, bucketSize)
   600  	} else {
   601  		metrics, err = a.historicalSource.GetAggregation(convertedMetricName, aggregations, keys, start, end, bucketSize)
   602  	}
   603  	if err != nil {
   604  		response.WriteError(http.StatusInternalServerError, err)
   605  		return
   606  	}
   607  
   608  	result := types.MetricAggregationResultList{
   609  		Items: make([]types.MetricAggregationResult, 0, len(keys)),
   610  	}
   611  	for _, key := range keys {
   612  		result.Items = append(result.Items, exportTimestampedAggregationValue(metrics[key]))
   613  	}
   614  	response.PrettyPrint(false)
   615  	response.WriteEntity(result)
   616  }
   617  
   618  // processMetricRequest retrieves a metric for the object at the requested key.
   619  func (a *HistoricalApi) processMetricRequest(key core.HistoricalKey, request *restful.Request, response *restful.Response) {
   620  	start, end, err := getStartEndTimeHistorical(request)
   621  	if err != nil {
   622  		response.WriteError(http.StatusBadRequest, err)
   623  		return
   624  	}
   625  	labels, err := getLabels(request)
   626  	if err != nil {
   627  		response.WriteError(http.StatusBadRequest, err)
   628  		return
   629  	}
   630  	metricName := request.PathParameter("metric-name")
   631  	convertedMetricName := convertMetricName(metricName)
   632  
   633  	var metrics map[core.HistoricalKey][]core.TimestampedMetricValue
   634  	if labels != nil {
   635  		metrics, err = a.historicalSource.GetLabeledMetric(convertedMetricName, labels, []core.HistoricalKey{key}, start, end)
   636  	} else {
   637  		metrics, err = a.historicalSource.GetMetric(convertedMetricName, []core.HistoricalKey{key}, start, end)
   638  	}
   639  	if err != nil {
   640  		response.WriteError(http.StatusInternalServerError, err)
   641  		return
   642  	}
   643  
   644  	converted := exportTimestampedMetricValue(metrics[key])
   645  	response.WriteEntity(converted)
   646  }
   647  
   648  // processMetricNamesRequest retrieves the available metrics for the object at the specified key.
   649  func (a *HistoricalApi) processMetricNamesRequest(key core.HistoricalKey, response *restful.Response) {
   650  	if resp, err := a.historicalSource.GetMetricNames(key); err != nil {
   651  		response.WriteError(http.StatusInternalServerError, err)
   652  	} else {
   653  		response.WriteEntity(resp)
   654  	}
   655  }
   656  
   657  // processAggregationRequest retrieves one or more aggregations (across time) of a metric for the object specified at the given key.
   658  func (a *HistoricalApi) processAggregationRequest(key core.HistoricalKey, request *restful.Request, response *restful.Response) {
   659  	start, end, err := getStartEndTimeHistorical(request)
   660  	if err != nil {
   661  		response.WriteError(http.StatusBadRequest, err)
   662  		return
   663  	}
   664  	bucketSize, err := getBucketSize(request)
   665  	if err != nil {
   666  		response.WriteError(http.StatusBadRequest, err)
   667  		return
   668  	}
   669  	aggregations, err := getAggregations(request)
   670  	if err != nil {
   671  		response.WriteError(http.StatusBadRequest, err)
   672  		return
   673  	}
   674  	labels, err := getLabels(request)
   675  	if err != nil {
   676  		response.WriteError(http.StatusBadRequest, err)
   677  		return
   678  	}
   679  
   680  	metricName := request.PathParameter("metric-name")
   681  	convertedMetricName := convertMetricName(metricName)
   682  	var metrics map[core.HistoricalKey][]core.TimestampedAggregationValue
   683  	if labels != nil {
   684  		metrics, err = a.historicalSource.GetLabeledAggregation(convertedMetricName, labels, aggregations, []core.HistoricalKey{key}, start, end, bucketSize)
   685  	} else {
   686  		metrics, err = a.historicalSource.GetAggregation(convertedMetricName, aggregations, []core.HistoricalKey{key}, start, end, bucketSize)
   687  	}
   688  	if err != nil {
   689  		response.WriteError(http.StatusInternalServerError, err)
   690  		return
   691  	}
   692  
   693  	converted := exportTimestampedAggregationValue(metrics[key])
   694  	response.WriteEntity(converted)
   695  }
   696  
   697  // getBucketSize parses the bucket size specifier into a
   698  func getBucketSize(request *restful.Request) (time.Duration, error) {
   699  	rawSize := request.QueryParameter("bucket")
   700  	if rawSize == "" {
   701  		return 0, nil
   702  	}
   703  
   704  	if len(rawSize) < 2 {
   705  		return 0, fmt.Errorf("unable to parse bucket size: %q is too short to be a duration", rawSize)
   706  	}
   707  	var multiplier time.Duration
   708  	var num string
   709  
   710  	switch rawSize[len(rawSize)-1] {
   711  	case 's':
   712  		// could be s or ms
   713  		if len(rawSize) < 3 || rawSize[len(rawSize)-2] != 'm' {
   714  			multiplier = time.Second
   715  			num = rawSize[:len(rawSize)-1]
   716  		} else {
   717  			multiplier = time.Millisecond
   718  			num = rawSize[:len(rawSize)-2]
   719  		}
   720  	case 'h':
   721  		multiplier = time.Hour
   722  		num = rawSize[:len(rawSize)-1]
   723  	case 'd':
   724  		multiplier = 24 * time.Hour
   725  		num = rawSize[:len(rawSize)-1]
   726  	case 'm':
   727  		multiplier = time.Minute
   728  		num = rawSize[:len(rawSize)-1]
   729  	default:
   730  		return 0, fmt.Errorf("unable to parse bucket size: %q has no known duration suffix", rawSize)
   731  	}
   732  
   733  	parsedNum, err := strconv.ParseUint(num, 10, 64)
   734  	if err != nil {
   735  		return 0, err
   736  	}
   737  
   738  	return time.Duration(parsedNum) * multiplier, nil
   739  }
   740  
   741  // getAggregations extracts and validates the list of requested aggregations
   742  func getAggregations(request *restful.Request) ([]core.AggregationType, error) {
   743  	aggregationsRaw := strings.Split(request.PathParameter("aggregations"), ",")
   744  	if len(aggregationsRaw) == 0 {
   745  		return nil, fmt.Errorf("No aggregations specified")
   746  	}
   747  
   748  	aggregations := make([]core.AggregationType, len(aggregationsRaw))
   749  
   750  	for ind, aggNameRaw := range aggregationsRaw {
   751  		aggName := core.AggregationType(aggNameRaw)
   752  		if _, ok := core.AllAggregations[aggName]; !ok {
   753  			return nil, fmt.Errorf("Unknown aggregation %q", aggName)
   754  		}
   755  		aggregations[ind] = aggName
   756  	}
   757  
   758  	return aggregations, nil
   759  }
   760  
   761  // exportMetricValue converts a core.MetricValue into an API MetricValue
   762  func exportMetricValue(value *core.MetricValue) *types.MetricValue {
   763  	if value == nil {
   764  		return nil
   765  	}
   766  
   767  	if value.ValueType == core.ValueInt64 {
   768  		return &types.MetricValue{
   769  			IntValue: &value.IntValue,
   770  		}
   771  	} else {
   772  		floatVal := float64(value.FloatValue)
   773  		return &types.MetricValue{
   774  			FloatValue: &floatVal,
   775  		}
   776  	}
   777  }
   778  
   779  // extractMetricValue checks to see if the given metric was present in the results, and if so,
   780  // returns it in API form
   781  func extractMetricValue(aggregations *core.AggregationValue, aggName core.AggregationType) *types.MetricValue {
   782  	if inputVal, ok := aggregations.Aggregations[aggName]; ok {
   783  		return exportMetricValue(&inputVal)
   784  	} else {
   785  		return nil
   786  	}
   787  }
   788  
   789  // exportTimestampedAggregationValue converts a core.TimestampedAggregationValue into an API MetricAggregationResult
   790  func exportTimestampedAggregationValue(values []core.TimestampedAggregationValue) types.MetricAggregationResult {
   791  	result := types.MetricAggregationResult{
   792  		Buckets:    make([]types.MetricAggregationBucket, 0, len(values)),
   793  		BucketSize: 0,
   794  	}
   795  	for _, value := range values {
   796  		// just use the largest bucket size, since all bucket sizes should be uniform
   797  		// (except for the last one, which may be smaller)
   798  		if result.BucketSize < value.BucketSize {
   799  			result.BucketSize = value.BucketSize
   800  		}
   801  
   802  		bucket := types.MetricAggregationBucket{
   803  			Timestamp: value.Timestamp,
   804  
   805  			Count: value.Count,
   806  
   807  			Average: extractMetricValue(&value.AggregationValue, core.AggregationTypeAverage),
   808  			Maximum: extractMetricValue(&value.AggregationValue, core.AggregationTypeMaximum),
   809  			Minimum: extractMetricValue(&value.AggregationValue, core.AggregationTypeMinimum),
   810  			Median:  extractMetricValue(&value.AggregationValue, core.AggregationTypeMedian),
   811  
   812  			Percentiles: make(map[string]types.MetricValue, 3),
   813  		}
   814  
   815  		if val, ok := value.Aggregations[core.AggregationTypePercentile50]; ok {
   816  			bucket.Percentiles["50"] = *exportMetricValue(&val)
   817  		}
   818  		if val, ok := value.Aggregations[core.AggregationTypePercentile95]; ok {
   819  			bucket.Percentiles["95"] = *exportMetricValue(&val)
   820  		}
   821  		if val, ok := value.Aggregations[core.AggregationTypePercentile99]; ok {
   822  			bucket.Percentiles["99"] = *exportMetricValue(&val)
   823  		}
   824  
   825  		result.Buckets = append(result.Buckets, bucket)
   826  	}
   827  	return result
   828  }
   829  
   830  // getStartEndTimeHistorical fetches the start and end times of the request.  Unlike
   831  // getStartEndTime, this function returns an error if the start time is not passed
   832  // (or is zero, since many things in go use time.Time{} as an empty value) --
   833  // different sinks have different defaults, and certain sinks have issues if an actual
   834  // time of zero (i.e. the epoch) is used (because that would be too many data points
   835  // to consider in certain cases).  Require applications to pass an explicit start time
   836  // that they can deal with.
   837  func getStartEndTimeHistorical(request *restful.Request) (time.Time, time.Time, error) {
   838  	start, end, err := getStartEndTime(request)
   839  	if err != nil {
   840  		return start, end, err
   841  	}
   842  
   843  	if start.IsZero() {
   844  		return start, end, fmt.Errorf("no start time (or a start time of zero) provided")
   845  	}
   846  
   847  	return start, end, err
   848  }