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 }