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
notification_methods = monasca_api.v2.reference.notifications:Notifications
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
[security]

View File

@ -94,9 +94,8 @@ Document Version: v2.0
- [Status Code](#status-code-2)
- [Response Body](#response-body-4)
- [Response Examples](#response-examples-3)
- [Measurements](#measurements)
- [List measurements](#list-measurements)
- [GET /v2.0/metrics/measurements](#get-v20metricsmeasurements)
- [List dimension names](#list-dimension-names)
- [GET /v2.0/metrics/dimensions/names](#get-v20metricsdimensionsnames)
- [Headers](#headers-5)
- [Path Parameters](#path-parameters-5)
- [Query Parameters](#query-parameters-5)
@ -106,9 +105,9 @@ Document Version: v2.0
- [Status Code](#status-code-3)
- [Response Body](#response-body-5)
- [Response Examples](#response-examples-4)
- [Metric Names](#metric-names)
- [List names](#list-names)
- [GET /v2.0/metrics/names](#get-v20metricsnames)
- [Measurements](#measurements)
- [List measurements](#list-measurements)
- [GET /v2.0/metrics/measurements](#get-v20metricsmeasurements)
- [Headers](#headers-6)
- [Path Parameters](#path-parameters-6)
- [Query Parameters](#query-parameters-6)
@ -118,9 +117,9 @@ Document Version: v2.0
- [Status Code](#status-code-4)
- [Response Body](#response-body-6)
- [Response Examples](#response-examples-5)
- [Statistics](#statistics)
- [List statistics](#list-statistics)
- [GET /v2.0/metrics/statistics](#get-v20metricsstatistics)
- [Metric Names](#metric-names)
- [List names](#list-names)
- [GET /v2.0/metrics/names](#get-v20metricsnames)
- [Headers](#headers-7)
- [Path Parameters](#path-parameters-7)
- [Query Parameters](#query-parameters-7)
@ -130,9 +129,9 @@ Document Version: v2.0
- [Status Code](#status-code-5)
- [Response Body](#response-body-7)
- [Response Examples](#response-examples-6)
- [Notification Methods](#notification-methods-1)
- [Create Notification Method](#create-notification-method)
- [POST /v2.0/notification-methods](#post-v20notification-methods)
- [Statistics](#statistics)
- [List statistics](#list-statistics)
- [GET /v2.0/metrics/statistics](#get-v20metricsstatistics)
- [Headers](#headers-8)
- [Path Parameters](#path-parameters-8)
- [Query Parameters](#query-parameters-8)
@ -142,8 +141,9 @@ Document Version: v2.0
- [Status Code](#status-code-6)
- [Response Body](#response-body-8)
- [Response Examples](#response-examples-7)
- [List Notification Methods](#list-notification-methods)
- [GET /v2.0/notification-methods](#get-v20notification-methods)
- [Notification Methods](#notification-methods-1)
- [Create Notification Method](#create-notification-method)
- [POST /v2.0/notification-methods](#post-v20notification-methods)
- [Headers](#headers-9)
- [Path Parameters](#path-parameters-9)
- [Query Parameters](#query-parameters-9)
@ -153,8 +153,8 @@ Document Version: v2.0
- [Status Code](#status-code-7)
- [Response Body](#response-body-9)
- [Response Examples](#response-examples-8)
- [Get Notification Method](#get-notification-method)
- [GET /v2.0/notification-methods/{notification_method_id}](#get-v20notification-methodsnotification_method_id)
- [List Notification Methods](#list-notification-methods)
- [GET /v2.0/notification-methods](#get-v20notification-methods)
- [Headers](#headers-10)
- [Path Parameters](#path-parameters-10)
- [Query Parameters](#query-parameters-10)
@ -164,8 +164,8 @@ Document Version: v2.0
- [Status Code](#status-code-8)
- [Response Body](#response-body-10)
- [Response Examples](#response-examples-9)
- [Update Notification Method](#update-notification-method)
- [PUT /v2.0/notification-methods/{notification_method_id}](#put-v20notification-methodsnotification_method_id)
- [Get Notification Method](#get-notification-method)
- [GET /v2.0/notification-methods/{notification_method_id}](#get-v20notification-methodsnotification_method_id)
- [Headers](#headers-11)
- [Path Parameters](#path-parameters-11)
- [Query Parameters](#query-parameters-11)
@ -175,8 +175,8 @@ Document Version: v2.0
- [Status Code](#status-code-9)
- [Response Body](#response-body-11)
- [Response Examples](#response-examples-10)
- [Patch Notification Method](#patch-notification-method)
- [PATCH /v2.0/notification-methods/{notification_method_id}](#patch-v20notification-methodsnotification_method_id)
- [Update Notification Method](#update-notification-method)
- [PUT /v2.0/notification-methods/{notification_method_id}](#put-v20notification-methodsnotification_method_id)
- [Headers](#headers-12)
- [Path Parameters](#path-parameters-12)
- [Query Parameters](#query-parameters-12)
@ -186,8 +186,8 @@ Document Version: v2.0
- [Status Code](#status-code-10)
- [Response Body](#response-body-12)
- [Response Examples](#response-examples-11)
- [Delete Notification Method](#delete-notification-method)
- [DELETE /v2.0/notification-methods/{notification_method_id}](#delete-v20notification-methodsnotification_method_id)
- [Patch Notification Method](#patch-notification-method)
- [PATCH /v2.0/notification-methods/{notification_method_id}](#patch-v20notification-methodsnotification_method_id)
- [Headers](#headers-13)
- [Path Parameters](#path-parameters-13)
- [Query Parameters](#query-parameters-13)
@ -196,21 +196,20 @@ Document Version: v2.0
- [Response](#response-13)
- [Status Code](#status-code-11)
- [Response Body](#response-body-13)
- [List supported Notification Method Types](#list-supported-notification-method-types)
- [GET /v2.0/notification-methods/types/](#get-v20notification-methodstypes)
- [Response Examples](#response-examples-12)
- [Delete Notification Method](#delete-notification-method)
- [DELETE /v2.0/notification-methods/{notification_method_id}](#delete-v20notification-methodsnotification_method_id)
- [Headers](#headers-14)
- [Path Parameters](#path-parameters-14)
- [Query Parameters](#query-parameters-14)
- [Request Body](#request-body-14)
- [Request Examples](#request-examples-14)
- [Response](#response-14)
- [Status Code](#status-code-12)
- [Response Body](#response-body-14)
- [Response Examples](#response-examples-12)
- [Alarm Definitions](#alarm-definitions)
- [Create Alarm Definition](#create-alarm-definition)
- [POST /v2.0/alarm-definitions](#post-v20alarm-definitions)
- [List supported Notification Method Types](#list-supported-notification-method-types)
- [GET /v2.0/notification-methods/types/](#get-v20notification-methodstypes)
- [Headers](#headers-15)
- [Path Parameters](#path-parameters-14)
- [Query Parameters](#query-parameters-15)
- [Request Body](#request-body-15)
- [Request Examples](#request-examples-15)
@ -218,8 +217,9 @@ Document Version: v2.0
- [Status Code](#status-code-13)
- [Response Body](#response-body-15)
- [Response Examples](#response-examples-13)
- [List Alarm Definitions](#list-alarm-definitions)
- [GET /v2.0/alarm-definitions](#get-v20alarm-definitions)
- [Alarm Definitions](#alarm-definitions)
- [Create Alarm Definition](#create-alarm-definition)
- [POST /v2.0/alarm-definitions](#post-v20alarm-definitions)
- [Headers](#headers-16)
- [Path Parameters](#path-parameters-15)
- [Query Parameters](#query-parameters-16)
@ -229,29 +229,29 @@ Document Version: v2.0
- [Status Code](#status-code-14)
- [Response Body](#response-body-16)
- [Response Examples](#response-examples-14)
- [Get Alarm Definition](#get-alarm-definition)
- [GET /v2.0/alarm-definitions/{alarm_definition_id}](#get-v20alarm-definitionsalarm_definition_id)
- [List Alarm Definitions](#list-alarm-definitions)
- [GET /v2.0/alarm-definitions](#get-v20alarm-definitions)
- [Headers](#headers-17)
- [Path Parameters](#path-parameters-16)
- [Query Parameters](#query-parameters-17)
- [Request Body](#request-body-17)
- [Request Examples](#request-examples-17)
- [Response](#response-17)
- [Status Code](#status-code-15)
- [Response Body](#response-body-17)
- [Response Examples](#response-examples-15)
- [Update Alarm Definition](#update-alarm-definition)
- [PUT /v2.0/alarm-definitions/{alarm_definition_id}](#put-v20alarm-definitionsalarm_definition_id)
- [Get Alarm Definition](#get-alarm-definition)
- [GET /v2.0/alarm-definitions/{alarm_definition_id}](#get-v20alarm-definitionsalarm_definition_id)
- [Headers](#headers-18)
- [Path Parameters](#path-parameters-17)
- [Query Parameters](#query-parameters-18)
- [Request Body](#request-body-18)
- [Request Examples](#request-examples-17)
- [Response](#response-18)
- [Status Code](#status-code-16)
- [Response Body](#response-body-18)
- [Response Examples](#response-examples-16)
- [Patch Alarm Definition](#patch-alarm-definition)
- [PATCH /v2.0/alarm-definitions/{alarm_definition_id}](#patch-v20alarm-definitionsalarm_definition_id)
- [Update Alarm Definition](#update-alarm-definition)
- [PUT /v2.0/alarm-definitions/{alarm_definition_id}](#put-v20alarm-definitionsalarm_definition_id)
- [Headers](#headers-19)
- [Path Parameters](#path-parameters-18)
- [Query Parameters](#query-parameters-19)
@ -261,8 +261,8 @@ Document Version: v2.0
- [Status Code](#status-code-17)
- [Response Body](#response-body-19)
- [Response Examples](#response-examples-17)
- [Delete Alarm Definition](#delete-alarm-definition)
- [DELETE /v2.0/alarm-definitions/{alarm_definition_id}](#delete-v20alarm-definitionsalarm_definition_id)
- [Patch Alarm Definition](#patch-alarm-definition)
- [PATCH /v2.0/alarm-definitions/{alarm_definition_id}](#patch-v20alarm-definitionsalarm_definition_id)
- [Headers](#headers-20)
- [Path Parameters](#path-parameters-19)
- [Query Parameters](#query-parameters-20)
@ -271,9 +271,9 @@ Document Version: v2.0
- [Response](#response-20)
- [Status Code](#status-code-18)
- [Response Body](#response-body-20)
- [Alarms](#alarms)
- [List Alarms](#list-alarms)
- [GET /v2.0/alarms](#get-v20alarms)
- [Response Examples](#response-examples-18)
- [Delete Alarm Definition](#delete-alarm-definition)
- [DELETE /v2.0/alarm-definitions/{alarm_definition_id}](#delete-v20alarm-definitionsalarm_definition_id)
- [Headers](#headers-21)
- [Path Parameters](#path-parameters-20)
- [Query Parameters](#query-parameters-21)
@ -282,19 +282,20 @@ Document Version: v2.0
- [Response](#response-21)
- [Status Code](#status-code-19)
- [Response Body](#response-body-21)
- [Response Examples](#response-examples-18)
- [List Alarms State History](#list-alarms-state-history)
- [GET /v2.0/alarms/state-history](#get-v20alarmsstate-history)
- [Alarms](#alarms)
- [List Alarms](#list-alarms)
- [GET /v2.0/alarms](#get-v20alarms)
- [Headers](#headers-22)
- [Path Parameters](#path-parameters-21)
- [Query Parameters](#query-parameters-22)
- [Request Body](#request-body-22)
- [Request Examples](#request-examples-21)
- [Response](#response-22)
- [Status Code](#status-code-20)
- [Response Body](#response-body-22)
- [Response Examples](#response-examples-19)
- [Get Alarm](#get-alarm)
- [GET /v2.0/alarms/{alarm_id}](#get-v20alarmsalarm_id)
- [List Alarms State History](#list-alarms-state-history)
- [GET /v2.0/alarms/state-history](#get-v20alarmsstate-history)
- [Headers](#headers-23)
- [Path Parameters](#path-parameters-22)
- [Query Parameters](#query-parameters-23)
@ -303,19 +304,18 @@ Document Version: v2.0
- [Status Code](#status-code-21)
- [Response Body](#response-body-23)
- [Response Examples](#response-examples-20)
- [Update Alarm](#update-alarm)
- [PUT /v2.0/alarms/{alarm_id}](#put-v20alarmsalarm_id)
- [Get Alarm](#get-alarm)
- [GET /v2.0/alarms/{alarm_id}](#get-v20alarmsalarm_id)
- [Headers](#headers-24)
- [Path Parameters](#path-parameters-23)
- [Query Parameters](#query-parameters-24)
- [Request Body](#request-body-24)
- [Request Examples](#request-examples-21)
- [Response](#response-24)
- [Status Code](#status-code-22)
- [Response Body](#response-body-24)
- [Response Examples](#response-examples-21)
- [Patch Alarm](#patch-alarm)
- [PATCH /v2.0/alarms/{alarm_id}](#patch-v20alarmsalarm_id)
- [Update Alarm](#update-alarm)
- [PUT /v2.0/alarms/{alarm_id}](#put-v20alarmsalarm_id)
- [Headers](#headers-25)
- [Path Parameters](#path-parameters-24)
- [Query Parameters](#query-parameters-25)
@ -325,8 +325,8 @@ Document Version: v2.0
- [Status Code](#status-code-23)
- [Response Body](#response-body-25)
- [Response Examples](#response-examples-22)
- [Delete Alarm](#delete-alarm)
- [DELETE /v2.0/alarms/{alarm_id}](#delete-v20alarmsalarm_id)
- [Patch Alarm](#patch-alarm)
- [PATCH /v2.0/alarms/{alarm_id}](#patch-v20alarmsalarm_id)
- [Headers](#headers-26)
- [Path Parameters](#path-parameters-25)
- [Query Parameters](#query-parameters-26)
@ -335,17 +335,28 @@ Document Version: v2.0
- [Response](#response-26)
- [Status Code](#status-code-24)
- [Response Body](#response-body-26)
- [List Alarm State History](#list-alarm-state-history)
- [GET /v2.0/alarms/{alarm_id}/state-history](#get-v20alarmsalarm_idstate-history)
- [Response Examples](#response-examples-23)
- [Delete Alarm](#delete-alarm)
- [DELETE /v2.0/alarms/{alarm_id}](#delete-v20alarmsalarm_id)
- [Headers](#headers-27)
- [Path Parameters](#path-parameters-26)
- [Query Parameters](#query-parameters-27)
- [Request Body](#request-body-27)
- [Request Data](#request-data)
- [Request Examples](#request-examples-24)
- [Response](#response-27)
- [Status Code](#status-code-25)
- [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)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
@ -1194,40 +1205,93 @@ Cache-Control: no-cache
* 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)
Returns a JSON object with a 'links' array of links and an 'elements' array of dimension values.
#### 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"
]
}
]
"elements": [
{
"dimension_value": "value2"
},
{
"dimension_value": "value3"
}
],
"links": [
{
"href": "http://192.168.10.6:8070/v2.0/metrics/dimensions/names/values?dimension_name=dim_name&offset=value1&limit=2",
"rel": "self"
},
{
"href": "http://192.168.10.6:8070/v2.0/metrics/dimensions/names/values?offset=value3&dimension_name=dim_name&limit=2",
"rel": "next"
}
]
}
````
___
## 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
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
notification_methods = monasca_api.v2.reference.notifications:Notifications
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
[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
* in compliance with the License. You may obtain a copy of the License at
@ -13,10 +13,10 @@
*/
package monasca.api.domain.model.dimension;
import java.util.List;
import javax.annotation.Nullable;
import java.util.List;
/**
* Repository for dimensions.
*/
@ -25,10 +25,19 @@ 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)
List<DimensionValue> findValues(String metricName,
String tenantId,
String dimensionName,
@Nullable String offset,
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;
}

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
* in compliance with the License. You may obtain a copy of the License at
@ -14,21 +14,21 @@
package monasca.api.infrastructure.persistence.influxdb;
import com.google.inject.Inject;
import com.google.common.base.Strings;
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.DimensionName;
import monasca.api.domain.model.dimension.DimensionValue;
import monasca.api.domain.model.dimension.DimensionRepo;
@ -54,7 +54,7 @@ public class InfluxV9DimensionRepo implements DimensionRepo {
}
@Override
public DimensionValues find(
public List<DimensionValue> findValues(
String metricName,
String tenantId,
String dimensionName,
@ -65,6 +65,7 @@ public class InfluxV9DimensionRepo implements DimensionRepo {
// Use treeset to keep list in alphabetic/predictable order
// for string based offset.
//
List<DimensionValue> dimensionValueList = new ArrayList<>();
Set<String> matchingValues = new TreeSet<String>();
String dimNamePart = "and \""
+ this.influxV9Utils.sanitize(dimensionName)
@ -93,19 +94,22 @@ public class InfluxV9DimensionRepo implements DimensionRepo {
}
List<String> filteredValues = filterDimensionValues(matchingValues,
dimensionName,
limit,
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,
String dimensionName,
int limit,
String offset)
{
Boolean haveOffset = (null != offset && !"".equals(offset));
Boolean haveOffset = !Strings.isNullOrEmpty(offset);
List<String> filteredValues = new ArrayList<String>();
int remaining_limit = limit + 1;
@ -122,4 +126,68 @@ public class InfluxV9DimensionRepo implements DimensionRepo {
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
* in compliance with the License. You may obtain a copy of the License at
@ -13,16 +13,17 @@
package monasca.api.infrastructure.persistence.vertica;
import monasca.api.ApiConfig;
import monasca.api.domain.model.dimension.DimensionName;
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.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;
@ -53,6 +54,23 @@ public class DimensionVerticaRepoImpl implements DimensionRepo {
+ "ORDER BY dims.value ASC "
+ "%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 String dbHint;
@ -65,25 +83,24 @@ public class DimensionVerticaRepoImpl implements DimensionRepo {
}
@Override
public DimensionValues find(
public List<DimensionValue> findValues(
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 + "' ";
offsetPart = " and dims.value > :offset";
}
if (metricName != null && !metricName.isEmpty()) {
metricNamePart = " and def.name = '" + metricName + "' ";
metricNamePart = " and def.name = :metricName";
}
String limitPart = " limit " + Integer.toString(limit + 1);
@ -98,12 +115,82 @@ public class DimensionVerticaRepoImpl implements DimensionRepo {
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);
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<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
* 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 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;
@ -32,35 +30,36 @@ 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.DimensionName;
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;
/**
* Dimension resource implementation.
*/
@Path("/v2.0/metrics/dimensions/names/values")
@Path("/v2.0/metrics/dimensions")
public class DimensionResource {
private final DimensionRepo repo;
private final PersistUtils persistUtils;
private final String admin_role;
private final String adminRole;
@Inject
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;
this.repo = repo;
this.persistUtils = persistUtils;
}
@GET
@Path("/names/values")
@Timed
@Produces(MediaType.APPLICATION_JSON)
public Object get(
public Object getDimensionValues(
@Context UriInfo uriInfo,
@HeaderParam("X-Tenant-Id") String tenantId,
@HeaderParam("X-Roles") String roles,
@ -71,9 +70,28 @@ public class DimensionResource {
@QueryParam("tenant_id") String crossTenantId) throws Exception
{
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);
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);
String queryTenantId = Validation.getQueryProject(roles, crossTenantId, tenantId, adminRole);
List<DimensionName> dimNames = repo.findNames(metricName, queryTenantId, offset, paging_limit);
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
* 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.domain.model.alarm.AlarmCount;
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.common.model.domain.common.AbstractEntity;
import monasca.api.domain.model.common.Link;
@ -360,21 +360,4 @@ public final class 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
* in compliance with the License. You may obtain a copy of the License at
@ -14,18 +14,11 @@
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;
@ -49,23 +42,32 @@ public class DimensionResourceTest extends AbstractMonApiResourceTest {
}
@SuppressWarnings("unchecked")
public void shouldQueryWithDefaultParams() throws Exception {
public void shouldQueryDimensionValuesWithDefaultParams() 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(),
verify(dimensionRepo).findValues(anyString(), anyString(), anyString(), anyString(),
anyInt());
}
public void shouldQueryWithOptionalMetricName() throws Exception {
public void shouldQueryDimensionValuesWithOptionalMetricName() 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(),
verify(dimensionRepo).findValues(anyString(), anyString(), anyString(), anyString(),
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
* 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.Paged;
import monasca.common.model.alarm.AlarmState;
import monasca.api.domain.model.dimension.DimensionValues;
import static org.testng.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ -135,49 +134,4 @@ public class LinksTest {
+ 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 Hewlett-Packard
# (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 in compliance with the License. You may obtain
@ -64,3 +64,12 @@ class DimensionValuesV2API(object):
def on_get(self, req, res):
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 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
# 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'),
cfg.StrOpt('dimension_values', default=None,
help='Dimension values'),
cfg.StrOpt('dimension_names', default=None,
help='Dimension names'),
cfg.StrOpt('notification_method_types', default=None,
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)()
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(
cfg.CONF.dispatcher.notification_method_types)()
app.add_route("/v2.0/notification-methods/types", notification_method_types)

View File

@ -16,7 +16,6 @@
# under the License.
from datetime import datetime
from datetime import timedelta
import hashlib
import json
from influxdb import client
@ -207,28 +206,11 @@ class MetricsRepository(metrics_repository.AbstractMetricsRepository):
LOG.exception(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
def _build_serie_dimension_values(self, series_names, dimension_name):
dim_values = []
json_dim_value_list = []
if not series_names:
return json_dim_vals
return json_dim_value_list
if 'series' in series_names.raw:
for series in series_names.raw['series']:
@ -240,12 +222,29 @@ class MetricsRepository(metrics_repository.AbstractMetricsRepository):
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])
if dimension_name in dims and dims[dimension_name] not in\
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_vals[u'values'] = dim_vals
return json_dim_vals
json_dim_value_list = sorted(json_dim_value_list)
return json_dim_value_list
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,
start_timestamp, end_timestamp,
@ -619,21 +618,25 @@ class MetricsRepository(metrics_repository.AbstractMetricsRepository):
return int((dt - datetime(1970, 1, 1)).total_seconds() * 1000)
def list_dimension_values(self, tenant_id, region, metric_name,
dimension_name, offset, limit):
dimension_name):
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)
json_dim_vals = self._build_serie_dimension_values(result,
metric_name,
dimension_name,
tenant_id,
region,
offset)
return json_dim_vals
json_dim_name_list = self._build_serie_dimension_values(
result, dimension_name)
return json_dim_name_list
except Exception as ex:
LOG.exception(ex)
raise exceptions.RepositoryException(ex)
def list_dimension_names(self, tenant_id, region, metric_name):
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:
LOG.exception(ex)
raise exceptions.RepositoryException(ex)

View File

@ -1,4 +1,5 @@
# 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
# not use this file except in compliance with the License. You may obtain
@ -152,18 +153,40 @@ class TestRepoMetricsInfluxDB(unittest.TestCase):
"38dc2a2549f94d2e9a4fa1cc45a4970c",
"useast",
"custom_metric",
"hostname",
offset=None,
limit=1)
"hostname")
self.assertEqual(result, {
u'dimension_name': 'hostname',
u'values': [
u'custom_host'
],
u'id': 'bea9565d854a16a3366164de213694c190f27675',
u'metric_name': 'custom_metric'
})
self.assertEqual(result, [{u'dimension_value': u'custom_host'}])
@patch("monasca_api.common.repositories.influxdb.metrics_repository.client.InfluxDBClient")
def test_list_dimension_names(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_names(
"38dc2a2549f94d2e9a4fa1cc45a4970c",
"useast",
"custom_metric")
self.assertEqual(result,
[
{u'dimension_name': u'hostname'},
{u'dimension_name': u'service'}
])
class TestRepoMetricsCassandra(testtools.TestCase):

View File

@ -1,5 +1,5 @@
# 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
# 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
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):
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
# 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)
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)
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,
@ -342,8 +343,43 @@ class DimensionValues(metrics_api_v2.DimensionValuesV2API):
result = self._metrics_repo.list_dimension_values(tenant_id,
self._region,
metric_name,
dimension_name,
offset,
limit)
dimension_name)
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)
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):
uri = 'metrics/dimensions/names/values'
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)
if 'offset' not in query_params:
self.fail("No offset in next link: {}".format(next_link))
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
# not use this file except in compliance with the License. You may obtain
@ -14,14 +14,13 @@
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
from urllib import urlencode
class TestDimensions(base.BaseMonascaTest):
@ -29,34 +28,56 @@ class TestDimensions(base.BaseMonascaTest):
@classmethod
def resource_setup(cls):
super(TestDimensions, cls).resource_setup()
name = data_utils.rand_name()
key = data_utils.rand_name()
metric_name1 = data_utils.rand_name()
name1 = "name_1"
name2 = "name_2"
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
timestamp = int(round(time.time() * 1000))
time_iso = helpers.timestamp_to_iso(timestamp)
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):
resp, response_body = cls.monasca_client.list_metrics(
parms)
param)
elements = response_body['elements']
for element in elements:
if str(element['name']) == cls._test_metric['name']:
return
returned_name_set.add(str(element['name']))
if cls._test_metric_names.issubset(returned_name_set):
return
time.sleep(constants.RETRY_WAIT_SECS)
assert False, 'Unable to initialize metrics'
@ -67,46 +88,73 @@ class TestDimensions(base.BaseMonascaTest):
@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)
param = '?dimension_name=' + self._dim_names[0]
resp, response_body = self.monasca_client.list_dimension_values(param)
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')
self.assertTrue({'links', 'elements'} == set(response_body))
response_values_length = len(response_body['elements'])
values = [str(response_body['elements'][i]['dimension_value'])
for i in xrange(response_values_length)]
self.assertEqual(values, self._dim_values)
@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
parms = '?metric_name=' + self._test_metric1['name']
parms += '&dimension_name=' + self._dim_names[0]
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')
self.assertTrue({'links', 'elements'} == set(response_body))
response_values_length = len(response_body['elements'])
values = [str(response_body['elements'][i]['dimension_value'])
for i in xrange(response_values_length)]
self.assertEqual(values, self._dim_values_for_metric1)
@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)
param = '?dimension_name=' + self._dim_names[0]
resp, response_body = self.monasca_client.list_dimension_values(param)
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')
elements = response_body['elements']
num_dim_values = len(elements)
for limit in xrange(1, num_dim_values):
start_index = 0
params = [('limit', limit)]
offset = None
while True:
num_expected_elements = limit
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=['negative'])
@ -114,46 +162,81 @@ class TestDimensions(base.BaseMonascaTest):
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
@test.attr(type='gate')
def test_list_dimension_names(self):
resp, response_body = self.monasca_client.list_dimension_names()
self.assertEqual(200, resp.status)
self.assertTrue({'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, self._dim_names)
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
@test.attr(type='gate')
def test_list_dimension_names_with_metric_name(self):
self._test_list_dimension_names_with_metric_name(
self._test_metric1['name'], self._dim_names_metric1)
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):
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
@test.attr(type='gate')
def test_list_dimension_names_limit_and_offset(self):
resp, response_body = self.monasca_client.list_dimension_names()
self.assertEqual(200, resp.status)
elements = response_body['elements']
num_dim_names = len(elements)
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):
elements = response_body['elements'][0]
if dim_val in elements['values']:
return True
return False
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 = '?' + 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):
links = response_body['links']
offset = "offset=" + dim_val
for link in links:
if offset in link['href']:
return True
return False
expected_elements = elements[start_index:start_index+limit]
expected_dimension_names = \
[expected_elements[i]['dimension_name'] for i in xrange(
len(expected_elements))]
self.assertEqual(expected_dimension_names, new_elements)
start_index += num_expected_elements
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):
links = response_body['links']
limit = "limit=1"
for link in links:
if limit in link['href']:
return True
return False
@test.attr(type='gate')
@test.attr(type=['negative'])
def test_list_dimension_names_with_wrong_metric_name(self):
self._test_list_dimension_names_with_metric_name(
'wrong_metric_name', [])
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
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
@ -13,8 +13,6 @@
import time
import six.moves.urllib.parse as urlparse
from monasca_tempest_tests.tests.api import base
from monasca_tempest_tests.tests.api import helpers
from tempest import test
@ -22,6 +20,7 @@ from tempest.lib import exceptions
from monasca_tempest_tests import clients
class TestReadOnlyRole(base.BaseMonascaTest):
@classmethod
@ -112,7 +111,19 @@ class TestReadOnlyRole(base.BaseMonascaTest):
#
url = '/v2.0/metrics/dimensions/names/values?dimension_name=foo'
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))
@test.attr(type="gate")
@ -129,7 +140,6 @@ class TestReadOnlyRole(base.BaseMonascaTest):
self.assertEqual(0, len(response_body['elements']))
self.assertTrue('/v2.0/metrics/measurements' in response_body['links'][0]['href'])
@test.attr(type="gate")
def test_list_statistics_success(self):
start_timestamp = int(time.time() * 1000)