Add metrics/dimensions/names/values api for grafana templating optimization

This endpoint will return all the dimension values for a given
dimension name and optional metric name (for the scoped project).
This will allow grafana templating to use this (much faster call)
instead of making a full metric-list call and then parsing out
dimensions.

Change-Id: Ia1e2487fe0f96dee03d97d865c58a3728b43f448
Implements: blueprint dimensions-api
This commit is contained in:
Brad Klein 2016-07-01 09:13:18 -06:00
parent 1bea52fb02
commit a317508031
24 changed files with 1164 additions and 86 deletions

View File

@ -1,7 +1,9 @@
Andreas Jaeger <aj@suse.com> Andreas Jaeger <aj@suse.com>
Angelo Mendonca <angelomendonca@gmail.com> Angelo Mendonca <angelomendonca@gmail.com>
Ben Motz <bmotz@cray.com> Ben Motz <bmotz@cray.com>
Bertrand Lallau <bertrand.lallau@thalesgroup.com>
Brad Klein <bradley.klein@twcable.com> Brad Klein <bradley.klein@twcable.com>
Clenimar Filemon <clenimar.filemon@gmail.com>
Craig Bryant <craig.bryant@hp.com> Craig Bryant <craig.bryant@hp.com>
David C Kennedy <david.c.kennedy@hp.com> David C Kennedy <david.c.kennedy@hp.com>
Deklan Dieterly <deklan.dieterly@hp.com> Deklan Dieterly <deklan.dieterly@hp.com>

View File

@ -21,6 +21,7 @@ alarms = monasca_api.v2.reference.alarms:Alarms
alarms_count = monasca_api.v2.reference.alarms:AlarmsCount alarms_count = monasca_api.v2.reference.alarms:AlarmsCount
alarms_state_history = monasca_api.v2.reference.alarms:AlarmsStateHistory alarms_state_history = monasca_api.v2.reference.alarms:AlarmsStateHistory
notification_methods = monasca_api.v2.reference.notifications:Notifications notification_methods = monasca_api.v2.reference.notifications:Notifications
dimension_values = monasca_api.v2.reference.metrics:DimensionValues
[security] [security]
# The roles that are allowed full access to the API. # The roles that are allowed full access to the API.

View File

