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 }