github.com/timstclair/heapster@v0.20.0-alpha1/metrics/api/v1/model_handlers.go (about)

     1  // Copyright 2015 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  	"errors"
    19  	"fmt"
    20  	"net/http"
    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/sinks/metric"
    29  	"k8s.io/heapster/metrics/util/metrics"
    30  )
    31  
    32  // errModelNotActivated is the error that is returned by the API handlers
    33  // when manager.model has not been initialized.
    34  var errModelNotActivated = errors.New("the model is not activated")
    35  
    36  var metricNamesConversion = map[string]string{
    37  	"cpu-usage":      "cpu/usage_rate",
    38  	"cpu-limit":      "cpu/limit",
    39  	"memory-limit":   "memory/limit",
    40  	"memory-usage":   "memory/usage",
    41  	"memory-working": "memory/working_set",
    42  }
    43  
    44  // RegisterModel registers the Model API endpoints.
    45  // All endpoints that end with a {metric-name} also receive a start time query parameter.
    46  // The start and end times should be specified as a string, formatted according to RFC 3339.
    47  func (a *Api) RegisterModel(container *restful.Container) {
    48  	ws := new(restful.WebService)
    49  	ws.Path("/api/v1/model").
    50  		Doc("Root endpoint of the stats model").
    51  		Consumes("*/*").
    52  		Produces(restful.MIME_JSON)
    53  
    54  	// The /metrics/ endpoint returns a list of all available metrics for the Cluster entity of the model.
    55  	ws.Route(ws.GET("/metrics/").
    56  		To(metrics.InstrumentRouteFunc("availableClusterMetrics", a.availableClusterMetrics)).
    57  		Doc("Get a list of all available metrics for the Cluster entity").
    58  		Operation("availableClusterMetrics"))
    59  
    60  	// The /metrics/{metric-name} endpoint exposes an aggregated metric for the Cluster entity of the model.
    61  	ws.Route(ws.GET("/metrics/{metric-name}").
    62  		To(metrics.InstrumentRouteFunc("clusterMetrics", a.clusterMetrics)).
    63  		Doc("Export an aggregated cluster-level metric").
    64  		Operation("clusterMetrics").
    65  		Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")).
    66  		Param(ws.QueryParameter("start", "Start time for requested metric").DataType("string")).
    67  		Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")).
    68  		Writes(types.MetricResult{}))
    69  
    70  	// The /nodes/{node-name}/metrics endpoint returns a list of all nodes with some metrics.
    71  	ws.Route(ws.GET("/nodes/").
    72  		To(metrics.InstrumentRouteFunc("nodeList", a.nodeList)).
    73  		Doc("Get a list of all nodes that have some current metrics").
    74  		Operation("nodeList"))
    75  
    76  	// The /nodes/{node-name}/metrics endpoint returns a list of all available metrics for a Node entity.
    77  	ws.Route(ws.GET("/nodes/{node-name}/metrics/").
    78  		To(metrics.InstrumentRouteFunc("availableNodeMetrics", a.availableNodeMetrics)).
    79  		Doc("Get a list of all available metrics for a Node entity").
    80  		Operation("availableNodeMetrics").
    81  		Param(ws.PathParameter("node-name", "The name of the node to lookup").DataType("string")))
    82  
    83  	// The /nodes/{node-name}/metrics/{metric-name} endpoint exposes a metric for a Node entity of the model.
    84  	// The {node-name} parameter is the hostname of a specific node.
    85  	ws.Route(ws.GET("/nodes/{node-name}/metrics/{metric-name}").
    86  		To(metrics.InstrumentRouteFunc("nodeMetrics", a.nodeMetrics)).
    87  		Doc("Export a node-level metric").
    88  		Operation("nodeMetrics").
    89  		Param(ws.PathParameter("node-name", "The name of the node to lookup").DataType("string")).
    90  		Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")).
    91  		Param(ws.QueryParameter("start", "Start time for requested metric").DataType("string")).
    92  		Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")).
    93  		Writes(types.MetricResult{}))
    94  
    95  	if a.runningInKubernetes {
    96  
    97  		ws.Route(ws.GET("/namespaces/").
    98  			To(metrics.InstrumentRouteFunc("namespaceList", a.namespaceList)).
    99  			Doc("Get a list of all namespaces that have some current metrics").
   100  			Operation("namespaceList"))
   101  
   102  		// The /namespaces/{namespace-name}/metrics endpoint returns a list of all available metrics for a Namespace entity.
   103  		ws.Route(ws.GET("/namespaces/{namespace-name}/metrics").
   104  			To(metrics.InstrumentRouteFunc("availableNamespaceMetrics", a.availableNamespaceMetrics)).
   105  			Doc("Get a list of all available metrics for a Namespace entity").
   106  			Operation("availableNamespaceMetrics").
   107  			Param(ws.PathParameter("namespace-name", "The name of the namespace to lookup").DataType("string")))
   108  
   109  		// The /namespaces/{namespace-name}/metrics/{metric-name} endpoint exposes an aggregated metrics
   110  		// for a Namespace entity of the model.
   111  		ws.Route(ws.GET("/namespaces/{namespace-name}/metrics/{metric-name}").
   112  			To(metrics.InstrumentRouteFunc("namespaceMetrics", a.namespaceMetrics)).
   113  			Doc("Export an aggregated namespace-level metric").
   114  			Operation("namespaceMetrics").
   115  			Param(ws.PathParameter("namespace-name", "The name of the namespace to lookup").DataType("string")).
   116  			Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")).
   117  			Param(ws.QueryParameter("start", "Start time for requested metrics").DataType("string")).
   118  			Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")).
   119  			Writes(types.MetricResult{}))
   120  
   121  		ws.Route(ws.GET("/namespaces/{namespace-name}/pods/").
   122  			To(metrics.InstrumentRouteFunc("namespacePodList", a.namespacePodList)).
   123  			Doc("Get a list of pods from the given namespace that have some metrics").
   124  			Operation("namespacePodList").
   125  			Param(ws.PathParameter("namespace-name", "The name of the namespace to lookup").DataType("string")))
   126  
   127  		// The /namespaces/{namespace-name}/pods/{pod-name}/metrics endpoint returns a list of all available metrics for a Pod entity.
   128  		ws.Route(ws.GET("/namespaces/{namespace-name}/pods/{pod-name}/metrics").
   129  			To(metrics.InstrumentRouteFunc("availablePodMetrics", a.availablePodMetrics)).
   130  			Doc("Get a list of all available metrics for a Pod entity").
   131  			Operation("availablePodMetrics").
   132  			Param(ws.PathParameter("namespace-name", "The name of the namespace to lookup").DataType("string")).
   133  			Param(ws.PathParameter("pod-name", "The name of the pod to lookup").DataType("string")))
   134  
   135  		// The /namespaces/{namespace-name}/pods/{pod-name}/metrics/{metric-name} endpoint exposes
   136  		// an aggregated metric for a Pod entity of the model.
   137  		ws.Route(ws.GET("/namespaces/{namespace-name}/pods/{pod-name}/metrics/{metric-name}").
   138  			To(metrics.InstrumentRouteFunc("podMetrics", a.podMetrics)).
   139  			Doc("Export an aggregated pod-level metric").
   140  			Operation("podMetrics").
   141  			Param(ws.PathParameter("namespace-name", "The name of the namespace to lookup").DataType("string")).
   142  			Param(ws.PathParameter("pod-name", "The name of the pod to lookup").DataType("string")).
   143  			Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")).
   144  			Param(ws.QueryParameter("start", "Start time for requested metrics").DataType("string")).
   145  			Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")).
   146  			Writes(types.MetricResult{}))
   147  
   148  		// The /namespaces/{namespace-name}/pods/{pod-name}/containers/metrics/{container-name}/metrics endpoint
   149  		// returns a list of all available metrics for a Pod Container entity.
   150  		ws.Route(ws.GET("/namespaces/{namespace-name}/pods/{pod-name}/containers/{container-name}/metrics").
   151  			To(metrics.InstrumentRouteFunc("availableContainerMetrics", a.availablePodContainerMetrics)).
   152  			Doc("Get a list of all available metrics for a Pod entity").
   153  			Operation("availableContainerMetrics").
   154  			Param(ws.PathParameter("namespace-name", "The name of the namespace to lookup").DataType("string")).
   155  			Param(ws.PathParameter("pod-name", "The name of the pod to lookup").DataType("string")).
   156  			Param(ws.PathParameter("container-name", "The name of the namespace to use").DataType("string")))
   157  
   158  		// The /namespaces/{namespace-name}/pods/{pod-name}/containers/{container-name}/metrics/{metric-name} endpoint exposes
   159  		// a metric for a Container entity of the model.
   160  		ws.Route(ws.GET("/namespaces/{namespace-name}/pods/{pod-name}/containers/{container-name}/metrics/{metric-name}").
   161  			To(metrics.InstrumentRouteFunc("podContainerMetrics", a.podContainerMetrics)).
   162  			Doc("Export an aggregated metric for a Pod Container").
   163  			Operation("podContainerMetrics").
   164  			Param(ws.PathParameter("namespace-name", "The name of the namespace to use").DataType("string")).
   165  			Param(ws.PathParameter("pod-name", "The name of the pod to use").DataType("string")).
   166  			Param(ws.PathParameter("container-name", "The name of the namespace to use").DataType("string")).
   167  			Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")).
   168  			Param(ws.QueryParameter("start", "Start time for requested metrics").DataType("string")).
   169  			Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")).
   170  			Writes(types.MetricResult{}))
   171  	}
   172  
   173  	ws.Route(ws.GET("/nodes/{node-name}/freecontainers/").
   174  		To(metrics.InstrumentRouteFunc("systemContainerList", a.nodeSystemContainerList)).
   175  		Doc("Get a list of all non-pod containers with some metrics").
   176  		Operation("systemContainerList").
   177  		Param(ws.PathParameter("node-name", "The name of the namespace to lookup").DataType("string")))
   178  
   179  	// The /nodes/{node-name}/freecontainers/{container-name}/metrics endpoint
   180  	// returns a list of all available metrics for a Free Container entity.
   181  	ws.Route(ws.GET("/nodes/{node-name}/freecontainers/{container-name}/metrics").
   182  		To(metrics.InstrumentRouteFunc("availableMetrics", a.availableFreeContainerMetrics)).
   183  		Doc("Get a list of all available metrics for a free Container entity").
   184  		Operation("availableMetrics").
   185  		Param(ws.PathParameter("node-name", "The name of the namespace to lookup").DataType("string")).
   186  		Param(ws.PathParameter("container-name", "The name of the namespace to use").DataType("string")))
   187  
   188  	// The /nodes/{node-name}/freecontainers/{container-name}/metrics/{metric-name} endpoint exposes
   189  	// a metric for a free Container entity of the model.
   190  	ws.Route(ws.GET("/nodes/{node-name}/freecontainers/{container-name}/metrics/{metric-name}").
   191  		To(metrics.InstrumentRouteFunc("freeContainerMetrics", a.freeContainerMetrics)).
   192  		Doc("Export a container-level metric for a free container").
   193  		Operation("freeContainerMetrics").
   194  		Param(ws.PathParameter("node-name", "The name of the node to use").DataType("string")).
   195  		Param(ws.PathParameter("container-name", "The name of the container to use").DataType("string")).
   196  		Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")).
   197  		Param(ws.QueryParameter("start", "Start time for requested metrics").DataType("string")).
   198  		Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")).
   199  		Writes(types.MetricResult{}))
   200  
   201  	if a.runningInKubernetes {
   202  		// The /namespaces/{namespace-name}/pod-list/{pod-list}/metrics/{metric-name} endpoint exposes
   203  		// metrics for a list od pods of the model.
   204  		ws.Route(ws.GET("/namespaces/{namespace-name}/pod-list/{pod-list}/metrics/{metric-name}").
   205  			To(metrics.InstrumentRouteFunc("podListMetric", a.podListMetrics)).
   206  			Doc("Export a metric for all pods from the given list").
   207  			Operation("podListMetric").
   208  			Param(ws.PathParameter("namespace-name", "The name of the namespace to lookup").DataType("string")).
   209  			Param(ws.PathParameter("pod-list", "Comma separated list of pod names to lookup").DataType("string")).
   210  			Param(ws.PathParameter("metric-name", "The name of the requested metric").DataType("string")).
   211  			Param(ws.QueryParameter("start", "Start time for requested metrics").DataType("string")).
   212  			Param(ws.QueryParameter("end", "End time for requested metric").DataType("string")).
   213  			Writes(types.MetricResult{}))
   214  	}
   215  
   216  	ws.Route(ws.GET("/debug/allkeys").
   217  		To(metrics.InstrumentRouteFunc("debugAllKeys", a.allKeys)).
   218  		Doc("Get keys of all metric sets available").
   219  		Operation("debugAllKeys"))
   220  
   221  	container.Add(ws)
   222  }
   223  
   224  // availableMetrics returns a list of available cluster metric names.
   225  func (a *Api) availableClusterMetrics(request *restful.Request, response *restful.Response) {
   226  	a.processMetricNamesRequest(core.ClusterKey(), response)
   227  }
   228  
   229  // availableMetrics returns a list of available node metric names.
   230  func (a *Api) availableNodeMetrics(request *restful.Request, response *restful.Response) {
   231  	a.processMetricNamesRequest(core.NodeKey(request.PathParameter("node-name")), response)
   232  }
   233  
   234  // availableMetrics returns a list of available namespace metric names.
   235  func (a *Api) availableNamespaceMetrics(request *restful.Request, response *restful.Response) {
   236  	a.processMetricNamesRequest(core.NamespaceKey(request.PathParameter("namespace-name")), response)
   237  }
   238  
   239  // availableMetrics returns a list of available pod metric names.
   240  func (a *Api) availablePodMetrics(request *restful.Request, response *restful.Response) {
   241  	a.processMetricNamesRequest(
   242  		core.PodKey(request.PathParameter("namespace-name"),
   243  			request.PathParameter("pod-name")), response)
   244  }
   245  
   246  // availableMetrics returns a list of available pod metric names.
   247  func (a *Api) availablePodContainerMetrics(request *restful.Request, response *restful.Response) {
   248  	a.processMetricNamesRequest(
   249  		core.PodContainerKey(request.PathParameter("namespace-name"),
   250  			request.PathParameter("pod-name"),
   251  			request.PathParameter("container-name"),
   252  		), response)
   253  }
   254  
   255  // availableMetrics returns a list of available pod metric names.
   256  func (a *Api) availableFreeContainerMetrics(request *restful.Request, response *restful.Response) {
   257  	a.processMetricNamesRequest(
   258  		core.NodeContainerKey(request.PathParameter("node-name"),
   259  			request.PathParameter("container-name"),
   260  		), response)
   261  }
   262  
   263  func (a *Api) nodeList(request *restful.Request, response *restful.Response) {
   264  	response.WriteEntity(a.metricSink.GetNodes())
   265  }
   266  
   267  func (a *Api) namespaceList(request *restful.Request, response *restful.Response) {
   268  	response.WriteEntity(a.metricSink.GetNamespaces())
   269  }
   270  
   271  func (a *Api) namespacePodList(request *restful.Request, response *restful.Response) {
   272  	response.WriteEntity(a.metricSink.GetPodsFromNamespace(request.PathParameter("namespace-name")))
   273  }
   274  
   275  func (a *Api) nodeSystemContainerList(request *restful.Request, response *restful.Response) {
   276  	response.WriteEntity(a.metricSink.GetSystemContainersFromNode(request.PathParameter("node-name")))
   277  }
   278  
   279  func (a *Api) allKeys(request *restful.Request, response *restful.Response) {
   280  	response.WriteEntity(a.metricSink.GetMetricSetKeys())
   281  }
   282  
   283  // clusterMetrics returns a metric timeseries for a metric of the Cluster entity.
   284  func (a *Api) clusterMetrics(request *restful.Request, response *restful.Response) {
   285  	a.processMetricRequest(core.ClusterKey(), request, response)
   286  }
   287  
   288  // nodeMetrics returns a metric timeseries for a metric of the Node entity.
   289  func (a *Api) nodeMetrics(request *restful.Request, response *restful.Response) {
   290  	a.processMetricRequest(core.NodeKey(request.PathParameter("node-name")),
   291  		request, response)
   292  }
   293  
   294  // namespaceMetrics returns a metric timeseries for a metric of the Namespace entity.
   295  func (a *Api) namespaceMetrics(request *restful.Request, response *restful.Response) {
   296  	a.processMetricRequest(core.NamespaceKey(request.PathParameter("namespace-name")),
   297  		request, response)
   298  }
   299  
   300  // podMetrics returns a metric timeseries for a metric of the Pod entity.
   301  func (a *Api) podMetrics(request *restful.Request, response *restful.Response) {
   302  	a.processMetricRequest(
   303  		core.PodKey(request.PathParameter("namespace-name"),
   304  			request.PathParameter("pod-name")),
   305  		request, response)
   306  }
   307  
   308  func (a *Api) podListMetrics(request *restful.Request, response *restful.Response) {
   309  	start, end, err := getStartEndTime(request)
   310  	if err != nil {
   311  		response.WriteError(http.StatusBadRequest, err)
   312  		return
   313  	}
   314  	ns := request.PathParameter("namespace-name")
   315  	keys := []string{}
   316  	for _, podName := range strings.Split(request.PathParameter("pod-list"), ",") {
   317  		keys = append(keys, core.PodKey(ns, podName))
   318  	}
   319  	metricName := getMetricName(request)
   320  	if metricName == "" {
   321  		response.WriteError(http.StatusBadRequest, fmt.Errorf("Metric not supported: %v", request.PathParameter("metric-name")))
   322  		return
   323  	}
   324  	metrics := a.metricSink.GetMetric(metricName, keys, start, end)
   325  	result := types.MetricResultList{
   326  		Items: make([]types.MetricResult, 0, len(keys)),
   327  	}
   328  	for _, key := range keys {
   329  		result.Items = append(result.Items, exportTimestampedMetricValue(metrics[key]))
   330  	}
   331  	response.WriteEntity(result)
   332  }
   333  
   334  // podContainerMetrics returns a metric timeseries for a metric of a Pod Container entity.
   335  // podContainerMetrics uses the namespace-name/pod-name/container-name path.
   336  func (a *Api) podContainerMetrics(request *restful.Request, response *restful.Response) {
   337  	a.processMetricRequest(
   338  		core.PodContainerKey(request.PathParameter("namespace-name"),
   339  			request.PathParameter("pod-name"),
   340  			request.PathParameter("container-name"),
   341  		),
   342  		request, response)
   343  }
   344  
   345  // freeContainerMetrics returns a metric timeseries for a metric of the Container entity.
   346  // freeContainerMetrics addresses only free containers, by using the node-name/container-name path.
   347  func (a *Api) freeContainerMetrics(request *restful.Request, response *restful.Response) {
   348  	a.processMetricRequest(
   349  		core.NodeContainerKey(request.PathParameter("node-name"),
   350  			request.PathParameter("container-name"),
   351  		),
   352  		request, response)
   353  }
   354  
   355  // parseRequestParam parses a time.Time from a named QueryParam, using the RFC3339 format.
   356  func parseTimeParam(queryParam string, defaultValue time.Time) (time.Time, error) {
   357  	if queryParam != "" {
   358  		reqStamp, err := time.Parse(time.RFC3339, queryParam)
   359  		if err != nil {
   360  			return time.Time{}, fmt.Errorf("timestamp argument cannot be parsed: %s", err)
   361  		}
   362  		return reqStamp, nil
   363  	}
   364  	return defaultValue, nil
   365  }
   366  
   367  func (a *Api) processMetricRequest(key string, request *restful.Request, response *restful.Response) {
   368  	start, end, err := getStartEndTime(request)
   369  	if err != nil {
   370  		response.WriteError(http.StatusBadRequest, err)
   371  		return
   372  	}
   373  	metricName := getMetricName(request)
   374  	if metricName == "" {
   375  		response.WriteError(http.StatusBadRequest, fmt.Errorf("Metric not supported: %v", request.PathParameter("metric-name")))
   376  		return
   377  	}
   378  	metrics := a.metricSink.GetMetric(metricName, []string{key}, start, end)
   379  	converted := exportTimestampedMetricValue(metrics[key])
   380  	response.WriteEntity(converted)
   381  }
   382  
   383  func (a *Api) processMetricNamesRequest(key string, response *restful.Response) {
   384  	metricNames := a.metricSink.GetMetricNames(key)
   385  	response.WriteEntity(metricNames)
   386  }
   387  
   388  func getMetricName(request *restful.Request) string {
   389  	param := request.PathParameter("metric-name")
   390  	return metricNamesConversion[param]
   391  }
   392  
   393  func getStartEndTime(request *restful.Request) (time.Time, time.Time, error) {
   394  	start, err := parseTimeParam(request.QueryParameter("start"), time.Time{})
   395  	if err != nil {
   396  		return time.Time{}, time.Time{}, err
   397  	}
   398  	end, err := parseTimeParam(request.QueryParameter("end"), time.Now())
   399  	if err != nil {
   400  		return time.Time{}, time.Time{}, err
   401  	}
   402  	return start, end, nil
   403  }
   404  
   405  func exportTimestampedMetricValue(values []metricsink.TimestampedMetricValue) types.MetricResult {
   406  	result := types.MetricResult{
   407  		Metrics: make([]types.MetricPoint, 0, len(values)),
   408  	}
   409  	for _, value := range values {
   410  		if result.LatestTimestamp.Before(value.Timestamp) {
   411  			result.LatestTimestamp = value.Timestamp
   412  		}
   413  		// TODO: clean up types in model api
   414  		var intValue int64
   415  		if value.ValueType == core.ValueInt64 {
   416  			intValue = value.IntValue
   417  		} else {
   418  			intValue = int64(value.FloatValue)
   419  		}
   420  
   421  		result.Metrics = append(result.Metrics, types.MetricPoint{
   422  			Timestamp: value.Timestamp,
   423  			Value:     uint64(intValue),
   424  		})
   425  	}
   426  	return result
   427  }