@ -16,11 +16,14 @@ Document Version: v2.0
- [Measurement](#measurement) - [Measurement](#measurement)
- [Value Meta](#value-meta) - [Value Meta](#value-meta)
- [Alarm Definitions and Alarms](#alarm-definitions-and-alarms) - [Alarm Definitions and Alarms](#alarm-definitions-and-alarms)
- [Deterministic or non-deterministic alarms](#deterministic-or-non-deterministic-alarms)
- [Alarm Definition Expressions](#alarm-definition-expressions) - [Alarm Definition Expressions](#alarm-definition-expressions)
- [Syntax](#syntax) - [Syntax](#syntax)
- [Simple Example](#simple-example) - [Simple Example](#simple-example)
- [More Complex Example](#more-complex-example) - [More Complex Example](#more-complex-example)
- [Compound alarm example](#compound-alarm-example) - [Compound alarm example](#compound-alarm-example)
- [Deterministic alarm example](#deterministic-alarm-example)
- [Non-deterministic alarm with deterministic sub expressions](#non-deterministic-alarm-with-deterministic-sub-expressions)
- [Changing Alarm Definitions](#changing-alarm-definitions) - [Changing Alarm Definitions](#changing-alarm-definitions)
- [Notification Methods](#notification-methods) - [Notification Methods](#notification-methods)
- [Common Request Headers](#common-request-headers) - [Common Request Headers](#common-request-headers)
@ -79,9 +82,8 @@ Document Version: v2.0
- [Status Code](#status-code-1) - [Status Code](#status-code-1)
- [Response Body](#response-body-3) - [Response Body](#response-body-3)
- [Response Examples](#response-examples-2) - [Response Examples](#response-examples-2)
- [Measurements](#measurements) - [List dimension values](#list-dimension-values)
- [List measurements](#list-measurements) - [GET /v2.0/metrics/dimensions/names/values](#get-v20metricsdimensionsnamesvalues)
- [GET /v2.0/metrics/measurements](#get-v20metricsmeasurements)
- [Headers](#headers-4) - [Headers](#headers-4)
- [Path Parameters](#path-parameters-4) - [Path Parameters](#path-parameters-4)
- [Query Parameters](#query-parameters-4) - [Query Parameters](#query-parameters-4)
@ -91,9 +93,9 @@ Document Version: v2.0
- [Status Code](#status-code-2) - [Status Code](#status-code-2)
- [Response Body](#response-body-4) - [Response Body](#response-body-4)
- [Response Examples](#response-examples-3) - [Response Examples](#response-examples-3)
- [Metric Names](#metric-names) - [Measurements](#measurements)
- [List names](#list-names) - [List measurements](#list-measurements)
- [GET /v2.0/metrics/names](#get-v20metricsnames) - [GET /v2.0/metrics/measurements](#get-v20metricsmeasurements)
- [Headers](#headers-5) - [Headers](#headers-5)
- [Path Parameters](#path-parameters-5) - [Path Parameters](#path-parameters-5)
- [Query Parameters](#query-parameters-5) - [Query Parameters](#query-parameters-5)
@ -103,9 +105,9 @@ Document Version: v2.0
- [Status Code](#status-code-3) - [Status Code](#status-code-3)
- [Response Body](#response-body-5) - [Response Body](#response-body-5)
- [Response Examples](#response-examples-4) - [Response Examples](#response-examples-4)
- [Statistics](#statistics) - [Metric Names](#metric-names)
- [List statistics](#list-statistics) - [List names](#list-names)
- [GET /v2.0/metrics/statistics](#get-v20metricsstatistics) - [GET /v2.0/metrics/names](#get-v20metricsnames)
- [Headers](#headers-6) - [Headers](#headers-6)
- [Path Parameters](#path-parameters-6) - [Path Parameters](#path-parameters-6)
- [Query Parameters](#query-parameters-6) - [Query Parameters](#query-parameters-6)
@ -115,9 +117,9 @@ Document Version: v2.0
- [Status Code](#status-code-4) - [Status Code](#status-code-4)
- [Response Body](#response-body-6) - [Response Body](#response-body-6)
- [Response Examples](#response-examples-5) - [Response Examples](#response-examples-5)
- [Notification Methods](#notification-methods-1) - [Statistics](#statistics)
- [Create Notification Method](#create-notification-method) - [List statistics](#list-statistics)
- [POST /v2.0/notification-methods](#post-v20notification-methods) - [GET /v2.0/metrics/statistics](#get-v20metricsstatistics)
- [Headers](#headers-7) - [Headers](#headers-7)
- [Path Parameters](#path-parameters-7) - [Path Parameters](#path-parameters-7)
- [Query Parameters](#query-parameters-7) - [Query Parameters](#query-parameters-7)
@ -127,8 +129,9 @@ Document Version: v2.0
- [Status Code](#status-code-5) - [Status Code](#status-code-5)
- [Response Body](#response-body-7) - [Response Body](#response-body-7)
- [Response Examples](#response-examples-6) - [Response Examples](#response-examples-6)
- [List Notification Methods](#list-notification-methods) - [Notification Methods](#notification-methods-1)
- [GET /v2.0/notification-methods](#get-v20notification-methods) - [Create Notification Method](#create-notification-method)
- [POST /v2.0/notification-methods](#post-v20notification-methods)
- [Headers](#headers-8) - [Headers](#headers-8)
- [Path Parameters](#path-parameters-8) - [Path Parameters](#path-parameters-8)
- [Query Parameters](#query-parameters-8) - [Query Parameters](#query-parameters-8)
@ -138,8 +141,8 @@ Document Version: v2.0
- [Status Code](#status-code-6) - [Status Code](#status-code-6)
- [Response Body](#response-body-8) - [Response Body](#response-body-8)
- [Response Examples](#response-examples-7) - [Response Examples](#response-examples-7)
- [Get Notification Method](#get-notification-method) - [List Notification Methods](#list-notification-methods)
- [GET /v2.0/notification-methods/{notification_method_id}](#get-v20notification-methodsnotification_method_id) - [GET /v2.0/notification-methods](#get-v20notification-methods)
- [Headers](#headers-9) - [Headers](#headers-9)
- [Path Parameters](#path-parameters-9) - [Path Parameters](#path-parameters-9)
- [Query Parameters](#query-parameters-9) - [Query Parameters](#query-parameters-9)
@ -149,8 +152,8 @@ Document Version: v2.0
- [Status Code](#status-code-7) - [Status Code](#status-code-7)
- [Response Body](#response-body-9) - [Response Body](#response-body-9)
- [Response Examples](#response-examples-8) - [Response Examples](#response-examples-8)
- [Update Notification Method](#update-notification-method) - [Get Notification Method](#get-notification-method)
- [PUT /v2.0/notification-methods/{notification_method_id}](#put-v20notification-methodsnotification_method_id) - [GET /v2.0/notification-methods/{notification_method_id}](#get-v20notification-methodsnotification_method_id)
- [Headers](#headers-10) - [Headers](#headers-10)
- [Path Parameters](#path-parameters-10) - [Path Parameters](#path-parameters-10)
- [Query Parameters](#query-parameters-10) - [Query Parameters](#query-parameters-10)
@ -171,8 +174,8 @@ Document Version: v2.0
- [Status Code](#status-code-8) - [Status Code](#status-code-8)
- [Response Body](#response-body-10) - [Response Body](#response-body-10)
- [Response Examples](#response-examples-9) - [Response Examples](#response-examples-9)
- [Delete Notification Method](#delete-notification-method) - [Update Notification Method](#update-notification-method)
- [DELETE /v2.0/notification-methods/{notification_method_id}](#delete-v20notification-methodsnotification_method_id) - [PUT /v2.0/notification-methods/{notification_method_id}](#put-v20notification-methodsnotification_method_id)
- [Headers](#headers-11) - [Headers](#headers-11)
- [Path Parameters](#path-parameters-11) - [Path Parameters](#path-parameters-11)
- [Query Parameters](#query-parameters-11) - [Query Parameters](#query-parameters-11)
@ -181,9 +184,9 @@ Document Version: v2.0
- [Response](#response-11) - [Response](#response-11)
- [Status Code](#status-code-9) - [Status Code](#status-code-9)
- [Response Body](#response-body-11) - [Response Body](#response-body-11)
- [Alarm Definitions](#alarm-definitions) - [Response Examples](#response-examples-10)
- [Create Alarm Definition](#create-alarm-definition) - [Patch Notification Method](#patch-notification-method)
- [POST /v2.0/alarm-definitions](#post-v20alarm-definitions) - [PATCH /v2.0/notification-methods/{notification_method_id}](#patch-v20notification-methodsnotification_method_id)
- [Headers](#headers-12) - [Headers](#headers-12)
- [Path Parameters](#path-parameters-12) - [Path Parameters](#path-parameters-12)
- [Query Parameters](#query-parameters-12) - [Query Parameters](#query-parameters-12)
@ -192,9 +195,9 @@ Document Version: v2.0
- [Response](#response-12) - [Response](#response-12)
- [Status Code](#status-code-10) - [Status Code](#status-code-10)
- [Response Body](#response-body-12) - [Response Body](#response-body-12)
- [Response Examples](#response-examples-10) - [Response Examples](#response-examples-11)
- [List Alarm Definitions](#list-alarm-definitions) - [Delete Notification Method](#delete-notification-method)
- [GET /v2.0/alarm-definitions](#get-v20alarm-definitions) - [DELETE /v2.0/notification-methods/{notification_method_id}](#delete-v20notification-methodsnotification_method_id)
- [Headers](#headers-13) - [Headers](#headers-13)
- [Path Parameters](#path-parameters-13) - [Path Parameters](#path-parameters-13)
- [Query Parameters](#query-parameters-13) - [Query Parameters](#query-parameters-13)
@ -203,41 +206,41 @@ Document Version: v2.0
- [Response](#response-13) - [Response](#response-13)
- [Status Code](#status-code-11) - [Status Code](#status-code-11)
- [Response Body](#response-body-13) - [Response Body](#response-body-13)
- [Response Examples](#response-examples-11) - [Alarm Definitions](#alarm-definitions)
- [Get Alarm Definition](#get-alarm-definition) - [Create Alarm Definition](#create-alarm-definition)
- [GET /v2.0/alarm-definitions/{alarm_definition_id}](#get-v20alarm-definitionsalarm_definition_id) - [POST /v2.0/alarm-definitions](#post-v20alarm-definitions)
- [Headers](#headers-14) - [Headers](#headers-14)
- [Path Parameters](#path-parameters-14) - [Path Parameters](#path-parameters-14)
- [Query Parameters](#query-parameters-14) - [Query Parameters](#query-parameters-14)
- [Request Body](#request-body-14) - [Request Body](#request-body-14)
- [Request Examples](#request-examples-14)
- [Response](#response-14) - [Response](#response-14)
- [Status Code](#status-code-12) - [Status Code](#status-code-12)
- [Response Body](#response-body-14) - [Response Body](#response-body-14)
- [Response Examples](#response-examples-12) - [Response Examples](#response-examples-12)
- [Update Alarm Definition](#update-alarm-definition) - [List Alarm Definitions](#list-alarm-definitions)
- [PUT /v2.0/alarm-definitions/{alarm_definition_id}](#put-v20alarm-definitionsalarm_definition_id) - [GET /v2.0/alarm-definitions](#get-v20alarm-definitions)
- [Headers](#headers-15) - [Headers](#headers-15)
- [Path Parameters](#path-parameters-15) - [Path Parameters](#path-parameters-15)
- [Query Parameters](#query-parameters-15) - [Query Parameters](#query-parameters-15)
- [Request Body](#request-body-15) - [Request Body](#request-body-15)
- [Request Examples](#request-examples-14) - [Request Examples](#request-examples-15)
- [Response](#response-15) - [Response](#response-15)
- [Status Code](#status-code-13) - [Status Code](#status-code-13)
- [Response Body](#response-body-15) - [Response Body](#response-body-15)
- [Response Examples](#response-examples-13) - [Response Examples](#response-examples-13)
- [Patch Alarm Definition](#patch-alarm-definition) - [Get Alarm Definition](#get-alarm-definition)
- [PATCH /v2.0/alarm-definitions/{alarm_definition_id}](#patch-v20alarm-definitionsalarm_definition_id) - [GET /v2.0/alarm-definitions/{alarm_definition_id}](#get-v20alarm-definitionsalarm_definition_id)
- [Headers](#headers-16) - [Headers](#headers-16)
- [Path Parameters](#path-parameters-16) - [Path Parameters](#path-parameters-16)
- [Query Parameters](#query-parameters-16) - [Query Parameters](#query-parameters-16)
- [Request Body](#request-body-16) - [Request Body](#request-body-16)
- [Request Examples](#request-examples-15)
- [Response](#response-16) - [Response](#response-16)
- [Status Code](#status-code-14) - [Status Code](#status-code-14)
- [Response Body](#response-body-16) - [Response Body](#response-body-16)
- [Response Examples](#response-examples-14) - [Response Examples](#response-examples-14)
- [Delete Alarm Definition](#delete-alarm-definition) - [Update Alarm Definition](#update-alarm-definition)
- [DELETE /v2.0/alarm-definitions/{alarm_definition_id}](#delete-v20alarm-definitionsalarm_definition_id) - [PUT /v2.0/alarm-definitions/{alarm_definition_id}](#put-v20alarm-definitionsalarm_definition_id)
- [Headers](#headers-17) - [Headers](#headers-17)
- [Path Parameters](#path-parameters-17) - [Path Parameters](#path-parameters-17)
- [Query Parameters](#query-parameters-17) - [Query Parameters](#query-parameters-17)
@ -246,9 +249,9 @@ Document Version: v2.0
- [Response](#response-17) - [Response](#response-17)
- [Status Code](#status-code-15) - [Status Code](#status-code-15)
- [Response Body](#response-body-17) - [Response Body](#response-body-17)
- [Alarms](#alarms) - [Response Examples](#response-examples-15)
- [List Alarms](#list-alarms) - [Patch Alarm Definition](#patch-alarm-definition)
- [GET /v2.0/alarms](#get-v20alarms) - [PATCH /v2.0/alarm-definitions/{alarm_definition_id}](#patch-v20alarm-definitionsalarm_definition_id)
- [Headers](#headers-18) - [Headers](#headers-18)
- [Path Parameters](#path-parameters-18) - [Path Parameters](#path-parameters-18)
- [Query Parameters](#query-parameters-18) - [Query Parameters](#query-parameters-18)
@ -257,9 +260,9 @@ Document Version: v2.0
- [Response](#response-18) - [Response](#response-18)
- [Status Code](#status-code-16) - [Status Code](#status-code-16)
- [Response Body](#response-body-18) - [Response Body](#response-body-18)
- [Response Examples](#response-examples-15) - [Response Examples](#response-examples-16)
- [Get Alarm Counts](#get-alarm-counts) - [Delete Alarm Definition](#delete-alarm-definition)
- [GET /v2.0/alarms/count](#get-v20alarmscount) - [DELETE /v2.0/alarm-definitions/{alarm_definition_id}](#delete-v20alarm-definitionsalarm_definition_id)
- [Headers](#headers-19) - [Headers](#headers-19)
- [Path Parameters](#path-parameters-19) - [Path Parameters](#path-parameters-19)
- [Query Parameters](#query-parameters-19) - [Query Parameters](#query-parameters-19)
@ -268,19 +271,20 @@ Document Version: v2.0
- [Response](#response-19) - [Response](#response-19)
- [Status Code](#status-code-17) - [Status Code](#status-code-17)
- [Response Body](#response-body-19) - [Response Body](#response-body-19)
- [Response Example](#response-example) - [Alarms](#alarms)
- [List Alarms State History](#list-alarms-state-history) - [List Alarms](#list-alarms)
- [GET /v2.0/alarms/state-history](#get-v20alarmsstate-history) - [GET /v2.0/alarms](#get-v20alarms)
- [Headers](#headers-20) - [Headers](#headers-20)
- [Path Parameters](#path-parameters-20) - [Path Parameters](#path-parameters-20)
- [Query Parameters](#query-parameters-20) - [Query Parameters](#query-parameters-20)
- [Request Body](#request-body-20) - [Request Body](#request-body-20)
- [Request Examples](#request-examples-19)
- [Response](#response-20) - [Response](#response-20)
- [Status Code](#status-code-18) - [Status Code](#status-code-18)
- [Response Body](#response-body-20) - [Response Body](#response-body-20)
- [Response Examples](#response-examples-16) - [Response Examples](#response-examples-17)
- [Get Alarm](#get-alarm) - [List Alarms State History](#list-alarms-state-history)
- [GET /v2.0/alarms/{alarm_id}](#get-v20alarmsalarm_id) - [GET /v2.0/alarms/state-history](#get-v20alarmsstate-history)
- [Headers](#headers-21) - [Headers](#headers-21)
- [Path Parameters](#path-parameters-21) - [Path Parameters](#path-parameters-21)
- [Query Parameters](#query-parameters-21) - [Query Parameters](#query-parameters-21)
@ -288,20 +292,19 @@ Document Version: v2.0
- [Response](#response-21) - [Response](#response-21)
- [Status Code](#status-code-19) - [Status Code](#status-code-19)
- [Response Body](#response-body-21) - [Response Body](#response-body-21)
- [Response Examples](#response-examples-17) - [Response Examples](#response-examples-18)
- [Update Alarm](#update-alarm) - [Get Alarm](#get-alarm)
- [PUT /v2.0/alarms/{alarm_id}](#put-v20alarmsalarm_id) - [GET /v2.0/alarms/{alarm_id}](#get-v20alarmsalarm_id)
- [Headers](#headers-22) - [Headers](#headers-22)
- [Path Parameters](#path-parameters-22) - [Path Parameters](#path-parameters-22)
- [Query Parameters](#query-parameters-22) - [Query Parameters](#query-parameters-22)
- [Request Body](#request-body-22) - [Request Body](#request-body-22)
- [Request Examples](#request-examples-19)
- [Response](#response-22) - [Response](#response-22)
- [Status Code](#status-code-20) - [Status Code](#status-code-20)
- [Response Body](#response-body-22) - [Response Body](#response-body-22)
- [Response Examples](#response-examples-18) - [Response Examples](#response-examples-19)
- [Patch Alarm](#patch-alarm) - [Update Alarm](#update-alarm)
- [PATCH /v2.0/alarms/{alarm_id}](#patch-v20alarmsalarm_id) - [PUT /v2.0/alarms/{alarm_id}](#put-v20alarmsalarm_id)
- [Headers](#headers-23) - [Headers](#headers-23)
- [Path Parameters](#path-parameters-23) - [Path Parameters](#path-parameters-23)
- [Query Parameters](#query-parameters-23) - [Query Parameters](#query-parameters-23)
@ -310,9 +313,9 @@ Document Version: v2.0
- [Response](#response-23) - [Response](#response-23)
- [Status Code](#status-code-21) - [Status Code](#status-code-21)
- [Response Body](#response-body-23) - [Response Body](#response-body-23)
- [Response Examples](#response-examples-19) - [Response Examples](#response-examples-20)
- [Delete Alarm](#delete-alarm) - [Patch Alarm](#patch-alarm)
- [DELETE /v2.0/alarms/{alarm_id}](#delete-v20alarmsalarm_id) - [PATCH /v2.0/alarms/{alarm_id}](#patch-v20alarmsalarm_id)
- [Headers](#headers-24) - [Headers](#headers-24)
- [Path Parameters](#path-parameters-24) - [Path Parameters](#path-parameters-24)
- [Query Parameters](#query-parameters-24) - [Query Parameters](#query-parameters-24)
@ -321,17 +324,28 @@ Document Version: v2.0
- [Response](#response-24) - [Response](#response-24)
- [Status Code](#status-code-22) - [Status Code](#status-code-22)
- [Response Body](#response-body-24) - [Response Body](#response-body-24)
- [List Alarm State History](#list-alarm-state-history) - [Response Examples](#response-examples-21)
- [GET /v2.0/alarms/{alarm_id}/state-history](#get-v20alarmsalarm_idstate-history) - [Delete Alarm](#delete-alarm)
- [DELETE /v2.0/alarms/{alarm_id}](#delete-v20alarmsalarm_id)
- [Headers](#headers-25) - [Headers](#headers-25)
- [Path Parameters](#path-parameters-25) - [Path Parameters](#path-parameters-25)
- [Query Parameters](#query-parameters-25) - [Query Parameters](#query-parameters-25)
- [Request Body](#request-body-25) - [Request Body](#request-body-25)
- [Request Data](#request-data) - [Request Examples](#request-examples-22)
- [Response](#response-25) - [Response](#response-25)
- [Status Code](#status-code-23) - [Status Code](#status-code-23)
- [Response Body](#response-body-25) - [Response Body](#response-body-25)
- [Response Examples](#response-examples-20) - [List Alarm State History](#list-alarm-state-history)
- [GET /v2.0/alarms/{alarm_id}/state-history](#get-v20alarmsalarm_idstate-history)
- [Headers](#headers-26)
- [Path Parameters](#path-parameters-26)
- [Query Parameters](#query-parameters-26)
- [Request Body](#request-body-26)
- [Request Data](#request-data)
- [Response](#response-26)
- [Status Code](#status-code-24)
- [Response Body](#response-body-26)
- [Response Examples](#response-examples-22)
- [License](#license) - [License](#license)
<!-- END doctoc generated TOC please keep comment here to allow auto update --> <!-- END doctoc generated TOC please keep comment here to allow auto update -->
@ -774,6 +788,13 @@ offset=2104-01-01T00:00:01Z
``` ```
A dimension value offset would look as follows:
```
offset=dimensionValue2
```
Different resources use different offset types because of the internal implementation of different resources depends on different types of mechanisms for indexing and identifying resources. The type and form of the offsets for each resource can be determined by referring to the examples in each resource section below. Different resources use different offset types because of the internal implementation of different resources depends on different types of mechanisms for indexing and identifying resources. The type and form of the offsets for each resource can be determined by referring to the examples in each resource section below.
The offset is determined by the ID of the last element in the result list. Users wishing to manually create a query URL can use the ID of the last element in the previously returned result set as the offset. The proceeding result set will return all elements with an ID greater than the offset up to the limit. The automatically generated offset in the next link does exactly this; it uses the ID in the last element. The offset is determined by the ID of the last element in the result list. Users wishing to manually create a query URL can use the ID of the last element in the previously returned result set as the offset. The proceeding result set will return all elements with an ID greater than the offset up to the limit. The automatically generated offset in the next link does exactly this; it uses the ID in the last element.
@ -1137,6 +1158,76 @@ Returns a JSON object with a 'links' array of links and an 'elements' array of m
```` ````
___ ___
## List dimension values
Get dimension values
#### GET /v2.0/metrics/dimensions/names/values
#### Headers
* X-Auth-Token (string, required) - Keystone auth token
* Accept (string) - application/json
#### Path Parameters
None.
#### Query Parameters
* tenant_id (string, optional, restricted) - Tenant ID to from which to get dimension values. This parameter can be used to get dimension values from a tenant other than the tenant the request auth token is scoped to. Usage of this query parameter is restricted to users with the the monasca admin role, as defined in the monasca api configuration file, which defaults to `monasca-admin`.
* metric_name (string(255), optional) - A metric name to filter dimension values by.
* dimension_name (string(255), required) - A dimension name to filter dimension values by.
* offset (string(255), optional) - The dimension values are returned in alphabetic order, and the offset is the dimension name after which to return in the next pagination request.
* limit (integer, optional)
#### Request Body
None.
#### Request Examples
```
GET /v2.0/metrics/dimensions/names/values?dimension_name=dimension_name HTTP/1.1
Host: 192.168.10.4:8080
Content-Type: application/json
X-Auth-Token: 27feed73a0ce4138934e30d619b415b0
Cache-Control: no-cache
```
### Response
#### Status Code
* 200 - OK
#### Response Body
Returns a JSON object with a 'links' array of links and an 'elements' array of metric definition objects with the following fields:
* dimension_name (string)
* metric_name (string)
* values (list of strings)
#### Response Examples
````
{
"links": [
{
"rel": "self",
"href": "http://192.168.10.4:8080/v2.0/metrics/dimensions/names/values?dimension_name=dimension_name"
},
{
"rel": "next",
"href": "http://192.168.10.4:8080/v2.0/metrics/dimensions/names/values?dimension_name=dimension_name&offset=dimensionValue2"
}
],
"elements": [
{
"id": "b63d9bc1e16582d0ca039616ce3c870556b3095b",
"metric_name": "metric_name",
"dimension_name": "dimension_name",
"values": [
"dimensonValue1",
"dimensonValue2"
]
}
]
}
````
___
# Measurements # Measurements
Operations for accessing measurements of metrics. Operations for accessing measurements of metrics.

View File

@ -21,6 +21,7 @@ alarms = monasca_api.v2.reference.alarms:Alarms
alarms_count = monasca_api.v2.reference.alarms:AlarmsCount alarms_count = monasca_api.v2.reference.alarms:AlarmsCount
alarms_state_history = monasca_api.v2.reference.alarms:AlarmsStateHistory alarms_state_history = monasca_api.v2.reference.alarms:AlarmsStateHistory
notification_methods = monasca_api.v2.reference.notifications:Notifications notification_methods = monasca_api.v2.reference.notifications:Notifications
dimension_values = monasca_api.v2.reference.metrics:DimensionValues
[security] [security]
# The roles that are allowed full access to the API. # The roles that are allowed full access to the API.

View File

@ -38,6 +38,7 @@ import monasca.api.infrastructure.servlet.PreAuthenticationFilter;
import monasca.api.infrastructure.servlet.RoleAuthorizationFilter; import monasca.api.infrastructure.servlet.RoleAuthorizationFilter;
import monasca.api.resource.AlarmDefinitionResource; import monasca.api.resource.AlarmDefinitionResource;
import monasca.api.resource.AlarmResource; import monasca.api.resource.AlarmResource;
import monasca.api.resource.DimensionResource;
import monasca.api.resource.MeasurementResource; import monasca.api.resource.MeasurementResource;
import monasca.api.resource.MetricResource; import monasca.api.resource.MetricResource;
import monasca.api.resource.NotificationMethodResource; import monasca.api.resource.NotificationMethodResource;
@ -107,6 +108,7 @@ public class MonApiApplication extends Application<ApiConfig> {
environment.jersey().register(Injector.getInstance(VersionResource.class)); environment.jersey().register(Injector.getInstance(VersionResource.class));
environment.jersey().register(Injector.getInstance(AlarmDefinitionResource.class)); environment.jersey().register(Injector.getInstance(AlarmDefinitionResource.class));
environment.jersey().register(Injector.getInstance(AlarmResource.class)); environment.jersey().register(Injector.getInstance(AlarmResource.class));
environment.jersey().register(Injector.getInstance(DimensionResource.class));
environment.jersey().register(Injector.getInstance(MetricResource.class)); environment.jersey().register(Injector.getInstance(MetricResource.class));
environment.jersey().register(Injector.getInstance(MeasurementResource.class)); environment.jersey().register(Injector.getInstance(MeasurementResource.class));
environment.jersey().register(Injector.getInstance(StatisticResource.class)); environment.jersey().register(Injector.getInstance(StatisticResource.class));

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) 2016 Hewlett-Packard Development Company, L.P.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package monasca.api.domain.model.dimension;
import java.util.List;
import javax.annotation.Nullable;
/**
* Repository for dimensions.
*/
public interface DimensionRepo {
/**
* Finds dimension values given a dimension name and
* optional metric name.
*/
DimensionValues find(String metricName,
String tenantId,
String dimensionName,
@Nullable String offset,
int limit)
throws Exception;
}

View File

@ -0,0 +1,122 @@
/*
* Copyright (c) 2016 Hewlett-Packard Development Company, L.P.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package monasca.api.domain.model.dimension;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonInclude;
import monasca.common.model.domain.common.AbstractEntity;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.codec.binary.Hex;
/**
* Encapsulates the list of dimension values for a given dimension name
* (and optional metric-name).
*/
public class DimensionValues extends AbstractEntity {
protected String id = null;
protected String dimensionName;
@JsonInclude(JsonInclude.Include.NON_NULL)
protected String metricName;
protected List<String> values;
protected Map<String, List<String>> dimensionValues;
public DimensionValues() {
this.values = new ArrayList<String>();
this.dimensionValues = new HashMap<String, List<String>>();
}
public DimensionValues(String metricName, String dimensionName, List<String> values) {
this.metricName = metricName;
this.dimensionName = dimensionName;
this.values = values;
this.dimensionValues = new HashMap<String, List<String>>();
this.dimensionValues.put(dimensionName, values);
this.id = generateId();
}
public List<String> getValues() {
return values;
}
public String getDimensionName() {
return dimensionName;
}
public String getMetricName() {
return metricName;
}
public String getId() {
if (null == this.id) {
this.id = generateId();
}
return this.id;
}
private String generateId() {
String hashstr = "metricName=" + metricName + "dimensionName=" + dimensionName;
byte[] sha1Hash = DigestUtils.sha(hashstr);
return Hex.encodeHexString(sha1Hash);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DimensionValues other = (DimensionValues) obj;
if (dimensionName == null) {
if (other.dimensionName != null)
return false;
} else if (!dimensionName.equals(other.dimensionName))
return false;
if (metricName == null) {
if (other.metricName != null)
return false;
} else if (!metricName.equals(other.metricName))
return false;
if (values == null) {
if (other.values != null)
return false;
} else if (!values.equals(other.values))
return false;
return true;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((dimensionName == null) ? 0 : dimensionName.hashCode());
result = prime * result + ((metricName == null) ? 0 : metricName.hashCode());
result = prime * result + ((values == null) ? 0 : values.hashCode());
return result;
}
@Override
public String toString() {
return String.format("MetricName=%s DimensionValues [name=%s, values=%s]",
metricName, dimensionName, values);
}
}

View File

@ -22,6 +22,7 @@ import monasca.api.ApiConfig;
import monasca.api.domain.model.alarm.AlarmRepo; import monasca.api.domain.model.alarm.AlarmRepo;
import monasca.api.domain.model.alarmdefinition.AlarmDefinitionRepo; import monasca.api.domain.model.alarmdefinition.AlarmDefinitionRepo;
import monasca.api.domain.model.alarmstatehistory.AlarmStateHistoryRepo; import monasca.api.domain.model.alarmstatehistory.AlarmStateHistoryRepo;
import monasca.api.domain.model.dimension.DimensionRepo;
import monasca.api.domain.model.measurement.MeasurementRepo; import monasca.api.domain.model.measurement.MeasurementRepo;
import monasca.api.domain.model.metric.MetricDefinitionRepo; import monasca.api.domain.model.metric.MetricDefinitionRepo;
import monasca.api.domain.model.notificationmethod.NotificationMethodRepo; import monasca.api.domain.model.notificationmethod.NotificationMethodRepo;
@ -29,6 +30,7 @@ import monasca.api.domain.model.statistic.StatisticRepo;
import monasca.api.infrastructure.persistence.PersistUtils; import monasca.api.infrastructure.persistence.PersistUtils;
import monasca.api.infrastructure.persistence.Utils; import monasca.api.infrastructure.persistence.Utils;
import monasca.api.infrastructure.persistence.influxdb.InfluxV9AlarmStateHistoryRepo; import monasca.api.infrastructure.persistence.influxdb.InfluxV9AlarmStateHistoryRepo;
import monasca.api.infrastructure.persistence.influxdb.InfluxV9DimensionRepo;
import monasca.api.infrastructure.persistence.influxdb.InfluxV9MeasurementRepo; import monasca.api.infrastructure.persistence.influxdb.InfluxV9MeasurementRepo;
import monasca.api.infrastructure.persistence.influxdb.InfluxV9MetricDefinitionRepo; import monasca.api.infrastructure.persistence.influxdb.InfluxV9MetricDefinitionRepo;
import monasca.api.infrastructure.persistence.influxdb.InfluxV9RepoReader; import monasca.api.infrastructure.persistence.influxdb.InfluxV9RepoReader;
@ -43,6 +45,7 @@ import monasca.api.infrastructure.persistence.hibernate.AlarmSqlRepoImpl;
import monasca.api.infrastructure.persistence.hibernate.NotificationMethodSqlRepoImpl; import monasca.api.infrastructure.persistence.hibernate.NotificationMethodSqlRepoImpl;
import monasca.api.infrastructure.persistence.hibernate.AlarmHibernateUtils; import monasca.api.infrastructure.persistence.hibernate.AlarmHibernateUtils;
import monasca.api.infrastructure.persistence.vertica.AlarmStateHistoryVerticaRepoImpl; import monasca.api.infrastructure.persistence.vertica.AlarmStateHistoryVerticaRepoImpl;
import monasca.api.infrastructure.persistence.vertica.DimensionVerticaRepoImpl;
import monasca.api.infrastructure.persistence.vertica.MeasurementVerticaRepoImpl; import monasca.api.infrastructure.persistence.vertica.MeasurementVerticaRepoImpl;
import monasca.api.infrastructure.persistence.vertica.MetricDefinitionVerticaRepoImpl; import monasca.api.infrastructure.persistence.vertica.MetricDefinitionVerticaRepoImpl;
import monasca.api.infrastructure.persistence.vertica.StatisticVerticaRepoImpl; import monasca.api.infrastructure.persistence.vertica.StatisticVerticaRepoImpl;
@ -84,6 +87,7 @@ public class InfrastructureModule extends AbstractModule {
if (config.databaseConfiguration.getDatabaseType().trim().equalsIgnoreCase(VERTICA)) { if (config.databaseConfiguration.getDatabaseType().trim().equalsIgnoreCase(VERTICA)) {
bind(AlarmStateHistoryRepo.class).to(AlarmStateHistoryVerticaRepoImpl.class).in(Singleton.class); bind(AlarmStateHistoryRepo.class).to(AlarmStateHistoryVerticaRepoImpl.class).in(Singleton.class);
bind(DimensionRepo.class).to(DimensionVerticaRepoImpl.class).in(Singleton.class);
bind(MetricDefinitionRepo.class).to(MetricDefinitionVerticaRepoImpl.class).in(Singleton.class); bind(MetricDefinitionRepo.class).to(MetricDefinitionVerticaRepoImpl.class).in(Singleton.class);
bind(MeasurementRepo.class).to(MeasurementVerticaRepoImpl.class).in(Singleton.class); bind(MeasurementRepo.class).to(MeasurementVerticaRepoImpl.class).in(Singleton.class);
bind(StatisticRepo.class).to(StatisticVerticaRepoImpl.class).in(Singleton.class); bind(StatisticRepo.class).to(StatisticVerticaRepoImpl.class).in(Singleton.class);
@ -103,6 +107,7 @@ public class InfrastructureModule extends AbstractModule {
bind(InfluxV9Utils.class).in(Singleton.class); bind(InfluxV9Utils.class).in(Singleton.class);
bind(InfluxV9RepoReader.class).in(Singleton.class); bind(InfluxV9RepoReader.class).in(Singleton.class);
bind(AlarmStateHistoryRepo.class).to(InfluxV9AlarmStateHistoryRepo.class).in(Singleton.class); bind(AlarmStateHistoryRepo.class).to(InfluxV9AlarmStateHistoryRepo.class).in(Singleton.class);
bind(DimensionRepo.class).to(InfluxV9DimensionRepo.class).in(Singleton.class);
bind(MetricDefinitionRepo.class).to(InfluxV9MetricDefinitionRepo.class).in(Singleton.class); bind(MetricDefinitionRepo.class).to(InfluxV9MetricDefinitionRepo.class).in(Singleton.class);
bind(MeasurementRepo.class).to(InfluxV9MeasurementRepo.class).in(Singleton.class); bind(MeasurementRepo.class).to(InfluxV9MeasurementRepo.class).in(Singleton.class);
bind(StatisticRepo.class).to(InfluxV9StatisticRepo.class).in(Singleton.class); bind(StatisticRepo.class).to(InfluxV9StatisticRepo.class).in(Singleton.class);

View File

@ -0,0 +1,125 @@
/*
* Copyright (c) 2016 Hewlett-Packard Development Company, L.P.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package monasca.api.infrastructure.persistence.influxdb;
import com.google.inject.Inject;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import java.util.Set;
import monasca.api.ApiConfig;
import monasca.api.domain.model.dimension.DimensionValues;
import monasca.api.domain.model.dimension.DimensionRepo;
public class InfluxV9DimensionRepo implements DimensionRepo {
private static final Logger logger = LoggerFactory.getLogger(InfluxV9DimensionRepo.class);
private final ApiConfig config;
private final InfluxV9RepoReader influxV9RepoReader;
private final InfluxV9Utils influxV9Utils;
private final String region;
private final ObjectMapper objectMapper = new ObjectMapper();
@Inject
public InfluxV9DimensionRepo(ApiConfig config,
InfluxV9RepoReader influxV9RepoReader,
InfluxV9Utils influxV9Utils) {
this.config = config;
this.region = config.region;
this.influxV9RepoReader = influxV9RepoReader;
this.influxV9Utils = influxV9Utils;
}
@Override
public DimensionValues find(
String metricName,
String tenantId,
String dimensionName,
String offset,
int limit) throws Exception
{
//
// Use treeset to keep list in alphabetic/predictable order
// for string based offset.
//
Set<String> matchingValues = new TreeSet<String>();
String dimNamePart = "and \""
+ this.influxV9Utils.sanitize(dimensionName)
+ "\" =~ /.*/";
String q = String.format("show series %1$s where %2$s %3$s",
this.influxV9Utils.namePart(metricName, false),
this.influxV9Utils.privateTenantIdPart(tenantId),
dimNamePart);
logger.debug("Dimension values query: {}", q);
String r = this.influxV9RepoReader.read(q);
Series series = this.objectMapper.readValue(r, Series.class);
if (!series.isEmpty()) {
for (Serie serie : series.getSeries()) {
for (String[] values : serie.getValues()) {
Map<String, String> dimensions = this.influxV9Utils.getDimensions(values, serie.getColumns());
for (Map.Entry<String, String> entry : dimensions.entrySet()) {
if (dimensionName.equals(entry.getKey())) {
matchingValues.add(entry.getValue());
}
}
}
}
}
List<String> filteredValues = filterDimensionValues(matchingValues,
dimensionName,
limit,
offset);
return new DimensionValues(metricName, dimensionName, filteredValues);
}
private List<String> filterDimensionValues(Set<String> matchingValues,
String dimensionName,
int limit,
String offset)
{
Boolean haveOffset = (null != offset && !"".equals(offset));
List<String> filteredValues = new ArrayList<String>();
int remaining_limit = limit + 1;
for (String dimVal : matchingValues) {
if (remaining_limit <= 0) {
break;
}
if (haveOffset && dimVal.compareTo(offset) <= 0) {
continue;
}
filteredValues.add(dimVal);
remaining_limit--;
}
return filteredValues;
}
}

View File

@ -166,7 +166,8 @@ public class InfluxV9MetricDefinitionRepo implements MetricDefinitionRepo {
for (String[] values : serie.getValues()) { for (String[] values : serie.getValues()) {
MetricDefinition m = new MetricDefinition(serie.getName(), dims(values, serie.getColumns())); MetricDefinition m = new MetricDefinition(serie.getName(),
this.influxV9Utils.getDimensions(values, serie.getColumns()));
// //
// If start/end time are specified, ensure we've got measurements // If start/end time are specified, ensure we've got measurements
// for this definition before we add to the return list // for this definition before we add to the return list
@ -202,27 +203,6 @@ public class InfluxV9MetricDefinitionRepo implements MetricDefinitionRepo {
return metricNameList; return metricNameList;
} }
private Map<String, String> dims(String[] vals, String[] cols) {
Map<String, String> dims = new HashMap<>();
for (int i = 0; i < cols.length; ++i) {
// Dimension names that start with underscore are reserved. I.e., _key, _region, _tenant_id.
// Influxdb inserts _key.
// Monasca Persister inserts _region and _tenant_id.
if (!cols[i].startsWith("_")) {
if (!vals[i].equalsIgnoreCase("null")) {
dims.put(cols[i], vals[i]);
}
}
}
return dims;
}
private boolean hasMeasurements(MetricDefinition m, private boolean hasMeasurements(MetricDefinition m,
String tenantId, String tenantId,
DateTime startTime, DateTime startTime,

View File

@ -291,4 +291,20 @@ public class InfluxV9Utils {
public List<String> parseMultiOffset(String offsetStr) { public List<String> parseMultiOffset(String offsetStr) {
return offsetSplitter.splitToList(offsetStr); return offsetSplitter.splitToList(offsetStr);
} }
public Map<String, String> getDimensions(String[] vals, String[] cols) {
Map<String, String> dims = new HashMap<>();
for (int i = 0; i < cols.length; ++i) {
// Dimension names that start with underscore are reserved. I.e., _key, _region, _tenant_id.
// Influxdb inserts _key.
// Monasca Persister inserts _region and _tenant_id.
if (!cols[i].startsWith("_")) {
if (!vals[i].equalsIgnoreCase("null")) {
dims.put(cols[i], vals[i]);
}
}
}
return dims;
}
} }

View File

@ -0,0 +1,109 @@
/* Copyright (c) 2016 Hewlett-Packard Development Company, L.P.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package monasca.api.infrastructure.persistence.vertica;
import monasca.api.ApiConfig;
import monasca.api.domain.model.dimension.DimensionRepo;
import monasca.api.domain.model.dimension.DimensionValues;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Named;
import javax.annotation.Nullable;
import org.skife.jdbi.v2.DBI;
import org.skife.jdbi.v2.Handle;
import org.skife.jdbi.v2.Query;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DimensionVerticaRepoImpl implements DimensionRepo {
private static final Logger logger = LoggerFactory
.getLogger(DimensionVerticaRepoImpl.class);
private static final String FIND_DIMENSION_VALUES_SQL =
"SELECT %s" // dbHint goes here
+ " DISTINCT dims.value as dValue "
+ "FROM "
+ " MonMetrics.Definitions def,"
+ " MonMetrics.DefinitionDimensions defdims "
+ "LEFT OUTER JOIN"
+ " MonMetrics.Dimensions dims"
+ " ON dims.dimension_set_id = defdims.dimension_set_id "
+ "WHERE"
+ " def.id = defdims.definition_id"
+ " %s " // optional offset goes here
+ " %s " // optional metric name goes here
+ " and def.tenant_id = '%s'" // tenant_id goes here
+ " and dims.name = '%s' " // dimension name goes here
+ "ORDER BY dims.value ASC "
+ "%s "; // limit goes here
private final DBI db;
private final String dbHint;
@Inject
public DimensionVerticaRepoImpl(
@Named("vertica") DBI db, ApiConfig config)
{
this.db = db;
this.dbHint = config.vertica.dbHint;
}
@Override
public DimensionValues find(
String metricName,
String tenantId,
String dimensionName,
String offset,
int limit) throws Exception
{
List<String> values = new ArrayList<String>();
String offsetPart = "";
String metricNamePart = "";
try (Handle h = db.open()) {
if (offset != null && !offset.isEmpty()) {
offsetPart = " and dims.value > '" + offset + "' ";
}
if (metricName != null && !metricName.isEmpty()) {
metricNamePart = " and def.name = '" + metricName + "' ";
}
String limitPart = " limit " + Integer.toString(limit + 1);
String sql = String.format(FIND_DIMENSION_VALUES_SQL,
this.dbHint,
offsetPart,
metricNamePart,
tenantId,
dimensionName,
limitPart);
Query<Map<String, Object>> query = h.createQuery(sql);
List<Map<String, Object>> rows = query.list();
for (Map<String, Object> row : rows) {
String dimValue = (String) row.get("dValue");
values.add(dimValue);
}
}
return new DimensionValues(metricName, dimensionName, values);
}
}

View File

@ -0,0 +1,79 @@
/*
* Copyright (c) 2016 Hewlett-Packard Development Company, L.P.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package monasca.api.resource;
import static monasca.api.app.validation.Validation.DEFAULT_ADMIN_ROLE;
import com.google.common.base.Strings;
import com.codahale.metrics.annotation.Timed;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import monasca.api.app.validation.MetricNameValidation;
import monasca.api.ApiConfig;
import monasca.api.app.validation.Validation;
import monasca.api.domain.model.dimension.DimensionRepo;
import monasca.api.domain.model.dimension.DimensionValues;
import monasca.api.infrastructure.persistence.PersistUtils;
/**
* Dimension resource implementation.
*/
@Path("/v2.0/metrics/dimensions/names/values")
public class DimensionResource {
private final DimensionRepo repo;
private final PersistUtils persistUtils;
private final String admin_role;
@Inject
public DimensionResource(ApiConfig config, DimensionRepo repo, PersistUtils persistUtils) {
this.admin_role = (config.middleware == null || config.middleware.adminRole == null)
? DEFAULT_ADMIN_ROLE : config.middleware.adminRole;
this.repo = repo;
this.persistUtils = persistUtils;
}
@GET
@Timed
@Produces(MediaType.APPLICATION_JSON)
public Object get(
@Context UriInfo uriInfo,
@HeaderParam("X-Tenant-Id") String tenantId,
@HeaderParam("X-Roles") String roles,
@QueryParam("limit") String limit,
@QueryParam("dimension_name") String dimensionName,
@QueryParam("metric_name") String metricName,
@QueryParam("offset") String offset,
@QueryParam("tenant_id") String crossTenantId) throws Exception
{
Validation.validateNotNullOrEmpty(dimensionName, "dimension_name");
final int paging_limit = this.persistUtils.getLimit(limit);
String queryTenantId = Validation.getQueryProject(roles, crossTenantId, tenantId, admin_role);
DimensionValues dimVals = repo.find(metricName, queryTenantId, dimensionName, offset, paging_limit);
return Links.paginateDimensionValues(dimVals, paging_limit, uriInfo);
}
}

View File

@ -26,6 +26,7 @@ import com.google.common.base.Preconditions;
import monasca.api.ApiConfig; import monasca.api.ApiConfig;
import monasca.api.domain.model.alarm.AlarmCount; import monasca.api.domain.model.alarm.AlarmCount;
import monasca.api.domain.model.common.Paged; import monasca.api.domain.model.common.Paged;
import monasca.api.domain.model.dimension.DimensionValues;
import monasca.api.domain.model.measurement.Measurements; import monasca.api.domain.model.measurement.Measurements;
import monasca.common.model.domain.common.AbstractEntity; import monasca.common.model.domain.common.AbstractEntity;
import monasca.api.domain.model.common.Link; import monasca.api.domain.model.common.Link;
@ -359,4 +360,21 @@ public final class Links {
alarmCount.setLinks(links); alarmCount.setLinks(links);
} }
public static Paged paginateDimensionValues(DimensionValues dimVals, int limit, UriInfo uriInfo)
throws UnsupportedEncodingException {
Paged paged = new Paged();
List<DimensionValues> elements = new ArrayList<DimensionValues>();
paged.links.add(getSelfLink(uriInfo));
if ((null != dimVals) && (dimVals.getValues().size() > limit)) {
dimVals.getValues().remove(dimVals.getValues().size()-1);
String offset = dimVals.getValues().get(dimVals.getValues().size()-1);
paged.links.add(getNextLink(offset, uriInfo));
}
elements.add(dimVals);
paged.elements = elements;
return paged;
}
} }

View File

@ -0,0 +1,71 @@
/*
* Copyright (c) 2016 Hewlett-Packard Development Company, L.P.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package monasca.api.resource;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.testng.Assert.assertEquals;
import java.util.List;
import java.util.Map;
import org.joda.time.DateTime;
import org.testng.annotations.Test;
import monasca.api.ApiConfig;
import monasca.api.domain.model.dimension.DimensionRepo;
import monasca.api.infrastructure.persistence.PersistUtils;
import com.sun.jersey.api.client.ClientResponse;
@Test
public class DimensionResourceTest extends AbstractMonApiResourceTest {
private DimensionRepo dimensionRepo;
private ApiConfig apiConfig;
@Override
protected void setupResources() throws Exception {
super.setupResources();
dimensionRepo = mock(DimensionRepo.class);
apiConfig = mock(ApiConfig.class);
addResources(new DimensionResource(apiConfig, dimensionRepo, new PersistUtils()));
}
@SuppressWarnings("unchecked")
public void shouldQueryWithDefaultParams() throws Exception {
client()
.resource(
"/v2.0/metrics/dimensions/names/values?dimension_name=hpcs.compute")
.header("X-Tenant-Id", "abc").get(ClientResponse.class);
verify(dimensionRepo).find(anyString(), anyString(), anyString(), anyString(),
anyInt());
}
public void shouldQueryWithOptionalMetricName() throws Exception {
client()
.resource(
"/v2.0/metrics/dimensions/names/values?dimension_name=hpcs.compute&metric_name=cpu_utilization")
.header("X-Tenant-Id", "abc").get(ClientResponse.class);
verify(dimensionRepo).find(anyString(), anyString(), anyString(), anyString(),
anyInt());
}
}

View File

@ -18,6 +18,7 @@ import monasca.api.domain.model.alarm.Alarm;
import monasca.api.domain.model.common.Link; import monasca.api.domain.model.common.Link;
import monasca.api.domain.model.common.Paged; import monasca.api.domain.model.common.Paged;
import monasca.common.model.alarm.AlarmState; import monasca.common.model.alarm.AlarmState;
import monasca.api.domain.model.dimension.DimensionValues;
import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertEquals;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -133,4 +134,50 @@ public class LinksTest {
+ AlarmDefinitionResource.ALARM_DEFINITIONS_PATH.substring(1) + "/" + AlarmDefinitionResource.ALARM_DEFINITIONS_PATH.substring(1) + "/"
+ ALARM_DEF_ID)); + ALARM_DEF_ID));
} }
public void verifyPaginateDimensionValues() throws UnsupportedEncodingException, URISyntaxException{
final String base = "http://TheVip:8070/v2.0/metrics/dimensions/names/values";
final String limitParam = "limit=1";
final String url = base + "?" + limitParam;
final UriInfo uriInfo = mock(UriInfo.class);
when(uriInfo.getRequestUri()).thenReturn(new URI(url));
when(uriInfo.getAbsolutePath()).thenReturn(new URI(base));
final Map<String, String> params = new HashMap<>();
params.put("limit", "1");
@SuppressWarnings("unchecked")
final MultivaluedMap<String, String> mockParams = mock(MultivaluedMap.class);
when(uriInfo.getQueryParameters()).thenReturn(mockParams);
when(mockParams.keySet()).thenReturn(params.keySet());
when(mockParams.get("limit")).thenReturn(Arrays.asList("1"));
List<String> values = new ArrayList<String>();
values.add("value1");
values.add("value2");
List<String> oneValue = Arrays.asList("value1");
DimensionValues dimVals = new DimensionValues("custom_metric",
"dimension_name",
values);
DimensionValues expectedDimVal = new DimensionValues("custom_metric",
"dimension_name",
oneValue);
final int limit = 1;
final Paged expected = new Paged();
final List<Link> links = new ArrayList<>();
String expectedSelf = url;
String expectedNext = base + "?offset=value1&" + limitParam;
links.add(new Link("self", expectedSelf));
links.add(new Link("next", expectedNext));
expected.links = links;
final ArrayList<DimensionValues> expectedElements = new ArrayList<DimensionValues>();
// Since limit is one, only the first element is returned
expectedElements.add(expectedDimVal);
expected.elements = expectedElements;
final Paged actual = Links.paginateDimensionValues(dimVals, 1, uriInfo);
assertEquals(actual, expected);
}
} }

View File

@ -55,3 +55,12 @@ class MetricsNamesV2API(object):
def on_get(self, req, res): def on_get(self, req, res):
res.status = '501 Not Implemented' res.status = '501 Not Implemented'
class DimensionValuesV2API(object):
def __init__(self):
super(DimensionValuesV2API, self).__init__()
LOG.info('Initializing DimensionValuesV2API!')
def on_get(self, req, res):
res.status = '501 Not Implemented'

View File

@ -43,7 +43,9 @@ dispatcher_opts = [cfg.StrOpt('versions', default=None,
cfg.StrOpt('alarms_state_history', default=None, cfg.StrOpt('alarms_state_history', default=None,
help='Alarms state history'), help='Alarms state history'),
cfg.StrOpt('notification_methods', default=None, cfg.StrOpt('notification_methods', default=None,
help='Notification methods')] help='Notification methods'),
cfg.StrOpt('dimension_values', default=None,
help='Dimension values')]
dispatcher_group = cfg.OptGroup(name='dispatcher', title='dispatcher') dispatcher_group = cfg.OptGroup(name='dispatcher', title='dispatcher')
cfg.CONF.register_group(dispatcher_group) cfg.CONF.register_group(dispatcher_group)
@ -108,6 +110,9 @@ def launch(conf, config_file="/etc/monasca/api-config.conf"):
app.add_route("/v2.0/notification-methods/{notification_method_id}", app.add_route("/v2.0/notification-methods/{notification_method_id}",
notification_methods) notification_methods)
dimension_values = simport.load(cfg.CONF.dispatcher.dimension_values)()
app.add_route("/v2.0/metrics/dimensions/names/values", dimension_values)
LOG.debug('Dispatcher drivers have been added to the routes!') LOG.debug('Dispatcher drivers have been added to the routes!')
return app return app

View File

@ -15,6 +15,8 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from datetime import datetime from datetime import datetime
import hashlib
import json import json
from influxdb import client from influxdb import client
@ -205,6 +207,46 @@ class MetricsRepository(metrics_repository.MetricsRepository):
LOG.exception(ex) LOG.exception(ex)
raise exceptions.RepositoryException(ex) raise exceptions.RepositoryException(ex)
def _generate_dimension_values_id(self, metric_name, dimension_name):
sha1 = hashlib.sha1()
hashstr = "metricName=" + (metric_name or "") + "dimensionName=" + dimension_name
sha1.update(hashstr)
return sha1.hexdigest()
def _build_serie_dimension_values(self, series_names, metric_name, dimension_name,
tenant_id, region, offset):
dim_vals = []
sha1_id = self._generate_dimension_values_id(metric_name, dimension_name)
json_dim_vals = {u'id': sha1_id,
u'dimension_name': dimension_name,
u'values': dim_vals}
#
# Only return metric name if one was provided
#
if metric_name:
json_dim_vals[u'metric_name'] = metric_name
if not series_names:
return json_dim_vals
if 'series' in series_names.raw:
for series in series_names.raw['series']:
for tag_values in series[u'values']:
dims = {
name: value
for name, value in zip(series[u'columns'], tag_values)
if value and not name.startswith(u'_')
}
if dimension_name in dims and dims[dimension_name] not in dim_vals:
dim_vals.append(dims[dimension_name])
dim_vals = sorted(dim_vals)
json_dim_vals[u'values'] = dim_vals
return json_dim_vals
def _build_serie_metric_list(self, series_names, tenant_id, region, def _build_serie_metric_list(self, series_names, tenant_id, region,
start_timestamp, end_timestamp, start_timestamp, end_timestamp,
offset): offset):
@ -585,3 +627,23 @@ class MetricsRepository(metrics_repository.MetricsRepository):
def _get_millis_from_timestamp(self, dt): def _get_millis_from_timestamp(self, dt):
dt = dt.replace(tzinfo=None) dt = dt.replace(tzinfo=None)
return int((dt - datetime(1970, 1, 1)).total_seconds() * 1000) return int((dt - datetime(1970, 1, 1)).total_seconds() * 1000)
def list_dimension_values(self, tenant_id, region, metric_name,
dimension_name, offset, limit):
try:
query = self._build_show_series_query(None, metric_name, tenant_id, region)
result = self.influxdb_client.query(query)
json_dim_vals = self._build_serie_dimension_values(result,
metric_name,
dimension_name,
tenant_id,
region,
offset)
return json_dim_vals
except Exception as ex:
LOG.exception(ex)
raise exceptions.RepositoryException(ex)

View File

@ -120,3 +120,40 @@ class TestRepoMetricsInfluxDB(unittest.TestCase):
u'hosttype': u'native' u'hosttype': u'native'
}, },
}]) }])
@patch("monasca_api.common.repositories.influxdb.metrics_repository.client.InfluxDBClient")
def test_list_dimension_values(self, influxdb_client_mock):
mock_client = influxdb_client_mock.return_value
mock_client.query.return_value.raw = {
u'series': [{
u'values': [[
u'custom_metric,_region=useast,_tenant_id=38dc2a2549f94d2e9a4fa1cc45a4970c,'
u'hostname=custom_host,service=custom_service',
u'useast',
u'38dc2a2549f94d2e9a4fa1cc45a4970c',
u'custom_host',
u'custom_service'
]],
u'name': u'custom_metric',
u'columns': [u'_key', u'_region', u'_tenant_id', u'hostname', u'service']
}]
}
repo = influxdb_repo.MetricsRepository()
result = repo.list_dimension_values(
"38dc2a2549f94d2e9a4fa1cc45a4970c",
"useast",
"custom_metric",
"hostname",
offset=None,
limit=1)
self.assertEqual(result, {
u'dimension_name': 'hostname',
u'values': [
u'custom_host'
],
u'id': 'bea9565d854a16a3366164de213694c190f27675',
u'metric_name': 'custom_metric'
})

View File

@ -398,6 +398,61 @@ def paginate_alarming(resource, uri, limit):
return resource return resource
def paginate_dimension_values(dimvals, uri, offset, limit):
parsed_uri = urlparse.urlparse(uri)
self_link = build_base_uri(parsed_uri)
old_query_params = _get_old_query_params(parsed_uri)
if old_query_params:
self_link += '?' + '&'.join(old_query_params)
if (dimvals and dimvals[u'values']):
have_more, truncated_values = _truncate_dimension_values(dimvals[u'values'],
limit,
offset)
links = [{u'rel': u'self', u'href': self_link.decode('utf8')}]
if have_more:
new_offset = truncated_values[limit - 1]
next_link = build_base_uri(parsed_uri)
new_query_params = [u'offset' + '=' + urlparse.quote(
new_offset.encode('utf8'), safe='')]
_get_old_query_params_except_offset(new_query_params, parsed_uri)
if new_query_params:
next_link += '?' + '&'.join(new_query_params)
links.append({u'rel': u'next', u'href': next_link.decode('utf8')})
truncated_dimvals = {u'id': dimvals[u'id'],
u'dimension_name': dimvals[u'dimension_name'],
u'values': truncated_values}
#
# Only return metric name if one was provided
#
if u'metric_name' in dimvals:
truncated_dimvals[u'metric_name'] = dimvals[u'metric_name']
resource = {u'links': links,
u'elements': [truncated_dimvals]}
else:
resource = {u'links': ([{u'rel': u'self',
u'href': self_link.decode('utf8')}]),
u'elements': [dimvals]}
return resource
def _truncate_dimension_values(values, limit, offset):
if offset and offset in values:
next_value_pos = values.index(offset) + 1
values = values[next_value_pos:]
have_more = len(values) > limit
return have_more, values[:limit]
def paginate_measurement(measurement, uri, limit): def paginate_measurement(measurement, uri, limit):
parsed_uri = urlparse.urlparse(uri) parsed_uri = urlparse.urlparse(uri)

View File

@ -302,3 +302,44 @@ class MetricsNames(metrics_api_v2.MetricsNamesV2API):
offset, limit) offset, limit)
return helpers.paginate(result, req_uri, limit) return helpers.paginate(result, req_uri, limit)
class DimensionValues(metrics_api_v2.DimensionValuesV2API):
def __init__(self):
try:
super(DimensionValues, self).__init__()
self._region = cfg.CONF.region
self._default_authorized_roles = (
cfg.CONF.security.default_authorized_roles)
self._metrics_repo = simport.load(
cfg.CONF.repositories.metrics_driver)()
except Exception as ex:
LOG.exception(ex)
raise falcon.HTTPInternalServerError('Service unavailable',
ex.message)
def on_get(self, req, res):
helpers.validate_authorization(req, self._default_authorized_roles)
tenant_id = helpers.get_tenant_id(req)
metric_name = helpers.get_query_param(req, 'metric_name')
dimension_name = helpers.get_query_param(req, 'dimension_name', required=True)
offset = helpers.get_query_param(req, 'offset')
limit = helpers.get_limit(req)
result = self._dimension_values(tenant_id, req.uri, metric_name,
dimension_name, offset, limit)
res.body = helpers.dumpit_utf8(result)
res.status = falcon.HTTP_200
@resource.resource_try_catch_block
def _dimension_values(self, tenant_id, req_uri, metric_name,
dimension_name, offset, limit):
result = self._metrics_repo.list_dimension_values(tenant_id,
self._region,
metric_name,
dimension_name,
offset,
limit)
return helpers.paginate_dimension_values(result, req_uri, offset, limit)

View File

@ -52,6 +52,13 @@ class MonascaClient(rest_client.RestClient):
resp, response_body = self.get(uri) resp, response_body = self.get(uri)
return resp, json.loads(response_body) return resp, json.loads(response_body)
def list_dimension_values(self, query_params=None):
uri = 'metrics/dimensions/names/values'
if query_params is not None:
uri = uri + query_params
resp, response_body = self.get(uri)
return resp, json.loads(response_body)
def list_measurements(self, query_params=None): def list_measurements(self, query_params=None):
uri = 'metrics/measurements' uri = 'metrics/measurements'
if query_params is not None: if query_params is not None:

View File

@ -0,0 +1,159 @@
# (C) Copyright 2016 Hewlett Packard Enterprise Development Company LP
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import time
from oslo_utils import timeutils
from monasca_tempest_tests.tests.api import base
from monasca_tempest_tests.tests.api import constants
from monasca_tempest_tests.tests.api import helpers
from tempest.common.utils import data_utils
from tempest import test
from tempest.lib import exceptions
class TestDimensions(base.BaseMonascaTest):
@classmethod
def resource_setup(cls):
super(TestDimensions, cls).resource_setup()
name = data_utils.rand_name()
key = data_utils.rand_name()
value1 = "value_1"
value2 = "value_2"
cls._param = key + ':' + value1
cls._dimension_name = key
cls._dim_val_1 = value1
cls._dim_val_2 = value2
metric = helpers.create_metric(name=name,
dimensions={key: value1})
cls.monasca_client.create_metrics(metric)
metric = helpers.create_metric(name=name,
dimensions={key: value2})
cls.monasca_client.create_metrics(metric)
cls._test_metric = metric
start_time = str(timeutils.iso8601_from_timestamp(
metric['timestamp'] / 1000.0))
parms = '?name=' + str(cls._test_metric['name']) + \
'&start_time=' + start_time
for i in xrange(constants.MAX_RETRIES):
resp, response_body = cls.monasca_client.list_metrics(
parms)
elements = response_body['elements']
for element in elements:
if str(element['name']) == cls._test_metric['name']:
return
time.sleep(constants.RETRY_WAIT_SECS)
assert False, 'Unable to initialize metrics'
@classmethod
def resource_cleanup(cls):
super(TestDimensions, cls).resource_cleanup()
@test.attr(type='gate')
def test_list_dimension_values_without_metric_name(self):
parms = '?dimension_name=' + self._dimension_name
resp, response_body = self.monasca_client.list_dimension_values(parms)
self.assertEqual(200, resp.status)
self.assertTrue(set(['links', 'elements']) == set(response_body))
if not self._is_dimension_name_in_list(response_body):
self.fail('Dimension name not found in response')
if self._is_metric_name_in_list(response_body):
self.fail('Metric name was in response and should not be')
if not self._are_dim_vals_in_list(response_body):
self.fail('Dimension value not found in response')
@test.attr(type='gate')
def test_list_dimension_values_with_metric_name(self):
parms = '?metric_name=' + self._test_metric['name']
parms += '&dimension_name=' + self._dimension_name
resp, response_body = self.monasca_client.list_dimension_values(parms)
self.assertEqual(200, resp.status)
self.assertTrue(set(['links', 'elements']) == set(response_body))
if not self._is_metric_name_in_list(response_body):
self.fail('Metric name not found in response')
if not self._is_dimension_name_in_list(response_body):
self.fail('Dimension name not found in response')
if not self._are_dim_vals_in_list(response_body):
self.fail('Dimension value not found in response')
@test.attr(type='gate')
def test_list_dimension_values_limit_and_offset(self):
parms = '?dimension_name=' + self._dimension_name
parms += '&limit=1'
resp, response_body = self.monasca_client.list_dimension_values(parms)
self.assertEqual(200, resp.status)
self.assertTrue(set(['links', 'elements']) == set(response_body))
if not self._is_dimension_name_in_list(response_body):
self.fail('Dimension name not found in response')
if not self._is_dim_val_in_list(response_body, self._dim_val_1):
self.fail('First dimension value not found in response')
if not self._is_offset_in_links(response_body, self._dim_val_1):
self.fail('Offset not found in response')
if not self._is_limit_in_links(response_body):
self.fail('Limit not found in response')
@test.attr(type='gate')
@test.attr(type=['negative'])
def test_list_dimension_values_no_dimension_name(self):
self.assertRaises(exceptions.UnprocessableEntity,
self.monasca_client.list_dimension_values)
def _is_metric_name_in_list(self, response_body):
elements = response_body['elements'][0]
if 'metric_name' not in elements:
return False
if str(elements['metric_name']) == self._test_metric['name']:
return True
return False
def _is_dimension_name_in_list(self, response_body):
elements = response_body['elements'][0]
if str(elements['dimension_name']) == self._dimension_name:
return True
return False
def _are_dim_vals_in_list(self, response_body):
elements = response_body['elements'][0]
have_dim_1 = self._is_dim_val_in_list(response_body, self._dim_val_1)
have_dim_2 = self._is_dim_val_in_list(response_body, self._dim_val_2)
if have_dim_1 and have_dim_1:
return True
return False
def _is_dim_val_in_list(self, response_body, dim_val):
elements = response_body['elements'][0]
if dim_val in elements['values']:
return True
return False
def _is_offset_in_links(self, response_body, dim_val):
links = response_body['links']
offset = "offset=" + dim_val
for link in links:
if offset in link['href']:
return True
return False
def _is_limit_in_links(self, response_body):
links = response_body['links']
limit = "limit=1"
for link in links:
if limit in link['href']:
return True
return False