Add metrics/dimensions/names into monasca-api

This endpoint will return all the dimension names for a given metric
name.
Added dimension-names for influx in python api and java api
Added dimension-names for vertica in java api

Depends-On: Id981dafd00778a6d4a376b9ceab011231e94c0c6
Change-Id: I0192ccb9276ea94103a477bd2ad7d10f21e64d31
Implements: blueprint dimensions-api
This commit is contained in:
Kaiyan Sheng 2016-08-03 17:52:24 -06:00
parent e7a7732446
commit f26c427b4b
24 changed files with 924 additions and 538 deletions

View File

@ -22,6 +22,7 @@ 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 dimension_values = monasca_api.v2.reference.metrics:DimensionValues
dimension_names = monasca_api.v2.reference.metrics:DimensionNames
notification_method_types = monasca_api.v2.reference.notificationstype:NotificationsType notification_method_types = monasca_api.v2.reference.notificationstype:NotificationsType
[security] [security]

View File

@ -94,9 +94,8 @@ 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)
- [Measurements](#measurements) - [List dimension names](#list-dimension-names)
- [List measurements](#list-measurements) - [GET /v2.0/metrics/dimensions/names](#get-v20metricsdimensionsnames)
- [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)
@ -106,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)
- [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-6) - [Headers](#headers-6)
- [Path Parameters](#path-parameters-6) - [Path Parameters](#path-parameters-6)
- [Query Parameters](#query-parameters-6) - [Query Parameters](#query-parameters-6)
@ -118,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)
- [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-7) - [Headers](#headers-7)
- [Path Parameters](#path-parameters-7) - [Path Parameters](#path-parameters-7)
- [Query Parameters](#query-parameters-7) - [Query Parameters](#query-parameters-7)
@ -130,9 +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)
- [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-8) - [Headers](#headers-8)
- [Path Parameters](#path-parameters-8) - [Path Parameters](#path-parameters-8)
- [Query Parameters](#query-parameters-8) - [Query Parameters](#query-parameters-8)
@ -142,8 +141,9 @@ 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)
- [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-9) - [Headers](#headers-9)
- [Path Parameters](#path-parameters-9) - [Path Parameters](#path-parameters-9)
- [Query Parameters](#query-parameters-9) - [Query Parameters](#query-parameters-9)
@ -153,8 +153,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)
- [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-10) - [Headers](#headers-10)
- [Path Parameters](#path-parameters-10) - [Path Parameters](#path-parameters-10)
- [Query Parameters](#query-parameters-10) - [Query Parameters](#query-parameters-10)
@ -164,8 +164,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)
- [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-11) - [Headers](#headers-11)
- [Path Parameters](#path-parameters-11) - [Path Parameters](#path-parameters-11)
- [Query Parameters](#query-parameters-11) - [Query Parameters](#query-parameters-11)
@ -175,8 +175,8 @@ Document Version: v2.0
- [Status Code](#status-code-9) - [Status Code](#status-code-9)
- [Response Body](#response-body-11) - [Response Body](#response-body-11)
- [Response Examples](#response-examples-10) - [Response Examples](#response-examples-10)
- [Patch Notification Method](#patch-notification-method) - [Update Notification Method](#update-notification-method)
- [PATCH /v2.0/notification-methods/{notification_method_id}](#patch-v20notification-methodsnotification_method_id) - [PUT /v2.0/notification-methods/{notification_method_id}](#put-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)
@ -186,8 +186,8 @@ Document Version: v2.0
- [Status Code](#status-code-10) - [Status Code](#status-code-10)
- [Response Body](#response-body-12) - [Response Body](#response-body-12)
- [Response Examples](#response-examples-11) - [Response Examples](#response-examples-11)
- [Delete Notification Method](#delete-notification-method) - [Patch Notification Method](#patch-notification-method)
- [DELETE /v2.0/notification-methods/{notification_method_id}](#delete-v20notification-methodsnotification_method_id) - [PATCH /v2.0/notification-methods/{notification_method_id}](#patch-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)
@ -196,21 +196,20 @@ 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)
- [List supported Notification Method Types](#list-supported-notification-method-types) - [Response Examples](#response-examples-12)
- [GET /v2.0/notification-methods/types/](#get-v20notification-methodstypes) - [Delete Notification Method](#delete-notification-method)
- [DELETE /v2.0/notification-methods/{notification_method_id}](#delete-v20notification-methodsnotification_method_id)
- [Headers](#headers-14) - [Headers](#headers-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) - [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) - [List supported Notification Method Types](#list-supported-notification-method-types)
- [Alarm Definitions](#alarm-definitions) - [GET /v2.0/notification-methods/types/](#get-v20notification-methodstypes)
- [Create Alarm Definition](#create-alarm-definition)
- [POST /v2.0/alarm-definitions](#post-v20alarm-definitions)
- [Headers](#headers-15) - [Headers](#headers-15)
- [Path Parameters](#path-parameters-14)
- [Query Parameters](#query-parameters-15) - [Query Parameters](#query-parameters-15)
- [Request Body](#request-body-15) - [Request Body](#request-body-15)
- [Request Examples](#request-examples-15) - [Request Examples](#request-examples-15)
@ -218,8 +217,9 @@ Document Version: v2.0
- [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)
- [List Alarm Definitions](#list-alarm-definitions) - [Alarm Definitions](#alarm-definitions)
- [GET /v2.0/alarm-definitions](#get-v20alarm-definitions) - [Create Alarm Definition](#create-alarm-definition)
- [POST /v2.0/alarm-definitions](#post-v20alarm-definitions)
- [Headers](#headers-16) - [Headers](#headers-16)
- [Path Parameters](#path-parameters-15) - [Path Parameters](#path-parameters-15)
- [Query Parameters](#query-parameters-16) - [Query Parameters](#query-parameters-16)
@ -229,29 +229,29 @@ Document Version: v2.0
- [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)
- [Get Alarm Definition](#get-alarm-definition) - [List Alarm Definitions](#list-alarm-definitions)
- [GET /v2.0/alarm-definitions/{alarm_definition_id}](#get-v20alarm-definitionsalarm_definition_id) - [GET /v2.0/alarm-definitions](#get-v20alarm-definitions)
- [Headers](#headers-17) - [Headers](#headers-17)
- [Path Parameters](#path-parameters-16) - [Path Parameters](#path-parameters-16)
- [Query Parameters](#query-parameters-17) - [Query Parameters](#query-parameters-17)
- [Request Body](#request-body-17) - [Request Body](#request-body-17)
- [Request Examples](#request-examples-17)
- [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)
- [Response Examples](#response-examples-15) - [Response Examples](#response-examples-15)
- [Update Alarm Definition](#update-alarm-definition) - [Get Alarm Definition](#get-alarm-definition)
- [PUT /v2.0/alarm-definitions/{alarm_definition_id}](#put-v20alarm-definitionsalarm_definition_id) - [GET /v2.0/alarm-definitions/{alarm_definition_id}](#get-v20alarm-definitionsalarm_definition_id)
- [Headers](#headers-18) - [Headers](#headers-18)
- [Path Parameters](#path-parameters-17) - [Path Parameters](#path-parameters-17)
- [Query Parameters](#query-parameters-18) - [Query Parameters](#query-parameters-18)
- [Request Body](#request-body-18) - [Request Body](#request-body-18)
- [Request Examples](#request-examples-17)
- [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-16) - [Response Examples](#response-examples-16)
- [Patch Alarm Definition](#patch-alarm-definition) - [Update Alarm Definition](#update-alarm-definition)
- [PATCH /v2.0/alarm-definitions/{alarm_definition_id}](#patch-v20alarm-definitionsalarm_definition_id) - [PUT /v2.0/alarm-definitions/{alarm_definition_id}](#put-v20alarm-definitionsalarm_definition_id)
- [Headers](#headers-19) - [Headers](#headers-19)
- [Path Parameters](#path-parameters-18) - [Path Parameters](#path-parameters-18)
- [Query Parameters](#query-parameters-19) - [Query Parameters](#query-parameters-19)
@ -261,8 +261,8 @@ Document Version: v2.0
- [Status Code](#status-code-17) - [Status Code](#status-code-17)
- [Response Body](#response-body-19) - [Response Body](#response-body-19)
- [Response Examples](#response-examples-17) - [Response Examples](#response-examples-17)
- [Delete Alarm Definition](#delete-alarm-definition) - [Patch Alarm Definition](#patch-alarm-definition)
- [DELETE /v2.0/alarm-definitions/{alarm_definition_id}](#delete-v20alarm-definitionsalarm_definition_id) - [PATCH /v2.0/alarm-definitions/{alarm_definition_id}](#patch-v20alarm-definitionsalarm_definition_id)
- [Headers](#headers-20) - [Headers](#headers-20)
- [Path Parameters](#path-parameters-19) - [Path Parameters](#path-parameters-19)
- [Query Parameters](#query-parameters-20) - [Query Parameters](#query-parameters-20)
@ -271,9 +271,9 @@ Document Version: v2.0
- [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)
- [Alarms](#alarms) - [Response Examples](#response-examples-18)
- [List Alarms](#list-alarms) - [Delete Alarm Definition](#delete-alarm-definition)
- [GET /v2.0/alarms](#get-v20alarms) - [DELETE /v2.0/alarm-definitions/{alarm_definition_id}](#delete-v20alarm-definitionsalarm_definition_id)
- [Headers](#headers-21) - [Headers](#headers-21)
- [Path Parameters](#path-parameters-20) - [Path Parameters](#path-parameters-20)
- [Query Parameters](#query-parameters-21) - [Query Parameters](#query-parameters-21)
@ -282,19 +282,20 @@ 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-18) - [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-22) - [Headers](#headers-22)
- [Path Parameters](#path-parameters-21) - [Path Parameters](#path-parameters-21)
- [Query Parameters](#query-parameters-22) - [Query Parameters](#query-parameters-22)
- [Request Body](#request-body-22) - [Request Body](#request-body-22)
- [Request Examples](#request-examples-21)
- [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-19) - [Response Examples](#response-examples-19)
- [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-23) - [Headers](#headers-23)
- [Path Parameters](#path-parameters-22) - [Path Parameters](#path-parameters-22)
- [Query Parameters](#query-parameters-23) - [Query Parameters](#query-parameters-23)
@ -303,19 +304,18 @@ Document Version: v2.0
- [Status Code](#status-code-21) - [Status Code](#status-code-21)
- [Response Body](#response-body-23) - [Response Body](#response-body-23)
- [Response Examples](#response-examples-20) - [Response Examples](#response-examples-20)
- [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-24) - [Headers](#headers-24)
- [Path Parameters](#path-parameters-23) - [Path Parameters](#path-parameters-23)
- [Query Parameters](#query-parameters-24) - [Query Parameters](#query-parameters-24)
- [Request Body](#request-body-24) - [Request Body](#request-body-24)
- [Request Examples](#request-examples-21)
- [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)
- [Response Examples](#response-examples-21) - [Response Examples](#response-examples-21)
- [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-25) - [Headers](#headers-25)
- [Path Parameters](#path-parameters-24) - [Path Parameters](#path-parameters-24)
- [Query Parameters](#query-parameters-25) - [Query Parameters](#query-parameters-25)
@ -325,8 +325,8 @@ Document Version: v2.0
- [Status Code](#status-code-23) - [Status Code](#status-code-23)
- [Response Body](#response-body-25) - [Response Body](#response-body-25)
- [Response Examples](#response-examples-22) - [Response Examples](#response-examples-22)
- [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-26) - [Headers](#headers-26)
- [Path Parameters](#path-parameters-25) - [Path Parameters](#path-parameters-25)
- [Query Parameters](#query-parameters-26) - [Query Parameters](#query-parameters-26)
@ -335,17 +335,28 @@ Document Version: v2.0
- [Response](#response-26) - [Response](#response-26)
- [Status Code](#status-code-24) - [Status Code](#status-code-24)
- [Response Body](#response-body-26) - [Response Body](#response-body-26)
- [List Alarm State History](#list-alarm-state-history) - [Response Examples](#response-examples-23)
- [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-27) - [Headers](#headers-27)
- [Path Parameters](#path-parameters-26) - [Path Parameters](#path-parameters-26)
- [Query Parameters](#query-parameters-27) - [Query Parameters](#query-parameters-27)
- [Request Body](#request-body-27) - [Request Body](#request-body-27)
- [Request Data](#request-data) - [Request Examples](#request-examples-24)
- [Response](#response-27) - [Response](#response-27)
- [Status Code](#status-code-25) - [Status Code](#status-code-25)
- [Response Body](#response-body-27) - [Response Body](#response-body-27)
- [Response Examples](#response-examples-23) - [List Alarm State History](#list-alarm-state-history)
- [GET /v2.0/alarms/{alarm_id}/state-history](#get-v20alarmsalarm_idstate-history)
- [Headers](#headers-28)
- [Path Parameters](#path-parameters-27)
- [Query Parameters](#query-parameters-28)
- [Request Body](#request-body-28)
- [Request Data](#request-data)
- [Response](#response-28)
- [Status Code](#status-code-26)
- [Response Body](#response-body-28)
- [Response Examples](#response-examples-24)
- [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 -->
@ -1194,40 +1205,93 @@ Cache-Control: no-cache
* 200 - OK * 200 - OK
#### Response Body #### Response Body
Returns a JSON object with a 'links' array of links and an 'elements' array of metric definition objects with the following fields: Returns a JSON object with a 'links' array of links and an 'elements' array of dimension values.
* dimension_name (string)
* metric_name (string)
* values (list of strings)
#### Response Examples #### Response Examples
```` ````
{ {
"links": [ "elements": [
{ {
"rel": "self", "dimension_value": "value2"
"href": "http://192.168.10.4:8080/v2.0/metrics/dimensions/names/values?dimension_name=dimension_name" },
}, {
{ "dimension_value": "value3"
"rel": "next", }
"href": "http://192.168.10.4:8080/v2.0/metrics/dimensions/names/values?dimension_name=dimension_name&offset=dimensionValue2" ],
} "links": [
], {
"elements": [ "href": "http://192.168.10.6:8070/v2.0/metrics/dimensions/names/values?dimension_name=dim_name&offset=value1&limit=2",
{ "rel": "self"
"id": "b63d9bc1e16582d0ca039616ce3c870556b3095b", },
"metric_name": "metric_name", {
"dimension_name": "dimension_name", "href": "http://192.168.10.6:8070/v2.0/metrics/dimensions/names/values?offset=value3&dimension_name=dim_name&limit=2",
"values": [ "rel": "next"
"dimensonValue1", }
"dimensonValue2" ]
]
}
]
} }
```` ````
___ ___
## List dimension names
Get dimension names
#### GET /v2.0/metrics/dimensions/names
#### 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 from which to get dimension names. This parameter can be used to get dimension names 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 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 names by.
* offset (string(255), optional) - The dimension names are returned in alphabetic order, and the offset is the dimension name after which will return in the next pagination request.
* limit (integer, optional)
#### Request Body
None.
#### Request Examples
```
GET /v2.0/metrics/dimensions/names HTTP/1.1
Host: 192.168.10.6:8070
Content-Type: application/json
X-Auth-Token: 818d3d8f10bd4987adb3f84bc94a801d
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 dimension names.
#### Response Examples
````
{
"elements": [
{
"dimension_name": "name2"
},
{
"dimension_name": "name3"
}
],
"links": [
{
"href": "http://192.168.10.6:8070/v2.0/metrics/dimensions/names?offset=name1&limit=2",
"rel": "self"
},
{
"href": "http://192.168.10.6:8070/v2.0/metrics/dimensions/names?offset=name3&limit=2",
"rel": "next"
}
]
}
````
# Measurements # Measurements
Operations for accessing measurements of metrics. Operations for accessing measurements of metrics.

View File

@ -22,6 +22,7 @@ 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 dimension_values = monasca_api.v2.reference.metrics:DimensionValues
dimension_names = monasca_api.v2.reference.metrics:DimensionNames
notification_method_types = monasca_api.v2.reference.notificationstype:NotificationsType notification_method_types = monasca_api.v2.reference.notificationstype:NotificationsType
[security] [security]

View File

@ -0,0 +1,73 @@
/*
* (C) Copyright 2016 Hewlett Packard Enterprise Development 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.
*/
package monasca.api.domain.model.dimension;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import monasca.common.model.domain.common.AbstractEntity;
/**
* Base class for DimensionNames and DimensionValues.
*/
public abstract class DimensionBase extends AbstractEntity {
@JsonInclude(JsonInclude.Include.NON_NULL)
final private String metricName;
final private String id;
public DimensionBase(String metricName, String id) {
this.metricName = metricName;
this.id = id;
}
public String getMetricName() {
return metricName;
}
@JsonIgnore
public String getId() {
return id;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DimensionBase other = (DimensionBase) obj;
if (metricName == null) {
if (other.getMetricName() != null)
return false;
} else if (!metricName.equals(other.getMetricName()))
return false;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 17;
result = prime * result + ((metricName == null) ? 0 : metricName.hashCode());
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
}

View File

@ -0,0 +1,59 @@
/*
* (C) Copyright 2016 Hewlett Packard Enterprise Development 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.
*/
package monasca.api.domain.model.dimension;
/**
* Encapsulates dimension name for an optional metric name.
*/
public class DimensionName extends DimensionBase {
final private String dimensionName;
public DimensionName(String metricName, String dimensionName) {
super(metricName, dimensionName);
this.dimensionName = dimensionName;
}
public String getDimensionName() {
return dimensionName;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null || getClass() != obj.getClass())
return false;
DimensionName other = (DimensionName) obj;
if (dimensionName == null) {
if (other.dimensionName != null)
return false;
} else if (!dimensionName.equals(other.dimensionName))
return false;
return super.equals(obj);
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((dimensionName == null) ? 0 : dimensionName.hashCode());
return result;
}
@Override
public String toString() {
return String.format("DimensionName: MetricName=%s DimensionName [names=%s]",
getMetricName(), dimensionName);
}
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2016 Hewlett-Packard Development Company, L.P. * (C) Copyright 2016 Hewlett Packard Enterprise Development LP
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * 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 * in compliance with the License. You may obtain a copy of the License at
@ -13,10 +13,10 @@
*/ */
package monasca.api.domain.model.dimension; package monasca.api.domain.model.dimension;
import java.util.List;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.List;
/** /**
* Repository for dimensions. * Repository for dimensions.
*/ */
@ -25,10 +25,19 @@ public interface DimensionRepo {
* Finds dimension values given a dimension name and * Finds dimension values given a dimension name and
* optional metric name. * optional metric name.
*/ */
DimensionValues find(String metricName, List<DimensionValue> findValues(String metricName,
String tenantId, String tenantId,
String dimensionName, String dimensionName,
@Nullable String offset, @Nullable String offset,
int limit) int limit)
throws Exception;
/**
* Finds dimension names given an optional metric name.
*/
List<DimensionName> findNames(String metricName,
String tenantId,
@Nullable String offset,
int limit)
throws Exception; throws Exception;
} }

View File

@ -0,0 +1,69 @@
/*
* (C) Copyright 2016 Hewlett Packard Enterprise Development 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.
*/
package monasca.api.domain.model.dimension;
/**
* Encapsulates dimension value for a given dimension name
* (and optional metric-name).
*/
public class DimensionValue extends DimensionBase {
final private String dimensionName;
final private String dimensionValue;
public DimensionValue(String metricName, String dimensionName, String dimensionValue) {
super(metricName, dimensionValue);
this.dimensionName = dimensionName;
this.dimensionValue = dimensionValue;
}
public String getDimensionValue() {
return dimensionValue;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null || getClass() != obj.getClass())
return false;
DimensionValue other = (DimensionValue) obj;
if (dimensionName == null) {
if (other.dimensionName != null)
return false;
} else if (!dimensionName.equals(other.dimensionName))
return false;
if (dimensionValue == null) {
if (other.dimensionValue != null)
return false;
} else if (!dimensionValue.equals(other.dimensionValue))
return false;
return super.equals(obj);
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((dimensionName == null) ? 0 : dimensionName.hashCode());
result = prime * result + ((dimensionValue == null) ? 0 : dimensionValue.hashCode());
return result;
}
@Override
public String toString() {
return String.format("DimensionValue: MetricName=%s DimensionValue [name=%s, values=%s]",
getMetricName(), dimensionName, dimensionValue);
}
}

View File

@ -1,122 +0,0 @@
/*
* 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

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2016 Hewlett-Packard Development Company, L.P. * (C) Copyright 2016 Hewlett Packard Enterprise Development LP
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * 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 * in compliance with the License. You may obtain a copy of the License at
@ -14,21 +14,21 @@
package monasca.api.infrastructure.persistence.influxdb; package monasca.api.infrastructure.persistence.influxdb;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.common.base.Strings;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.Set; import java.util.Set;
import monasca.api.ApiConfig; import monasca.api.ApiConfig;
import monasca.api.domain.model.dimension.DimensionValues; import monasca.api.domain.model.dimension.DimensionName;
import monasca.api.domain.model.dimension.DimensionValue;
import monasca.api.domain.model.dimension.DimensionRepo; import monasca.api.domain.model.dimension.DimensionRepo;
@ -54,7 +54,7 @@ public class InfluxV9DimensionRepo implements DimensionRepo {
} }
@Override @Override
public DimensionValues find( public List<DimensionValue> findValues(
String metricName, String metricName,
String tenantId, String tenantId,
String dimensionName, String dimensionName,
@ -65,6 +65,7 @@ public class InfluxV9DimensionRepo implements DimensionRepo {
// Use treeset to keep list in alphabetic/predictable order // Use treeset to keep list in alphabetic/predictable order
// for string based offset. // for string based offset.
// //
List<DimensionValue> dimensionValueList = new ArrayList<>();
Set<String> matchingValues = new TreeSet<String>(); Set<String> matchingValues = new TreeSet<String>();
String dimNamePart = "and \"" String dimNamePart = "and \""
+ this.influxV9Utils.sanitize(dimensionName) + this.influxV9Utils.sanitize(dimensionName)
@ -93,19 +94,22 @@ public class InfluxV9DimensionRepo implements DimensionRepo {
} }
List<String> filteredValues = filterDimensionValues(matchingValues, List<String> filteredValues = filterDimensionValues(matchingValues,
dimensionName,
limit, limit,
offset); offset);
return new DimensionValues(metricName, dimensionName, filteredValues); for (String filteredValue : filteredValues) {
DimensionValue dimValue = new DimensionValue(metricName, dimensionName, filteredValue);
dimensionValueList.add(dimValue);
}
return dimensionValueList;
} }
private List<String> filterDimensionValues(Set<String> matchingValues, private List<String> filterDimensionValues(Set<String> matchingValues,
String dimensionName,
int limit, int limit,
String offset) String offset)
{ {
Boolean haveOffset = (null != offset && !"".equals(offset)); Boolean haveOffset = !Strings.isNullOrEmpty(offset);
List<String> filteredValues = new ArrayList<String>(); List<String> filteredValues = new ArrayList<String>();
int remaining_limit = limit + 1; int remaining_limit = limit + 1;
@ -122,4 +126,68 @@ public class InfluxV9DimensionRepo implements DimensionRepo {
return filteredValues; return filteredValues;
} }
@Override
public List<DimensionName> findNames(
String metricName,
String tenantId,
String offset,
int limit) throws Exception
{
//
// Use treeset to keep list in alphabetic/predictable order
// for string based offset.
//
List<DimensionName> dimensionNameList = new ArrayList<>();
Set<String> matchingNames = new TreeSet<String>();
String q = String.format("show series %1$s where %2$s",
this.influxV9Utils.namePart(metricName, false),
this.influxV9Utils.privateTenantIdPart(tenantId));
logger.debug("Dimension names 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[] names : serie.getValues()) {
Map<String, String> dimensions = this.influxV9Utils.getDimensions(names, serie.getColumns());
for (Map.Entry<String, String> entry : dimensions.entrySet()) {
matchingNames.add(entry.getKey());
}
}
}
}
List<String> filteredNames = filterDimensionNames(matchingNames, limit, offset);
for (String filteredName : filteredNames) {
DimensionName dimName = new DimensionName(metricName, filteredName);
dimensionNameList.add(dimName);
}
return dimensionNameList;
}
private List<String> filterDimensionNames(Set<String> matchingNames,
int limit,
String offset) {
Boolean haveOffset = !Strings.isNullOrEmpty(offset);
List<String> filteredNames = new ArrayList<String>();
int remaining_limit = limit + 1;
for (String dimName : matchingNames) {
if (remaining_limit <= 0) {
break;
}
if (haveOffset && dimName.compareTo(offset) <= 0) {
continue;
}
filteredNames.add(dimName);
remaining_limit--;
}
return filteredNames;
}
} }

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2016 Hewlett-Packard Development Company, L.P. /* (C) Copyright 2016 Hewlett Packard Enterprise Development LP
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * 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 * in compliance with the License. You may obtain a copy of the License at
@ -13,16 +13,17 @@
package monasca.api.infrastructure.persistence.vertica; package monasca.api.infrastructure.persistence.vertica;
import monasca.api.ApiConfig; import monasca.api.ApiConfig;
import monasca.api.domain.model.dimension.DimensionName;
import monasca.api.domain.model.dimension.DimensionRepo; import monasca.api.domain.model.dimension.DimensionRepo;
import monasca.api.domain.model.dimension.DimensionValues; import monasca.api.domain.model.dimension.DimensionValue;
import com.google.common.base.Strings;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import javax.annotation.Nullable;
import org.skife.jdbi.v2.DBI; import org.skife.jdbi.v2.DBI;
import org.skife.jdbi.v2.Handle; import org.skife.jdbi.v2.Handle;
@ -53,6 +54,23 @@ public class DimensionVerticaRepoImpl implements DimensionRepo {
+ "ORDER BY dims.value ASC " + "ORDER BY dims.value ASC "
+ "%s "; // limit goes here + "%s "; // limit goes here
private static final String FIND_DIMENSION_NAMES_SQL =
"SELECT %s" // dbHint goes here
+ " DISTINCT dims.name as dName "
+ "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 " // metric name goes here
+ " and def.tenant_id = '%s' " // tenant_id goes here
+ "ORDER BY dims.name ASC "
+ "%s "; // limit goes here
private final DBI db; private final DBI db;
private final String dbHint; private final String dbHint;
@ -65,25 +83,24 @@ public class DimensionVerticaRepoImpl implements DimensionRepo {
} }
@Override @Override
public DimensionValues find( public List<DimensionValue> findValues(
String metricName, String metricName,
String tenantId, String tenantId,
String dimensionName, String dimensionName,
String offset, String offset,
int limit) throws Exception int limit) throws Exception
{ {
List<String> values = new ArrayList<String>();
String offsetPart = ""; String offsetPart = "";
String metricNamePart = ""; String metricNamePart = "";
try (Handle h = db.open()) { try (Handle h = db.open()) {
if (offset != null && !offset.isEmpty()) { if (offset != null && !offset.isEmpty()) {
offsetPart = " and dims.value > '" + offset + "' "; offsetPart = " and dims.value > :offset";
} }
if (metricName != null && !metricName.isEmpty()) { if (metricName != null && !metricName.isEmpty()) {
metricNamePart = " and def.name = '" + metricName + "' "; metricNamePart = " and def.name = :metricName";
} }
String limitPart = " limit " + Integer.toString(limit + 1); String limitPart = " limit " + Integer.toString(limit + 1);
@ -98,12 +115,82 @@ public class DimensionVerticaRepoImpl implements DimensionRepo {
Query<Map<String, Object>> query = h.createQuery(sql); Query<Map<String, Object>> query = h.createQuery(sql);
List<Map<String, Object>> rows = query.list();
for (Map<String, Object> row : rows) { if (!Strings.isNullOrEmpty(offset)) {
String dimValue = (String) row.get("dValue"); logger.debug("binding offset: {}", offset);
values.add(dimValue); query.bind("offset", offset);
} }
if (!Strings.isNullOrEmpty(metricName)) {
logger.debug("binding metricName: {}", metricName);
query.bind("metricName", metricName);
}
List<Map<String, Object>> rows = query.list();
List<DimensionValue> dimensionValuesList = new ArrayList<>(rows.size());
for (Map<String, Object> row : rows) {
String dimensionValue = (String) row.get("dValue");
DimensionValue dimValue = new DimensionValue(metricName, dimensionName, dimensionValue);
dimensionValuesList.add(dimValue);
}
return dimensionValuesList;
} }
return new DimensionValues(metricName, dimensionName, values);
}
@Override
public List<DimensionName> findNames(
String metricName,
String tenantId,
String offset,
int limit) throws Exception
{
String offsetPart = "";
String metricNamePart = "";
try (Handle h = db.open()) {
if (!Strings.isNullOrEmpty(offset)) {
offsetPart = " and dims.name > :offset";
}
if (!Strings.isNullOrEmpty(metricName)) {
metricNamePart = " and def.name = :metricName";
}
String limitPart = " limit " + Integer.toString(limit + 1);
String sql = String.format(FIND_DIMENSION_NAMES_SQL,
this.dbHint,
offsetPart,
metricNamePart,
tenantId,
limitPart);
Query<Map<String, Object>> query = h.createQuery(sql);
if (!Strings.isNullOrEmpty(offset)) {
logger.debug("binding offset: {}", offset);
query.bind("offset", offset);
}
if (!Strings.isNullOrEmpty(metricName)) {
logger.debug("binding metricName: {}", metricName);
query.bind("metricName", metricName);
}
List<Map<String, Object>> rows = query.list();
List<DimensionName> dimensionNamesList = new ArrayList<>(rows.size());
for (Map<String, Object> row : rows) {
String dimensionName = (String) row.get("dName");
DimensionName dimName = new DimensionName(metricName, dimensionName);
dimensionNamesList.add(dimName);
}
return dimensionNamesList;
}
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2016 Hewlett-Packard Development Company, L.P. * (C) Copyright 2016 Hewlett Packard Enterprise Development LP
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * 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 * in compliance with the License. You may obtain a copy of the License at
@ -15,12 +15,10 @@ package monasca.api.resource;
import static monasca.api.app.validation.Validation.DEFAULT_ADMIN_ROLE; import static monasca.api.app.validation.Validation.DEFAULT_ADMIN_ROLE;
import com.google.common.base.Strings;
import com.codahale.metrics.annotation.Timed; import com.codahale.metrics.annotation.Timed;
import java.util.List; import java.util.List;
import java.util.Map;
import javax.inject.Inject; import javax.inject.Inject;
import javax.ws.rs.GET; import javax.ws.rs.GET;
@ -32,35 +30,36 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
import monasca.api.app.validation.MetricNameValidation;
import monasca.api.ApiConfig; import monasca.api.ApiConfig;
import monasca.api.app.validation.Validation; import monasca.api.app.validation.Validation;
import monasca.api.domain.model.dimension.DimensionName;
import monasca.api.domain.model.dimension.DimensionRepo; import monasca.api.domain.model.dimension.DimensionRepo;
import monasca.api.domain.model.dimension.DimensionValues; import monasca.api.domain.model.dimension.DimensionValue;
import monasca.api.infrastructure.persistence.PersistUtils; import monasca.api.infrastructure.persistence.PersistUtils;
/** /**
* Dimension resource implementation. * Dimension resource implementation.
*/ */
@Path("/v2.0/metrics/dimensions/names/values") @Path("/v2.0/metrics/dimensions")
public class DimensionResource { public class DimensionResource {
private final DimensionRepo repo; private final DimensionRepo repo;
private final PersistUtils persistUtils; private final PersistUtils persistUtils;
private final String admin_role; private final String adminRole;
@Inject @Inject
public DimensionResource(ApiConfig config, DimensionRepo repo, PersistUtils persistUtils) { public DimensionResource(ApiConfig config, DimensionRepo repo, PersistUtils persistUtils) {
this.admin_role = (config.middleware == null || config.middleware.adminRole == null) this.adminRole = (config.middleware == null || config.middleware.adminRole == null)
? DEFAULT_ADMIN_ROLE : config.middleware.adminRole; ? DEFAULT_ADMIN_ROLE : config.middleware.adminRole;
this.repo = repo; this.repo = repo;
this.persistUtils = persistUtils; this.persistUtils = persistUtils;
} }
@GET @GET
@Path("/names/values")
@Timed @Timed
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public Object get( public Object getDimensionValues(
@Context UriInfo uriInfo, @Context UriInfo uriInfo,
@HeaderParam("X-Tenant-Id") String tenantId, @HeaderParam("X-Tenant-Id") String tenantId,
@HeaderParam("X-Roles") String roles, @HeaderParam("X-Roles") String roles,
@ -71,9 +70,28 @@ public class DimensionResource {
@QueryParam("tenant_id") String crossTenantId) throws Exception @QueryParam("tenant_id") String crossTenantId) throws Exception
{ {
Validation.validateNotNullOrEmpty(dimensionName, "dimension_name"); Validation.validateNotNullOrEmpty(dimensionName, "dimension_name");
final int pagingLimit = this.persistUtils.getLimit(limit);
String queryTenantId = Validation.getQueryProject(roles, crossTenantId, tenantId, adminRole);
List<DimensionValue> dimValues = repo.findValues(metricName, queryTenantId, dimensionName, offset, pagingLimit);
return Links.paginate(pagingLimit, dimValues, uriInfo);
}
@GET
@Path("/names")
@Timed
@Produces(MediaType.APPLICATION_JSON)
public Object getDimensionNames(
@Context UriInfo uriInfo,
@HeaderParam("X-Tenant-Id") String tenantId,
@HeaderParam("X-Roles") String roles,
@QueryParam("limit") String limit,
@QueryParam("metric_name") String metricName,
@QueryParam("offset") String offset,
@QueryParam("tenant_id") String crossTenantId) throws Exception
{
final int paging_limit = this.persistUtils.getLimit(limit); final int paging_limit = this.persistUtils.getLimit(limit);
String queryTenantId = Validation.getQueryProject(roles, crossTenantId, tenantId, admin_role); String queryTenantId = Validation.getQueryProject(roles, crossTenantId, tenantId, adminRole);
DimensionValues dimVals = repo.find(metricName, queryTenantId, dimensionName, offset, paging_limit); List<DimensionName> dimNames = repo.findNames(metricName, queryTenantId, offset, paging_limit);
return Links.paginateDimensionValues(dimVals, paging_limit, uriInfo); return Links.paginate(paging_limit, dimNames, uriInfo);
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* (C) Copyright 2014-2016 Hewlett Packard Enterprise Development Company LP * (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * 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 * in compliance with the License. You may obtain a copy of the License at
@ -26,7 +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.dimension.DimensionBase;
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;
@ -360,21 +360,4 @@ 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

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2016 Hewlett-Packard Development Company, L.P. * (C) Copyright 2016 Hewlett Packard Enterprise Development LP
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * 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 * in compliance with the License. You may obtain a copy of the License at
@ -14,18 +14,11 @@
package monasca.api.resource; 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.anyInt;
import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; 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 org.testng.annotations.Test;
import monasca.api.ApiConfig; import monasca.api.ApiConfig;
@ -49,23 +42,32 @@ public class DimensionResourceTest extends AbstractMonApiResourceTest {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void shouldQueryWithDefaultParams() throws Exception { public void shouldQueryDimensionValuesWithDefaultParams() throws Exception {
client() client()
.resource( .resource(
"/v2.0/metrics/dimensions/names/values?dimension_name=hpcs.compute") "/v2.0/metrics/dimensions/names/values?dimension_name=hpcs.compute")
.header("X-Tenant-Id", "abc").get(ClientResponse.class); .header("X-Tenant-Id", "abc").get(ClientResponse.class);
verify(dimensionRepo).find(anyString(), anyString(), anyString(), anyString(), verify(dimensionRepo).findValues(anyString(), anyString(), anyString(), anyString(),
anyInt()); anyInt());
} }
public void shouldQueryWithOptionalMetricName() throws Exception { public void shouldQueryDimensionValuesWithOptionalMetricName() throws Exception {
client() client()
.resource( .resource(
"/v2.0/metrics/dimensions/names/values?dimension_name=hpcs.compute&metric_name=cpu_utilization") "/v2.0/metrics/dimensions/names/values?dimension_name=hpcs.compute&metric_name=cpu_utilization")
.header("X-Tenant-Id", "abc").get(ClientResponse.class); .header("X-Tenant-Id", "abc").get(ClientResponse.class);
verify(dimensionRepo).find(anyString(), anyString(), anyString(), anyString(), verify(dimensionRepo).findValues(anyString(), anyString(), anyString(), anyString(),
anyInt()); anyInt());
} }
public void shouldQueryDimensionNamesWithDefaultParams() throws Exception {
client()
.resource(
"/v2.0/metrics/dimensions/names")
.header("X-Tenant-Id", "abc").get(ClientResponse.class);
verify(dimensionRepo).findNames(anyString(), anyString(), anyString(), anyInt());
}
} }

View File

@ -1,5 +1,5 @@
/* /*
* (C) Copyright 2015-2016 Hewlett Packard Enterprise Development Company LP * (C) Copyright 2015-2016 Hewlett Packard Enterprise Development LP
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * 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 * in compliance with the License. You may obtain a copy of the License at
@ -18,7 +18,6 @@ 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;
@ -135,49 +134,4 @@ public class LinksTest {
+ 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

@ -1,5 +1,5 @@
# Copyright 2014 IBM Corp # Copyright 2014 IBM Corp
# Copyright 2014 Hewlett-Packard # (C) Copyright 2014,2016 Hewlett Packard Enterprise Development LP
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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 # not use this file except in compliance with the License. You may obtain
@ -64,3 +64,12 @@ class DimensionValuesV2API(object):
def on_get(self, req, res): def on_get(self, req, res):
res.status = '501 Not Implemented' res.status = '501 Not Implemented'
class DimensionNamesV2API(object):
def __init__(self):
super(DimensionNamesV2API, self).__init__()
LOG.info('Initializing DimensionNamesV2API!')
def on_get(self, req, res):
res.status = '501 Not Implemented'

View File

@ -1,5 +1,5 @@
# Copyright 2014 IBM Corp # Copyright 2014 IBM Corp
# Copyright 2015-2016 Hewlett Packard Enterprise Development LP # (C) Copyright 2015,2016 Hewlett Packard Enterprise Development LP
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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 # not use this file except in compliance with the License. You may obtain
@ -46,6 +46,8 @@ dispatcher_opts = [cfg.StrOpt('versions', default=None,
help='Notification methods'), help='Notification methods'),
cfg.StrOpt('dimension_values', default=None, cfg.StrOpt('dimension_values', default=None,
help='Dimension values'), help='Dimension values'),
cfg.StrOpt('dimension_names', default=None,
help='Dimension names'),
cfg.StrOpt('notification_method_types', default=None, cfg.StrOpt('notification_method_types', default=None,
help='notification_method_types methods')] help='notification_method_types methods')]
@ -115,6 +117,9 @@ def launch(conf, config_file="/etc/monasca/api-config.conf"):
dimension_values = simport.load(cfg.CONF.dispatcher.dimension_values)() dimension_values = simport.load(cfg.CONF.dispatcher.dimension_values)()
app.add_route("/v2.0/metrics/dimensions/names/values", dimension_values) app.add_route("/v2.0/metrics/dimensions/names/values", dimension_values)
dimension_names = simport.load(cfg.CONF.dispatcher.dimension_names)()
app.add_route("/v2.0/metrics/dimensions/names", dimension_names)
notification_method_types = simport.load( notification_method_types = simport.load(
cfg.CONF.dispatcher.notification_method_types)() cfg.CONF.dispatcher.notification_method_types)()
app.add_route("/v2.0/notification-methods/types", notification_method_types) app.add_route("/v2.0/notification-methods/types", notification_method_types)

View File

@ -16,7 +16,6 @@
# under the License. # under the License.
from datetime import datetime from datetime import datetime
from datetime import timedelta from datetime import timedelta
import hashlib
import json import json
from influxdb import client from influxdb import client
@ -207,28 +206,11 @@ class MetricsRepository(metrics_repository.AbstractMetricsRepository):
LOG.exception(ex) LOG.exception(ex)
raise exceptions.RepositoryException(ex) raise exceptions.RepositoryException(ex)
def _generate_dimension_values_id(self, metric_name, dimension_name): def _build_serie_dimension_values(self, series_names, dimension_name):
sha1 = hashlib.sha1() dim_values = []
hashstr = "metricName=" + (metric_name or "") + "dimensionName=" + dimension_name json_dim_value_list = []
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: if not series_names:
return json_dim_vals return json_dim_value_list
if 'series' in series_names.raw: if 'series' in series_names.raw:
for series in series_names.raw['series']: for series in series_names.raw['series']:
@ -240,12 +222,29 @@ class MetricsRepository(metrics_repository.AbstractMetricsRepository):
if value and not name.startswith(u'_') if value and not name.startswith(u'_')
} }
if dimension_name in dims and dims[dimension_name] not in dim_vals: if dimension_name in dims and dims[dimension_name] not in\
dim_vals.append(dims[dimension_name]) dim_values:
dim_values.append(dims[dimension_name])
json_dim_value_list.append({u'dimension_value':
dims[dimension_name]})
dim_vals = sorted(dim_vals) json_dim_value_list = sorted(json_dim_value_list)
json_dim_vals[u'values'] = dim_vals return json_dim_value_list
return json_dim_vals
def _build_serie_dimension_names(self, series_names):
dim_names = []
json_dim_name_list = []
if not series_names:
return json_dim_name_list
if 'series' in series_names.raw:
for series in series_names.raw['series']:
for name in series[u'columns']:
if name not in dim_names and not name.startswith(u'_'):
dim_names.append(name)
json_dim_name_list.append({u'dimension_name': name})
json_dim_name_list = sorted(json_dim_name_list)
return json_dim_name_list
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,
@ -619,21 +618,25 @@ class MetricsRepository(metrics_repository.AbstractMetricsRepository):
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, def list_dimension_values(self, tenant_id, region, metric_name,
dimension_name, offset, limit): dimension_name):
try: try:
query = self._build_show_series_query(None, metric_name, tenant_id, region) query = self._build_show_series_query(None, metric_name,
tenant_id, region)
result = self.influxdb_client.query(query) result = self.influxdb_client.query(query)
json_dim_name_list = self._build_serie_dimension_values(
json_dim_vals = self._build_serie_dimension_values(result, result, dimension_name)
metric_name, return json_dim_name_list
dimension_name, except Exception as ex:
tenant_id, LOG.exception(ex)
region, raise exceptions.RepositoryException(ex)
offset)
def list_dimension_names(self, tenant_id, region, metric_name):
return json_dim_vals try:
query = self._build_show_series_query(None, metric_name,
tenant_id, region)
result = self.influxdb_client.query(query)
json_dim_name_list = self._build_serie_dimension_names(result)
return json_dim_name_list
except Exception as ex: except Exception as ex:
LOG.exception(ex) LOG.exception(ex)
raise exceptions.RepositoryException(ex) raise exceptions.RepositoryException(ex)

View File

@ -1,4 +1,5 @@
# Copyright 2015 Cray Inc. All Rights Reserved. # Copyright 2015 Cray Inc. All Rights Reserved.
# (C) Copyright 2016 Hewlett Packard Enterprise Development LP
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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 # not use this file except in compliance with the License. You may obtain
@ -152,18 +153,40 @@ class TestRepoMetricsInfluxDB(unittest.TestCase):
"38dc2a2549f94d2e9a4fa1cc45a4970c", "38dc2a2549f94d2e9a4fa1cc45a4970c",
"useast", "useast",
"custom_metric", "custom_metric",
"hostname", "hostname")
offset=None,
limit=1)
self.assertEqual(result, { self.assertEqual(result, [{u'dimension_value': u'custom_host'}])
u'dimension_name': 'hostname',
u'values': [ @patch("monasca_api.common.repositories.influxdb.metrics_repository.client.InfluxDBClient")
u'custom_host' def test_list_dimension_names(self, influxdb_client_mock):
], mock_client = influxdb_client_mock.return_value
u'id': 'bea9565d854a16a3366164de213694c190f27675', mock_client.query.return_value.raw = {
u'metric_name': 'custom_metric' 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_names(
"38dc2a2549f94d2e9a4fa1cc45a4970c",
"useast",
"custom_metric")
self.assertEqual(result,
[
{u'dimension_name': u'hostname'},
{u'dimension_name': u'service'}
])
class TestRepoMetricsCassandra(testtools.TestCase): class TestRepoMetricsCassandra(testtools.TestCase):

View File

@ -1,5 +1,5 @@
# Copyright 2015 Cray Inc. All Rights Reserved. # Copyright 2015 Cray Inc. All Rights Reserved.
# Copyright 2014, 2016 Hewlett Packard Enterprise Development LP # Copyright 2014,2016 Hewlett Packard Enterprise Development LP
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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 # not use this file except in compliance with the License. You may obtain
@ -463,61 +463,6 @@ 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

@ -1,4 +1,4 @@
# (C) Copyright 2014, 2016 Hewlett Packard Enterprise Development Company LP # (C) Copyright 2014, 2016 Hewlett Packard Enterprise Development LP
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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 # not use this file except in compliance with the License. You may obtain
@ -327,7 +327,8 @@ class DimensionValues(metrics_api_v2.DimensionValuesV2API):
helpers.validate_authorization(req, self._get_metrics_authorized_roles) helpers.validate_authorization(req, self._get_metrics_authorized_roles)
tenant_id = helpers.get_tenant_id(req) tenant_id = helpers.get_tenant_id(req)
metric_name = helpers.get_query_param(req, 'metric_name') metric_name = helpers.get_query_param(req, 'metric_name')
dimension_name = helpers.get_query_param(req, 'dimension_name', required=True) dimension_name = helpers.get_query_param(req, 'dimension_name',
required=True)
offset = helpers.get_query_param(req, 'offset') offset = helpers.get_query_param(req, 'offset')
limit = helpers.get_limit(req) limit = helpers.get_limit(req)
result = self._dimension_values(tenant_id, req.uri, metric_name, result = self._dimension_values(tenant_id, req.uri, metric_name,
@ -342,8 +343,43 @@ class DimensionValues(metrics_api_v2.DimensionValuesV2API):
result = self._metrics_repo.list_dimension_values(tenant_id, result = self._metrics_repo.list_dimension_values(tenant_id,
self._region, self._region,
metric_name, metric_name,
dimension_name, dimension_name)
offset,
limit)
return helpers.paginate_dimension_values(result, req_uri, offset, limit) return helpers.paginate_with_no_id(result, req_uri, offset, limit)
class DimensionNames(metrics_api_v2.DimensionNamesV2API):
def __init__(self):
try:
super(DimensionNames, self).__init__()
self._region = cfg.CONF.region
self._get_metrics_authorized_roles = (
cfg.CONF.security.default_authorized_roles +
cfg.CONF.security.read_only_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._get_metrics_authorized_roles)
tenant_id = helpers.get_tenant_id(req)
metric_name = helpers.get_query_param(req, 'metric_name')
offset = helpers.get_query_param(req, 'offset')
limit = helpers.get_limit(req)
result = self._dimension_names(tenant_id, req.uri, metric_name,
offset, limit)
res.body = helpers.dumpit_utf8(result)
res.status = falcon.HTTP_200
@resource.resource_try_catch_block
def _dimension_names(self, tenant_id, req_uri, metric_name, offset, limit):
result = self._metrics_repo.list_dimension_names(tenant_id,
self._region,
metric_name)
return helpers.paginate_with_no_id(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_names(self, query_params=None):
uri = 'metrics/dimensions/names'
if query_params is not None:
uri = uri + query_params
resp, response_body = self.get(uri)
return resp, json.loads(response_body)
def list_dimension_values(self, query_params=None): def list_dimension_values(self, query_params=None):
uri = 'metrics/dimensions/names/values' uri = 'metrics/dimensions/names/values'
if query_params is not None: if query_params is not None:

View File

@ -93,5 +93,4 @@ class BaseMonascaTest(tempest.test.BaseTestCase):
query_params = urlparse.parse_qs(urlparse.urlparse(next_link).query) query_params = urlparse.parse_qs(urlparse.urlparse(next_link).query)
if 'offset' not in query_params: if 'offset' not in query_params:
self.fail("No offset in next link: {}".format(next_link)) self.fail("No offset in next link: {}".format(next_link))
return query_params['offset'][0] return query_params['offset'][0]

View File

@ -1,4 +1,4 @@
# (C) Copyright 2016 Hewlett Packard Enterprise Development Company LP # (C) Copyright 2016 Hewlett Packard Enterprise Development LP
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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 # not use this file except in compliance with the License. You may obtain
@ -14,14 +14,13 @@
import time import time
from oslo_utils import timeutils
from monasca_tempest_tests.tests.api import base from monasca_tempest_tests.tests.api import base
from monasca_tempest_tests.tests.api import constants from monasca_tempest_tests.tests.api import constants
from monasca_tempest_tests.tests.api import helpers from monasca_tempest_tests.tests.api import helpers
from tempest.common.utils import data_utils from tempest.common.utils import data_utils
from tempest import test from tempest import test
from tempest.lib import exceptions from tempest.lib import exceptions
from urllib import urlencode
class TestDimensions(base.BaseMonascaTest): class TestDimensions(base.BaseMonascaTest):
@ -29,34 +28,56 @@ class TestDimensions(base.BaseMonascaTest):
@classmethod @classmethod
def resource_setup(cls): def resource_setup(cls):
super(TestDimensions, cls).resource_setup() super(TestDimensions, cls).resource_setup()
name = data_utils.rand_name() metric_name1 = data_utils.rand_name()
key = data_utils.rand_name() name1 = "name_1"
name2 = "name_2"
value1 = "value_1" value1 = "value_1"
value2 = "value_2" 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( timestamp = int(round(time.time() * 1000))
metric['timestamp'] / 1000.0)) time_iso = helpers.timestamp_to_iso(timestamp)
parms = '?name=' + str(cls._test_metric['name']) + \
'&start_time=' + start_time
metric1 = helpers.create_metric(name=metric_name1,
dimensions={name1: value1,
name2: value2
})
cls.monasca_client.create_metrics(metric1)
metric1 = helpers.create_metric(name=metric_name1,
dimensions={name1: value2})
cls.monasca_client.create_metrics(metric1)
metric_name2 = data_utils.rand_name()
name3 = "name_3"
value3 = "value_3"
metric2 = helpers.create_metric(name=metric_name2,
dimensions={name3: value3})
cls.monasca_client.create_metrics(metric2)
metric_name3 = data_utils.rand_name()
metric3 = helpers.create_metric(name=metric_name3,
dimensions={name1: value3})
cls.monasca_client.create_metrics(metric3)
cls._test_metric1 = metric1
cls._test_metric2 = metric2
cls._test_metric_names = {metric_name1, metric_name2, metric_name3}
cls._dim_names_metric1 = [name1, name2]
cls._dim_names_metric2 = [name3]
cls._dim_names = cls._dim_names_metric1 + cls._dim_names_metric2
cls._dim_values_for_metric1 = [value1, value2]
cls._dim_values = [value1, value2, value3]
param = '?start_time=' + time_iso
returned_name_set = set()
for i in xrange(constants.MAX_RETRIES): for i in xrange(constants.MAX_RETRIES):
resp, response_body = cls.monasca_client.list_metrics( resp, response_body = cls.monasca_client.list_metrics(
parms) param)
elements = response_body['elements'] elements = response_body['elements']
for element in elements: for element in elements:
if str(element['name']) == cls._test_metric['name']: returned_name_set.add(str(element['name']))
return if cls._test_metric_names.issubset(returned_name_set):
return
time.sleep(constants.RETRY_WAIT_SECS) time.sleep(constants.RETRY_WAIT_SECS)
assert False, 'Unable to initialize metrics' assert False, 'Unable to initialize metrics'
@ -67,46 +88,73 @@ class TestDimensions(base.BaseMonascaTest):
@test.attr(type='gate') @test.attr(type='gate')
def test_list_dimension_values_without_metric_name(self): def test_list_dimension_values_without_metric_name(self):
parms = '?dimension_name=' + self._dimension_name param = '?dimension_name=' + self._dim_names[0]
resp, response_body = self.monasca_client.list_dimension_values(parms) resp, response_body = self.monasca_client.list_dimension_values(param)
self.assertEqual(200, resp.status) self.assertEqual(200, resp.status)
self.assertTrue(set(['links', 'elements']) == set(response_body)) self.assertTrue({'links', 'elements'} == set(response_body))
if not self._is_dimension_name_in_list(response_body): response_values_length = len(response_body['elements'])
self.fail('Dimension name not found in response') values = [str(response_body['elements'][i]['dimension_value'])
if self._is_metric_name_in_list(response_body): for i in xrange(response_values_length)]
self.fail('Metric name was in response and should not be') self.assertEqual(values, self._dim_values)
if not self._are_dim_vals_in_list(response_body):
self.fail('Dimension value not found in response')
@test.attr(type='gate') @test.attr(type='gate')
def test_list_dimension_values_with_metric_name(self): def test_list_dimension_values_with_metric_name(self):
parms = '?metric_name=' + self._test_metric['name'] parms = '?metric_name=' + self._test_metric1['name']
parms += '&dimension_name=' + self._dimension_name parms += '&dimension_name=' + self._dim_names[0]
resp, response_body = self.monasca_client.list_dimension_values(parms) resp, response_body = self.monasca_client.list_dimension_values(parms)
self.assertEqual(200, resp.status) self.assertEqual(200, resp.status)
self.assertTrue(set(['links', 'elements']) == set(response_body)) self.assertTrue({'links', 'elements'} == set(response_body))
if not self._is_metric_name_in_list(response_body): response_values_length = len(response_body['elements'])
self.fail('Metric name not found in response') values = [str(response_body['elements'][i]['dimension_value'])
if not self._is_dimension_name_in_list(response_body): for i in xrange(response_values_length)]
self.fail('Dimension name not found in response') self.assertEqual(values, self._dim_values_for_metric1)
if not self._are_dim_vals_in_list(response_body):
self.fail('Dimension value not found in response')
@test.attr(type='gate') @test.attr(type='gate')
def test_list_dimension_values_limit_and_offset(self): def test_list_dimension_values_limit_and_offset(self):
parms = '?dimension_name=' + self._dimension_name param = '?dimension_name=' + self._dim_names[0]
parms += '&limit=1' resp, response_body = self.monasca_client.list_dimension_values(param)
resp, response_body = self.monasca_client.list_dimension_values(parms)
self.assertEqual(200, resp.status) self.assertEqual(200, resp.status)
self.assertTrue(set(['links', 'elements']) == set(response_body)) elements = response_body['elements']
if not self._is_dimension_name_in_list(response_body): num_dim_values = len(elements)
self.fail('Dimension name not found in response') for limit in xrange(1, num_dim_values):
if not self._is_dim_val_in_list(response_body, self._dim_val_1): start_index = 0
self.fail('First dimension value not found in response') params = [('limit', limit)]
if not self._is_offset_in_links(response_body, self._dim_val_1): offset = None
self.fail('Offset not found in response') while True:
if not self._is_limit_in_links(response_body): num_expected_elements = limit
self.fail('Limit not found in response') if (num_expected_elements + start_index) > num_dim_values:
num_expected_elements = num_dim_values - start_index
these_params = list(params)
# If not the first call, use the offset returned by the last
# call
if offset:
these_params.extend([('offset', str(offset))])
query_parms = '?dimension_name=' + self._dim_names[0] + '&' + \
urlencode(these_params)
resp, response_body = \
self.monasca_client.list_dimension_values(query_parms)
self.assertEqual(200, resp.status)
if not response_body['elements']:
self.fail("No metrics returned")
response_values_length = len(response_body['elements'])
if response_values_length == 0:
self.fail("No dimension names returned")
new_elements = [str(response_body['elements'][i]
['dimension_value']) for i in
xrange(response_values_length)]
self.assertEqual(num_expected_elements, len(new_elements))
expected_elements = elements[start_index:start_index+limit]
expected_dimension_values = \
[expected_elements[i]['dimension_value'] for i in xrange(
len(expected_elements))]
self.assertEqual(expected_dimension_values, new_elements)
start_index += num_expected_elements
if start_index >= num_dim_values:
break
# Get the next set
offset = self._get_offset(response_body)
@test.attr(type='gate') @test.attr(type='gate')
@test.attr(type=['negative']) @test.attr(type=['negative'])
@ -114,46 +162,81 @@ class TestDimensions(base.BaseMonascaTest):
self.assertRaises(exceptions.UnprocessableEntity, self.assertRaises(exceptions.UnprocessableEntity,
self.monasca_client.list_dimension_values) self.monasca_client.list_dimension_values)
def _is_metric_name_in_list(self, response_body): @test.attr(type='gate')
elements = response_body['elements'][0] def test_list_dimension_names(self):
if 'metric_name' not in elements: resp, response_body = self.monasca_client.list_dimension_names()
return False self.assertEqual(200, resp.status)
if str(elements['metric_name']) == self._test_metric['name']: self.assertTrue({'links', 'elements'} == set(response_body))
return True response_names_length = len(response_body['elements'])
return False names = [str(response_body['elements'][i]['dimension_name']) for i
in xrange(response_names_length)]
self.assertEqual(names, self._dim_names)
def _is_dimension_name_in_list(self, response_body): @test.attr(type='gate')
elements = response_body['elements'][0] def test_list_dimension_names_with_metric_name(self):
if str(elements['dimension_name']) == self._dimension_name: self._test_list_dimension_names_with_metric_name(
return True self._test_metric1['name'], self._dim_names_metric1)
return False self._test_list_dimension_names_with_metric_name(
self._test_metric2['name'], self._dim_names_metric2)
def _are_dim_vals_in_list(self, response_body): @test.attr(type='gate')
elements = response_body['elements'][0] def test_list_dimension_names_limit_and_offset(self):
have_dim_1 = self._is_dim_val_in_list(response_body, self._dim_val_1) resp, response_body = self.monasca_client.list_dimension_names()
have_dim_2 = self._is_dim_val_in_list(response_body, self._dim_val_2) self.assertEqual(200, resp.status)
if have_dim_1 and have_dim_1: elements = response_body['elements']
return True num_dim_names = len(elements)
return False for limit in xrange(1, num_dim_names):
start_index = 0
params = [('limit', limit)]
offset = None
while True:
num_expected_elements = limit
if (num_expected_elements + start_index) > num_dim_names:
num_expected_elements = num_dim_names - start_index
def _is_dim_val_in_list(self, response_body, dim_val): these_params = list(params)
elements = response_body['elements'][0] # If not the first call, use the offset returned by the last
if dim_val in elements['values']: # call
return True if offset:
return False these_params.extend([('offset', str(offset))])
query_parms = '?' + urlencode(these_params)
resp, response_body = self.monasca_client.list_dimension_names(
query_parms)
self.assertEqual(200, resp.status)
if not response_body['elements']:
self.fail("No metrics returned")
response_names_length = len(response_body['elements'])
if response_names_length == 0:
self.fail("No dimension names returned")
new_elements = [str(response_body['elements'][i]
['dimension_name']) for i in
xrange(response_names_length)]
self.assertEqual(num_expected_elements, len(new_elements))
def _is_offset_in_links(self, response_body, dim_val): expected_elements = elements[start_index:start_index+limit]
links = response_body['links'] expected_dimension_names = \
offset = "offset=" + dim_val [expected_elements[i]['dimension_name'] for i in xrange(
for link in links: len(expected_elements))]
if offset in link['href']: self.assertEqual(expected_dimension_names, new_elements)
return True start_index += num_expected_elements
return False if start_index >= num_dim_names:
break
# Get the next set
offset = self._get_offset(response_body)
def _is_limit_in_links(self, response_body): @test.attr(type='gate')
links = response_body['links'] @test.attr(type=['negative'])
limit = "limit=1" def test_list_dimension_names_with_wrong_metric_name(self):
for link in links: self._test_list_dimension_names_with_metric_name(
if limit in link['href']: 'wrong_metric_name', [])
return True
return False def _test_list_dimension_names_with_metric_name(self, metric_name,
dimension_names):
param = '?metric_name=' + metric_name
resp, response_body = self.monasca_client.list_dimension_names(param)
self.assertEqual(200, resp.status)
self.assertTrue(set(['links', 'elements']) == set(response_body))
response_names_length = len(response_body['elements'])
names = [str(response_body['elements'][i]['dimension_name']) for i
in xrange(response_names_length)]
self.assertEqual(names, dimension_names)

View File

@ -1,4 +1,4 @@
# # (C) Copyright 2016 Hewlett Packard Enterprise Development LP
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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 # not use this file except in compliance with the License. You may obtain
# a copy of the License at # a copy of the License at
@ -13,8 +13,6 @@
import time import time
import six.moves.urllib.parse as urlparse
from monasca_tempest_tests.tests.api import base from monasca_tempest_tests.tests.api import base
from monasca_tempest_tests.tests.api import helpers from monasca_tempest_tests.tests.api import helpers
from tempest import test from tempest import test
@ -22,6 +20,7 @@ from tempest.lib import exceptions
from monasca_tempest_tests import clients from monasca_tempest_tests import clients
class TestReadOnlyRole(base.BaseMonascaTest): class TestReadOnlyRole(base.BaseMonascaTest):
@classmethod @classmethod
@ -112,7 +111,19 @@ class TestReadOnlyRole(base.BaseMonascaTest):
# #
url = '/v2.0/metrics/dimensions/names/values?dimension_name=foo' url = '/v2.0/metrics/dimensions/names/values?dimension_name=foo'
self.assertEqual(200, resp.status) self.assertEqual(200, resp.status)
self.assertEqual(0, len(response_body['elements'][0]['values'])) self.assertEqual(0, len(response_body['elements']))
self.assertTrue(response_body['links'][0]['href'].endswith(url))
@test.attr(type="gate")
def test_list_dimension_names_success(self):
resp, response_body = self.monasca_client.list_dimension_names()
#
# Validate the call succeeds with empty result (we didn't
# create any metrics/dimensions)
#
url = '/v2.0/metrics/dimensions/names'
self.assertEqual(200, resp.status)
self.assertEqual(0, len(response_body['elements']))
self.assertTrue(response_body['links'][0]['href'].endswith(url)) self.assertTrue(response_body['links'][0]['href'].endswith(url))
@test.attr(type="gate") @test.attr(type="gate")
@ -129,7 +140,6 @@ class TestReadOnlyRole(base.BaseMonascaTest):
self.assertEqual(0, len(response_body['elements'])) self.assertEqual(0, len(response_body['elements']))
self.assertTrue('/v2.0/metrics/measurements' in response_body['links'][0]['href']) self.assertTrue('/v2.0/metrics/measurements' in response_body['links'][0]['href'])
@test.attr(type="gate") @test.attr(type="gate")
def test_list_statistics_success(self): def test_list_statistics_success(self):
start_timestamp = int(time.time() * 1000) start_timestamp = int(time.time() * 1000)