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