Add alarm count resource
Add alarm counts resource at /v2.0/alarms/count Add alarm counts spec Add tempest tests for alarm count Change-Id: I84f762ddf438ff87bc8e5daa04c970f5d484a61b
This commit is contained in:
parent
50f2310bf0
commit
b0ff5227b0
|
@ -18,6 +18,7 @@ metrics_statistics = monasca_api.v2.reference.metrics:MetricsStatistics
|
|||
metrics_names = monasca_api.v2.reference.metrics:MetricsNames
|
||||
alarm_definitions = monasca_api.v2.reference.alarm_definitions:AlarmDefinitions
|
||||
alarms = monasca_api.v2.reference.alarms:Alarms
|
||||
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
|
||||
|
||||
|
|
|
@ -247,18 +247,19 @@ Document Version: v2.0
|
|||
- [Status Code](#status-code-16)
|
||||
- [Response Body](#response-body-18)
|
||||
- [Response Examples](#response-examples-15)
|
||||
- [List Alarms State History](#list-alarms-state-history)
|
||||
- [GET /v2.0/alarms/state-history](#get-v20alarmsstate-history)
|
||||
- [Get Alarm Counts](#get-alarm-counts)
|
||||
- [GET /v2.0/alarms/count](#get-v20alarmscount)
|
||||
- [Headers](#headers-19)
|
||||
- [Path Parameters](#path-parameters-19)
|
||||
- [Query Parameters](#query-parameters-19)
|
||||
- [Request Body](#request-body-19)
|
||||
- [Request Examples](#request-examples-18)
|
||||
- [Response](#response-19)
|
||||
- [Status Code](#status-code-17)
|
||||
- [Response Body](#response-body-19)
|
||||
- [Response Examples](#response-examples-16)
|
||||
- [Get Alarm](#get-alarm)
|
||||
- [GET /v2.0/alarms/{alarm_id}](#get-v20alarmsalarm_id)
|
||||
- [Response Example](#response-example)
|
||||
- [List Alarms State History](#list-alarms-state-history)
|
||||
- [GET /v2.0/alarms/state-history](#get-v20alarmsstate-history)
|
||||
- [Headers](#headers-20)
|
||||
- [Path Parameters](#path-parameters-20)
|
||||
- [Query Parameters](#query-parameters-20)
|
||||
|
@ -266,20 +267,19 @@ Document Version: v2.0
|
|||
- [Response](#response-20)
|
||||
- [Status Code](#status-code-18)
|
||||
- [Response Body](#response-body-20)
|
||||
- [Response Examples](#response-examples-17)
|
||||
- [Update Alarm](#update-alarm)
|
||||
- [PUT /v2.0/alarms/{alarm_id}](#put-v20alarmsalarm_id)
|
||||
- [Response Examples](#response-examples-16)
|
||||
- [Get Alarm](#get-alarm)
|
||||
- [GET /v2.0/alarms/{alarm_id}](#get-v20alarmsalarm_id)
|
||||
- [Headers](#headers-21)
|
||||
- [Path Parameters](#path-parameters-21)
|
||||
- [Query Parameters](#query-parameters-21)
|
||||
- [Request Body](#request-body-21)
|
||||
- [Request Examples](#request-examples-18)
|
||||
- [Response](#response-21)
|
||||
- [Status Code](#status-code-19)
|
||||
- [Response Body](#response-body-21)
|
||||
- [Response Examples](#response-examples-18)
|
||||
- [Patch Alarm](#patch-alarm)
|
||||
- [PATCH /v2.0/alarms/{alarm_id}](#patch-v20alarmsalarm_id)
|
||||
- [Response Examples](#response-examples-17)
|
||||
- [Update Alarm](#update-alarm)
|
||||
- [PUT /v2.0/alarms/{alarm_id}](#put-v20alarmsalarm_id)
|
||||
- [Headers](#headers-22)
|
||||
- [Path Parameters](#path-parameters-22)
|
||||
- [Query Parameters](#query-parameters-22)
|
||||
|
@ -288,9 +288,9 @@ Document Version: v2.0
|
|||
- [Response](#response-22)
|
||||
- [Status Code](#status-code-20)
|
||||
- [Response Body](#response-body-22)
|
||||
- [Response Examples](#response-examples-19)
|
||||
- [Delete Alarm](#delete-alarm)
|
||||
- [DELETE /v2.0/alarms/{alarm_id}](#delete-v20alarmsalarm_id)
|
||||
- [Response Examples](#response-examples-18)
|
||||
- [Patch Alarm](#patch-alarm)
|
||||
- [PATCH /v2.0/alarms/{alarm_id}](#patch-v20alarmsalarm_id)
|
||||
- [Headers](#headers-23)
|
||||
- [Path Parameters](#path-parameters-23)
|
||||
- [Query Parameters](#query-parameters-23)
|
||||
|
@ -299,16 +299,27 @@ Document Version: v2.0
|
|||
- [Response](#response-23)
|
||||
- [Status Code](#status-code-21)
|
||||
- [Response Body](#response-body-23)
|
||||
- [List Alarm State History](#list-alarm-state-history)
|
||||
- [GET /v2.0/alarms/{alarm_id}/state-history](#get-v20alarmsalarm_idstate-history)
|
||||
- [Response Examples](#response-examples-19)
|
||||
- [Delete Alarm](#delete-alarm)
|
||||
- [DELETE /v2.0/alarms/{alarm_id}](#delete-v20alarmsalarm_id)
|
||||
- [Headers](#headers-24)
|
||||
- [Path Parameters](#path-parameters-24)
|
||||
- [Query Parameters](#query-parameters-24)
|
||||
- [Request Body](#request-body-24)
|
||||
- [Request Data](#request-data)
|
||||
- [Request Examples](#request-examples-21)
|
||||
- [Response](#response-24)
|
||||
- [Status Code](#status-code-22)
|
||||
- [Response Body](#response-body-24)
|
||||
- [List Alarm State History](#list-alarm-state-history)
|
||||
- [GET /v2.0/alarms/{alarm_id}/state-history](#get-v20alarmsalarm_idstate-history)
|
||||
- [Headers](#headers-25)
|
||||
- [Path Parameters](#path-parameters-25)
|
||||
- [Query Parameters](#query-parameters-25)
|
||||
- [Request Body](#request-body-25)
|
||||
- [Request Data](#request-data)
|
||||
- [Response](#response-25)
|
||||
- [Status Code](#status-code-23)
|
||||
- [Response Body](#response-body-25)
|
||||
- [Response Examples](#response-examples-20)
|
||||
- [License](#license)
|
||||
|
||||
|
@ -2333,6 +2344,78 @@ Returns a JSON object with a 'links' array of links and an 'elements' array of a
|
|||
```
|
||||
___
|
||||
|
||||
##Get Alarm Counts
|
||||
Get the number of alarms that match the criteria.
|
||||
|
||||
###GET /v2.0/alarms/count
|
||||
|
||||
####Headers
|
||||
* X-Auth-Token (string, required) - Keystone auth token
|
||||
* Content-Type (string, required) - application/json
|
||||
* Accept (string) - application/json
|
||||
|
||||
####Path Parameters
|
||||
None
|
||||
|
||||
####Query Parameters
|
||||
* alarm_definition_id (string, optional) - Alarm definition ID to filter by.
|
||||
* metric_name (string(255), optional) - Name of metric to filter by.
|
||||
* metric_dimensions ({string(255): string(255)}, optional) - Dimensions of metrics to filter by specified as a comma separated array of (key, value) pairs as `key1:value1,key1:value1,...`
|
||||
* state (string, optional) - State of alarm to filter by, either `OK`, `ALARM` or `UNDETERMINED`.
|
||||
* lifecycle_state (string(50), optional) - Lifecycle state to filter by.
|
||||
* link (string(512), optional) - Link to filter by.
|
||||
* state_updated_start_time (string, optional) - The start time in ISO 8601 combined date and time format in UTC.
|
||||
* offset (integer, optional)
|
||||
* limit (integer, optional)
|
||||
* group_by (string, optional) – a list of fields to group the results by as ```field1,field2,…```
|
||||
The group_by field is limited to `alarm_definition_id`, `name`, `state`, `severity`, `link`, `lifecycle_state`, `metric_name`, `dimension_name`, `dimension_value`.
|
||||
If any of the fields `metric_name`, `dimension_name`, or `dimension_value` are specified, the sum of the resulting counts is not guaranteed to equal the total number of alarms in the system. Alarms with multiple metrics may be included in multiple counts when grouped by any of these three fields.
|
||||
|
||||
####Request Body
|
||||
None
|
||||
|
||||
####Request Examples
|
||||
```
|
||||
GET /v2.0/alarms/count?metric_name=cpu.system_perc&metric_dimensions=hostname:devstack&group_by=state,lifecycle_state
|
||||
HTTP/1.1 Host: 192.168.10.4:8080
|
||||
Content-Type: application/json
|
||||
X-Auth-Token: 2b8882ba2ec44295bf300aecb2caa4f7
|
||||
Cache-Control: no-cache
|
||||
```
|
||||
|
||||
###Response
|
||||
####Status Code
|
||||
* 200 OK
|
||||
|
||||
####Response Body
|
||||
Returns a JSON object containing the following fields:
|
||||
* links ([link]) - Links to alarms count resource
|
||||
* columns ([string]) - List of the column names, in the order they were returned
|
||||
* counts ([array[]]) - A two dimensional array of the counts returned
|
||||
|
||||
####Response Example
|
||||
```
|
||||
{
|
||||
"links": [
|
||||
{
|
||||
"rel": "self",
|
||||
"href": "http://192.168.10.4:8080/v2.0/alarms/count?name=cpu.system_perc&dimensions=hostname%3Adevstack&group_by=state,lifecycle_state"
|
||||
}
|
||||
],
|
||||
"columns": ["count", "state", "lifecycle_state"],
|
||||
"counts": [
|
||||
[124, "ALARM", "ACKNOWLEDGED"],
|
||||
[12, "ALARM", "RESOLVED"],
|
||||
[235, "OK", "OPEN"],
|
||||
[61, "OK", "RESOLVED"],
|
||||
[13, "UNDETERMINED", "ACKNOWLEDGED"],
|
||||
[1, "UNDETERMINED", "OPEN"],
|
||||
[2, "UNDETERMINED", "RESOLVED"],
|
||||
]
|
||||
}
|
||||
```
|
||||
___
|
||||
|
||||
## List Alarms State History
|
||||
List alarm state history for alarms.
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ metrics_statistics = monasca_api.v2.reference.metrics:MetricsStatistics
|
|||
metrics_names = monasca_api.v2.reference.metrics:MetricsNames
|
||||
alarm_definitions = monasca_api.v2.reference.alarm_definitions:AlarmDefinitions
|
||||
alarms = monasca_api.v2.reference.alarms:Alarms
|
||||
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
|
||||
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (c) 2016 Hewlett Packard Enterprise 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.alarm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import monasca.api.domain.model.common.Link;
|
||||
import monasca.api.domain.model.common.Linked;
|
||||
import monasca.common.model.domain.common.AbstractEntity;
|
||||
|
||||
public class AlarmCount extends AbstractEntity implements Linked {
|
||||
private List<Link> links;
|
||||
private List<String> columns;
|
||||
private List<List<Object>> counts;
|
||||
|
||||
public AlarmCount() {}
|
||||
|
||||
public AlarmCount(List<String> columns, List<List<Object>> counts) {
|
||||
this.columns = new ArrayList<>();
|
||||
this.columns.add("count");
|
||||
if (columns != null) {
|
||||
this.columns.addAll(columns);
|
||||
}
|
||||
this.counts = new ArrayList<>();
|
||||
this.counts.addAll(counts);
|
||||
}
|
||||
|
||||
public void setColumns(List<String> columns) {
|
||||
this.columns = columns;
|
||||
}
|
||||
|
||||
public List<String> getColumns() {
|
||||
return this.columns;
|
||||
}
|
||||
|
||||
public void setCounts(List<List<Object>> counts) {
|
||||
this.counts = counts;
|
||||
}
|
||||
|
||||
public List<List<Object>> getCounts() {
|
||||
return this.counts;
|
||||
}
|
||||
|
||||
public void setLinks(List<Link> links) {
|
||||
this.links = links;
|
||||
}
|
||||
|
||||
public List<Link> getLinks() {
|
||||
return this.links;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
|
||||
* Copyright (c) 2015-2016 Hewlett Packard Enterprise 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
|
||||
|
@ -55,4 +55,13 @@ public interface AlarmRepo {
|
|||
* Alarm Definition Id
|
||||
*/
|
||||
Map<String, Map<String, AlarmSubExpression>> findAlarmSubExpressionsForAlarmDefinition(String alarmDefinitionId);
|
||||
|
||||
/**
|
||||
* Gets the count(s) of the alarms matching the parameters
|
||||
* @return 2 dimensional list of the counts with their group tags
|
||||
*/
|
||||
AlarmCount getAlarmsCount(String tenantId, String alarmDefId, String metricName,
|
||||
Map<String, String> metricDimensions, AlarmState state,
|
||||
String lifecycleState, String link, DateTime stateUpdatedStart,
|
||||
List<String> groupBy, String offset, int limit);
|
||||
}
|
||||
|
|
|
@ -38,7 +38,9 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
import monasca.api.domain.exception.EntityNotFoundException;
|
||||
import monasca.api.domain.model.alarm.Alarm;
|
||||
import monasca.api.domain.model.alarm.AlarmCount;
|
||||
import monasca.api.domain.model.alarm.AlarmRepo;
|
||||
import monasca.api.resource.exception.Exceptions;
|
||||
import monasca.common.hibernate.db.AlarmDb;
|
||||
import monasca.common.hibernate.db.SubAlarmDb;
|
||||
import monasca.common.hibernate.type.BinaryId;
|
||||
|
@ -523,4 +525,13 @@ public class AlarmSqlRepoImpl
|
|||
|
||||
return subAlarms;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlarmCount getAlarmsCount(String tenantId, String alarmDefId, String metricName,
|
||||
Map<String, String> metricDimensions, AlarmState state,
|
||||
String lifecycleState, String link, DateTime stateUpdatedStart,
|
||||
List<String> groupBy, String offset, int limit) {
|
||||
// Not Implemented
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
|
||||
* Copyright (c) 2014-2016 Hewlett Packard Enterprise 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
|
||||
|
@ -13,8 +13,12 @@
|
|||
*/
|
||||
package monasca.api.infrastructure.persistence.mysql;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Strings;
|
||||
|
||||
import monasca.api.domain.exception.EntityNotFoundException;
|
||||
import monasca.api.domain.model.alarm.Alarm;
|
||||
import monasca.api.domain.model.alarm.AlarmCount;
|
||||
import monasca.api.domain.model.alarm.AlarmRepo;
|
||||
import monasca.api.infrastructure.persistence.DimensionQueries;
|
||||
import monasca.api.infrastructure.persistence.PersistUtils;
|
||||
|
@ -51,6 +55,8 @@ public class AlarmMySqlRepoImpl implements AlarmRepo {
|
|||
private final DBI db;
|
||||
private final PersistUtils persistUtils;
|
||||
|
||||
private static final Joiner COMMA_JOINER = Joiner.on(',');
|
||||
|
||||
private static final String FIND_ALARM_BY_ID_SQL =
|
||||
"select ad.id as alarm_definition_id, ad.severity, ad.name as alarm_definition_name, "
|
||||
+ "a.id, a.state, a.lifecycle_state, a.link, a.state_updated_at as state_updated_timestamp, "
|
||||
|
@ -400,4 +406,191 @@ public class AlarmMySqlRepoImpl implements AlarmRepo {
|
|||
return subAlarms;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlarmCount getAlarmsCount(String tenantId, String alarmDefId, String metricName,
|
||||
Map<String, String> metricDimensions, AlarmState state,
|
||||
String lifecycleState, String link, DateTime stateUpdatedStart,
|
||||
List<String> groupBy, String offset, int limit) {
|
||||
final String SELECT_CLAUSE = "SELECT count(*) as count%1$s "
|
||||
+ " FROM alarm AS a "
|
||||
+ " INNER JOIN alarm_definition as ad on ad.id = a.alarm_definition_id ";
|
||||
|
||||
StringBuilder queryBuilder = new StringBuilder();
|
||||
|
||||
String groupByStr = "";
|
||||
String metricSelect;
|
||||
if (groupBy != null) {
|
||||
groupByStr = COMMA_JOINER.join(groupBy);
|
||||
queryBuilder.append(String.format(SELECT_CLAUSE, ',' + groupByStr));
|
||||
|
||||
if (groupBy.contains("metric_name") || groupBy.contains("dimension_name") || groupBy
|
||||
.contains("dimension_value")) {
|
||||
metricSelect = " INNER JOIN (SELECT distinct am.alarm_id%1$s "
|
||||
+ "FROM metric_definition AS md "
|
||||
+ "JOIN metric_definition_dimensions AS mdd on md.id = mdd.metric_definition_id "
|
||||
+ "JOIN metric_dimension AS mdim ON mdd.metric_dimension_set_id = mdim.dimension_set_id "
|
||||
+ "JOIN alarm_metric AS am ON am.metric_definition_dimensions_id = mdd.id)"
|
||||
+ "AS metrics ON a.id = metrics.alarm_id ";
|
||||
String subSelect = "";
|
||||
if (groupBy.contains("metric_name")) {
|
||||
subSelect = subSelect + ",md.name AS metric_name";
|
||||
}
|
||||
if (groupBy.contains("dimension_name")) {
|
||||
subSelect = subSelect + ",mdim.name AS dimension_name";
|
||||
}
|
||||
if (groupBy.contains("dimension_value")) {
|
||||
subSelect = subSelect + ",mdim.value AS dimension_value";
|
||||
}
|
||||
|
||||
queryBuilder.append(String.format(metricSelect, subSelect));
|
||||
}
|
||||
} else {
|
||||
queryBuilder.append(String.format(SELECT_CLAUSE, groupByStr));
|
||||
}
|
||||
|
||||
|
||||
queryBuilder.append(" INNER JOIN (SELECT a.id "
|
||||
+ "FROM alarm AS a, alarm_definition AS ad "
|
||||
+ "WHERE ad.id = a.alarm_definition_id "
|
||||
+ " AND ad.deleted_at IS NULL "
|
||||
+ " AND ad.tenant_id = :tenantId ");
|
||||
|
||||
if (alarmDefId != null) {
|
||||
queryBuilder.append(" AND ad.id = :alarmDefId ");
|
||||
}
|
||||
|
||||
if (metricName != null) {
|
||||
|
||||
queryBuilder.append(" AND a.id IN (SELECT distinct a.id FROM alarm AS a "
|
||||
+ "INNER JOIN alarm_metric AS am ON am.alarm_id = a.id "
|
||||
+ "INNER JOIN metric_definition_dimensions AS mdd "
|
||||
+ " ON mdd.id = am.metric_definition_dimensions_id "
|
||||
+ "INNER JOIN (SELECT distinct id FROM metric_definition "
|
||||
+ " WHERE name = :metricName) AS md "
|
||||
+ " ON md.id = mdd.metric_definition_id ");
|
||||
|
||||
buildJoinClauseFor(metricDimensions, queryBuilder);
|
||||
|
||||
queryBuilder.append(")");
|
||||
|
||||
} else if (metricDimensions != null) {
|
||||
|
||||
queryBuilder.append(" AND a.id IN (SELECT distinct a.id FROM alarm AS a "
|
||||
+ "INNER JOIN alarm_metric AS am ON am.alarm_id = a.id "
|
||||
+ "INNER JOIN metric_definition_dimensions AS mdd "
|
||||
+ " ON mdd.id = am.metric_definition_dimensions_id ");
|
||||
|
||||
buildJoinClauseFor(metricDimensions, queryBuilder);
|
||||
|
||||
queryBuilder.append(")");
|
||||
|
||||
}
|
||||
|
||||
if (state != null) {
|
||||
queryBuilder.append(" AND a.state = :state");
|
||||
}
|
||||
|
||||
if (lifecycleState != null) {
|
||||
queryBuilder.append(" AND a.lifecycle_state = :lifecycleState");
|
||||
}
|
||||
|
||||
if (link != null) {
|
||||
queryBuilder.append(" AND a.link = :link");
|
||||
}
|
||||
|
||||
if (stateUpdatedStart != null) {
|
||||
queryBuilder.append(" AND a.state_updated_at >= :stateUpdatedStart");
|
||||
}
|
||||
|
||||
queryBuilder.append(") AS alarm_id_list ON alarm_id_list.id = a.id ");
|
||||
|
||||
if (groupBy != null) {
|
||||
queryBuilder.append(" GROUP BY ");
|
||||
queryBuilder.append(groupByStr);
|
||||
}
|
||||
|
||||
queryBuilder.append(" ORDER BY ");
|
||||
if (!Strings.isNullOrEmpty(groupByStr)) {
|
||||
queryBuilder.append(groupByStr);
|
||||
} else {
|
||||
queryBuilder.append(" a.id ");
|
||||
}
|
||||
|
||||
queryBuilder.append(" LIMIT :limit");
|
||||
|
||||
if (offset != null) {
|
||||
queryBuilder.append(String.format(" OFFSET %1$s ", offset));
|
||||
}
|
||||
|
||||
|
||||
|
||||
try (Handle h = db.open()) {
|
||||
|
||||
final Query<Map<String, Object>> q = h.createQuery(queryBuilder.toString()).bind("tenantId", tenantId);
|
||||
|
||||
if (alarmDefId != null) {
|
||||
q.bind("alarmDefId", alarmDefId);
|
||||
}
|
||||
|
||||
if (metricName != null) {
|
||||
q.bind("metricName", metricName);
|
||||
}
|
||||
|
||||
if (state != null) {
|
||||
q.bind("state", state.name());
|
||||
}
|
||||
|
||||
if (lifecycleState != null) {
|
||||
q.bind("lifecycleState", lifecycleState);
|
||||
}
|
||||
|
||||
if (link != null) {
|
||||
q.bind("link", link);
|
||||
}
|
||||
|
||||
if (stateUpdatedStart != null) {
|
||||
q.bind("stateUpdatedStart", stateUpdatedStart.toString());
|
||||
}
|
||||
|
||||
q.bind("limit", limit + 1);
|
||||
|
||||
DimensionQueries.bindDimensionsToQuery(q, metricDimensions);
|
||||
|
||||
final List<Map<String, Object>> rows = q.list();
|
||||
|
||||
return createAlarmCounts(groupBy, rows);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private AlarmCount createAlarmCounts(List<String> groupBy, List<Map<String, Object>> rows) {
|
||||
List<List<Object>> counts = new ArrayList<>();
|
||||
|
||||
// if no results, return 0 and fill columns with null
|
||||
if (rows.size() == 0) {
|
||||
List<Object> countsAndTags = new ArrayList<>();
|
||||
countsAndTags.add(0);
|
||||
if (groupBy != null) {
|
||||
for (final String columnName : groupBy) {
|
||||
countsAndTags.add(null);
|
||||
}
|
||||
}
|
||||
counts.add(countsAndTags);
|
||||
return new AlarmCount(groupBy, counts);
|
||||
}
|
||||
|
||||
for (final Map<String, Object> row : rows) {
|
||||
List<Object> countAndTags = new ArrayList<>();
|
||||
countAndTags.add(row.get("count"));
|
||||
if (groupBy != null && !groupBy.isEmpty()) {
|
||||
for (final String columnName : groupBy) {
|
||||
countAndTags.add(row.get(columnName));
|
||||
}
|
||||
}
|
||||
counts.add(countAndTags);
|
||||
}
|
||||
|
||||
return new AlarmCount(groupBy, counts);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
/*
|
||||
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
|
||||
*
|
||||
* Copyright (c) 2014-2016 Hewlett Packard Enterprise 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
|
||||
|
@ -13,7 +13,9 @@
|
|||
*/
|
||||
package monasca.api.resource;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import com.codahale.metrics.annotation.Timed;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
|
@ -21,6 +23,7 @@ import com.fasterxml.jackson.databind.JsonMappingException;
|
|||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -44,11 +47,13 @@ import monasca.api.app.command.UpdateAlarmCommand;
|
|||
import monasca.api.app.validation.MetricNameValidation;
|
||||
import monasca.api.app.validation.Validation;
|
||||
import monasca.api.domain.model.alarm.Alarm;
|
||||
import monasca.api.domain.model.alarm.AlarmCount;
|
||||
import monasca.api.domain.model.alarm.AlarmRepo;
|
||||
import monasca.api.domain.model.alarmstatehistory.AlarmStateHistory;
|
||||
import monasca.api.domain.model.alarmstatehistory.AlarmStateHistoryRepo;
|
||||
import monasca.api.infrastructure.persistence.PersistUtils;
|
||||
import monasca.api.resource.annotation.PATCH;
|
||||
import monasca.api.resource.exception.Exceptions;
|
||||
import monasca.common.model.alarm.AlarmState;
|
||||
|
||||
/**
|
||||
|
@ -61,6 +66,12 @@ public class AlarmResource {
|
|||
private final PersistUtils persistUtils;
|
||||
private final AlarmStateHistoryRepo stateHistoryRepo;
|
||||
|
||||
private final static List<String> ALLOWED_GROUP_BY = Arrays.asList("alarm_definition_id", "name",
|
||||
"state", "severity", "link",
|
||||
"lifecycle_state",
|
||||
"metric_name", "dimension_name",
|
||||
"dimension_value");
|
||||
|
||||
@Inject
|
||||
public AlarmResource(AlarmService service, AlarmRepo repo,
|
||||
AlarmStateHistoryRepo stateHistoryRepo,
|
||||
|
@ -91,7 +102,7 @@ public class AlarmResource {
|
|||
|
||||
private Alarm fixAlarmLinks(UriInfo uriInfo, Alarm alarm) {
|
||||
Links.hydrate(alarm.getAlarmDefinition(), uriInfo,
|
||||
AlarmDefinitionResource.ALARM_DEFINITIONS_PATH);
|
||||
AlarmDefinitionResource.ALARM_DEFINITIONS_PATH);
|
||||
return Links.hydrate(alarm, uriInfo, true);
|
||||
}
|
||||
|
||||
|
@ -106,9 +117,9 @@ public class AlarmResource {
|
|||
throws Exception {
|
||||
final int paging_limit = this.persistUtils.getLimit(limit);
|
||||
final List<AlarmStateHistory> resource = stateHistoryRepo.findById(tenantId,
|
||||
alarmId,
|
||||
offset,
|
||||
paging_limit
|
||||
alarmId,
|
||||
offset,
|
||||
paging_limit
|
||||
);
|
||||
return Links.paginate(paging_limit, resource, uriInfo);
|
||||
}
|
||||
|
@ -220,4 +231,62 @@ public class AlarmResource {
|
|||
|
||||
return fixAlarmLinks(uriInfo, service.update(tenantId, alarmId, command));
|
||||
}
|
||||
|
||||
@GET
|
||||
@Timed
|
||||
@Path("/count")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Object getCount(@Context UriInfo uriInfo,
|
||||
@HeaderParam("X-Tenant-Id") String tenantId, @PathParam("alarm_id") String alarmId,
|
||||
@QueryParam("alarm_definition_id") String alarmDefId,
|
||||
@QueryParam("metric_name") String metricName,
|
||||
@QueryParam("metric_dimensions") String metricDimensionsStr,
|
||||
@QueryParam("state") AlarmState state,
|
||||
@QueryParam("lifecycle_state") String lifecycleState,
|
||||
@QueryParam("link") String link,
|
||||
@QueryParam("state_updated_start_time") String stateUpdatedStartStr,
|
||||
@QueryParam("group_by") String groupByStr,
|
||||
@QueryParam("offset") String offset,
|
||||
@QueryParam("limit") String limit)
|
||||
throws Exception {
|
||||
Map<String, String> metricDimensions =
|
||||
Strings.isNullOrEmpty(metricDimensionsStr) ? null : Validation
|
||||
.parseAndValidateDimensions(metricDimensionsStr);
|
||||
MetricNameValidation.validate(metricName, false);
|
||||
DateTime stateUpdatedStart =
|
||||
Validation.parseAndValidateDate(stateUpdatedStartStr,
|
||||
"state_updated_start_time", false);
|
||||
List<String> groupBy = (Strings.isNullOrEmpty(groupByStr)) ? null : parseAndValidateGroupBy(
|
||||
groupByStr);
|
||||
|
||||
if (offset != null) {
|
||||
Validation.parseAndValidateNumber(offset, "offset");
|
||||
}
|
||||
|
||||
final int paging_limit = this.persistUtils.getLimit(limit);
|
||||
final AlarmCount resource = repo.getAlarmsCount(tenantId,
|
||||
alarmDefId,
|
||||
metricName,
|
||||
metricDimensions,
|
||||
state,
|
||||
lifecycleState,
|
||||
link,
|
||||
stateUpdatedStart,
|
||||
groupBy,
|
||||
offset,
|
||||
paging_limit);
|
||||
Links.paginateAlarmCount(resource, paging_limit, uriInfo);
|
||||
return resource;
|
||||
}
|
||||
|
||||
private List<String> parseAndValidateGroupBy(String groupByStr) {
|
||||
List<String> groupBy = null;
|
||||
if (!Strings.isNullOrEmpty(groupByStr)) {
|
||||
groupBy = Lists.newArrayList(Splitter.on(',').omitEmptyStrings().trimResults().split(groupByStr));
|
||||
if (!ALLOWED_GROUP_BY.containsAll(groupBy)) {
|
||||
throw Exceptions.unprocessableEntity("Unprocessable Entity", "Invalid group_by field");
|
||||
}
|
||||
}
|
||||
return groupBy;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
/*
|
||||
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
|
||||
*
|
||||
* Copyright (c) 2014-2016 Hewlett Packard Enterprise 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
|
||||
|
@ -23,6 +23,7 @@ import javax.ws.rs.core.UriInfo;
|
|||
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.measurement.Measurements;
|
||||
import monasca.api.domain.model.statistic.Statistics;
|
||||
|
@ -348,4 +349,17 @@ public final class Links {
|
|||
return nextLink;
|
||||
}
|
||||
|
||||
public static void paginateAlarmCount(AlarmCount alarmCount, int limit, UriInfo uriInfo)
|
||||
throws UnsupportedEncodingException {
|
||||
List<Link> links = new ArrayList<>();
|
||||
links.add(getSelfLink(uriInfo));
|
||||
if (alarmCount.getCounts().size() > limit) {
|
||||
alarmCount.getCounts().remove(alarmCount.getCounts().size()-1);
|
||||
String offset = String.valueOf(limit);
|
||||
links.add(getNextLink(offset, uriInfo));
|
||||
}
|
||||
|
||||
alarmCount.setLinks(links);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright 2014 Hewlett-Packard
|
||||
# Copyright 2014-2016 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
|
@ -35,6 +35,14 @@ class AlarmsV2API(object):
|
|||
res.status = '501 Not Implemented'
|
||||
|
||||
|
||||
class AlarmsCountV2API(object):
|
||||
def __init__(self):
|
||||
super(AlarmsCountV2API, self).__init__()
|
||||
|
||||
def on_get(self, req, res):
|
||||
res.status = "501 Not Implemented"
|
||||
|
||||
|
||||
class AlarmsStateHistoryV2API(object):
|
||||
def __init__(self):
|
||||
super(AlarmsStateHistoryV2API, self).__init__()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# Copyright 2014 IBM Corp
|
||||
# Copyright 2015 Hewlett-Packard
|
||||
# Copyright 2015-2016 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
|
@ -38,6 +38,8 @@ dispatcher_opts = [cfg.StrOpt('versions', default=None,
|
|||
help='Alarm definitions'),
|
||||
cfg.StrOpt('alarms', default=None,
|
||||
help='Alarms'),
|
||||
cfg.StrOpt('alarms_count', default=None,
|
||||
help='Alarms Count'),
|
||||
cfg.StrOpt('alarms_state_history', default=None,
|
||||
help='Alarms state history'),
|
||||
cfg.StrOpt('notification_methods', default=None,
|
||||
|
@ -91,6 +93,9 @@ def launch(conf, config_file="/etc/monasca/api-config.conf"):
|
|||
app.add_route("/v2.0/alarms", alarms)
|
||||
app.add_route("/v2.0/alarms/{alarm_id}", alarms)
|
||||
|
||||
alarm_count = simport.load(cfg.CONF.dispatcher.alarms_count)()
|
||||
app.add_route("/v2.0/alarms/count/", alarm_count)
|
||||
|
||||
alarms_state_history = simport.load(
|
||||
cfg.CONF.dispatcher.alarms_state_history)()
|
||||
app.add_route("/v2.0/alarms/state-history", alarms_state_history)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright 2014 Hewlett-Packard
|
||||
# Copyright 2014-2016 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
|
@ -47,3 +47,7 @@ class AlarmsRepository(object):
|
|||
@abc.abstractmethod
|
||||
def get_alarms(self, tenant_id, query_parms, offset, limit):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_alarms_count(self, tenant_id, query_parms, offset, limit):
|
||||
pass
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright 2014 Hewlett-Packard
|
||||
# Copyright 2014-2016 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
|
@ -296,3 +296,128 @@ class AlarmsRepository(mysql_repository.MySQLRepository,
|
|||
query = select_clause + where_clause + order_by_clause + limit_clause
|
||||
|
||||
return self._execute_query(query, parms)
|
||||
|
||||
@mysql_repository.mysql_try_catch_block
|
||||
def get_alarms_count(self, tenant_id, query_parms, offset, limit):
|
||||
|
||||
select_clause = """select count(*) as count{}
|
||||
from alarm as a
|
||||
join alarm_definition as ad on ad.id = a.alarm_definition_id
|
||||
"""
|
||||
|
||||
if 'group_by' in query_parms:
|
||||
group_by_str = ",".join(query_parms['group_by'])
|
||||
|
||||
metric_group_by = {'metric_name',
|
||||
'dimension_name',
|
||||
'dimension_value'}.intersection(set(query_parms['group_by']))
|
||||
if metric_group_by:
|
||||
metric_select = """
|
||||
join ( select distinct am.alarm_id{}
|
||||
from metric_definition as md
|
||||
join metric_definition_dimensions as mdd on md.id = mdd.metric_definition_id
|
||||
join metric_dimension as mdim on mdd.metric_dimension_set_id = mdim.dimension_set_id
|
||||
join alarm_metric as am on am.metric_definition_dimensions_id = mdd.id
|
||||
) as metrics on a.id = metrics.alarm_id """
|
||||
sub_select_clause = ""
|
||||
if 'metric_name' in metric_group_by:
|
||||
sub_select_clause += ', md.name as metric_name'
|
||||
if 'dimension_name' in metric_group_by:
|
||||
sub_select_clause += ', mdim.name as dimension_name'
|
||||
if 'dimension_value' in metric_group_by:
|
||||
sub_select_clause += ', mdim.value as dimension_value'
|
||||
|
||||
select_clause += metric_select.format(sub_select_clause)
|
||||
|
||||
else:
|
||||
group_by_str = ""
|
||||
|
||||
parms = []
|
||||
|
||||
where_clause = " where ad.tenant_id = %s "
|
||||
parms.append(tenant_id)
|
||||
|
||||
if 'alarm_definition_id' in query_parms:
|
||||
parms.append(query_parms['alarm_definition_id'])
|
||||
where_clause += " and ad.id = %s "
|
||||
|
||||
if 'state' in query_parms:
|
||||
parms.append(query_parms['state'].encode('utf8'))
|
||||
where_clause += " and a.state = %s "
|
||||
|
||||
if 'lifecycle_state' in query_parms:
|
||||
parms.append(query_parms['lifecycle_state'].encode('utf8'))
|
||||
where_clause += " and a.lifecycle_state = %s "
|
||||
|
||||
if 'link' in query_parms:
|
||||
parms.append(query_parms['link'].encode('utf8'))
|
||||
where_clause += " and a.link = %s "
|
||||
|
||||
if 'state_updated_start_time' in query_parms:
|
||||
parms.append(query_parms['state_updated_start_time']
|
||||
.encode("utf8"))
|
||||
where_clause += " and state_updated_at >= %s "
|
||||
|
||||
if 'metric_name' in query_parms:
|
||||
sub_select_clause = """
|
||||
and a.id in (select distinct a.id from alarm as a
|
||||
inner join alarm_metric as am on am.alarm_id
|
||||
= a.id
|
||||
inner join metric_definition_dimensions as mdd
|
||||
on mdd.id =
|
||||
am.metric_definition_dimensions_id
|
||||
inner join (select distinct id from
|
||||
metric_definition
|
||||
where name = %s) as md
|
||||
on md.id = mdd.metric_definition_id)
|
||||
"""
|
||||
parms.append(query_parms['metric_name'].encode('utf8'))
|
||||
where_clause += sub_select_clause
|
||||
|
||||
if 'metric_dimensions' in query_parms:
|
||||
sub_select_clause = """
|
||||
and a.id in (select distinct a.id from alarm as a
|
||||
inner join alarm_metric as am on am.alarm_id = a.id
|
||||
inner join metric_definition_dimensions as mdd
|
||||
on mdd.id = am.metric_definition_dimensions_id
|
||||
"""
|
||||
sub_select_parms = []
|
||||
i = 0
|
||||
for metric_dimension in query_parms['metric_dimensions']:
|
||||
parsed_dimension = metric_dimension.split(':')
|
||||
sub_select_clause += """
|
||||
inner join (select distinct dimension_set_id
|
||||
from metric_dimension
|
||||
where name = %s and value = %s) as md{}
|
||||
on md{}.dimension_set_id = mdd.metric_dimension_set_id
|
||||
""".format(i, i)
|
||||
i += 1
|
||||
sub_select_parms += [parsed_dimension[0].encode('utf8'),
|
||||
parsed_dimension[1].encode('utf8')]
|
||||
|
||||
sub_select_clause += ")"
|
||||
parms += sub_select_parms
|
||||
where_clause += sub_select_clause
|
||||
|
||||
if group_by_str:
|
||||
group_order_by_clause = " group by {} order by {} ".format(group_by_str, group_by_str)
|
||||
else:
|
||||
group_order_by_clause = ""
|
||||
|
||||
if limit:
|
||||
limit_clause = " limit %s "
|
||||
parms.append(limit + 1)
|
||||
else:
|
||||
limit_clause = ""
|
||||
|
||||
if offset:
|
||||
offset_clause = " offset {} ".format(offset)
|
||||
else:
|
||||
offset_clause = ""
|
||||
|
||||
select_group_by = ',' + group_by_str if group_by_str else ""
|
||||
select_clause = select_clause.format(select_group_by)
|
||||
|
||||
query = select_clause + where_clause + group_order_by_clause + limit_clause + offset_clause
|
||||
|
||||
return self._execute_query(query, parms)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright 2014 Hewlett-Packard
|
||||
# Copyright 2014-2016 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
|
@ -330,6 +330,102 @@ class Alarms(alarms_api_v2.AlarmsV2API,
|
|||
return helpers.paginate(result, req_uri, limit)
|
||||
|
||||
|
||||
class AlarmsCount(alarms_api_v2.AlarmsCountV2API, alarming.Alarming):
|
||||
|
||||
def __init__(self):
|
||||
try:
|
||||
super(AlarmsCount, self).__init__()
|
||||
self._region = cfg.CONF.region
|
||||
self._default_authorized_roles = (
|
||||
cfg.CONF.security.default_authorized_roles)
|
||||
self._alarms_repo = simport.load(
|
||||
cfg.CONF.repositories.alarms_driver)()
|
||||
|
||||
except Exception as ex:
|
||||
LOG.exception(ex)
|
||||
raise exceptions.RepositoryException(ex)
|
||||
|
||||
def on_get(self, req, res):
|
||||
helpers.validate_authorization(req, self._default_authorized_roles)
|
||||
tenant_id = helpers.get_tenant_id(req)
|
||||
query_parms = falcon.uri.parse_query_string(req.query_string)
|
||||
if 'group_by' in query_parms:
|
||||
if not isinstance(query_parms['group_by'], list):
|
||||
query_parms['group_by'] = [query_parms['group_by']]
|
||||
self._validate_group_by(query_parms['group_by'])
|
||||
|
||||
# ensure metric_dimensions is a list
|
||||
if 'metric_dimensions' in query_parms and isinstance(query_parms['metric_dimensions'], str):
|
||||
query_parms['metric_dimensions'] = query_parms['metric_dimensions'].split(',')
|
||||
|
||||
offset = helpers.get_query_param(req, 'offset')
|
||||
|
||||
if offset is not None:
|
||||
try:
|
||||
offset = int(offset)
|
||||
except Exception:
|
||||
raise HTTPUnprocessableEntityError("Unprocessable Entity",
|
||||
"Offset must be a valid integer, was {}".format(offset))
|
||||
|
||||
limit = helpers.get_limit(req)
|
||||
|
||||
result = self._alarms_count(req.uri, tenant_id, query_parms, offset, limit)
|
||||
|
||||
res.body = helpers.dumpit_utf8(result)
|
||||
res.status = falcon.HTTP_200
|
||||
|
||||
@resource.resource_try_catch_block
|
||||
def _alarms_count(self, req_uri, tenant_id, query_parms, offset, limit):
|
||||
|
||||
count_data = self._alarms_repo.get_alarms_count(tenant_id, query_parms, offset, limit)
|
||||
group_by = query_parms['group_by'] if 'group_by' in query_parms else []
|
||||
|
||||
# result = count_data
|
||||
result = {
|
||||
'links': [
|
||||
{
|
||||
'rel': 'self',
|
||||
'href': req_uri
|
||||
}
|
||||
],
|
||||
'columns': ['count']
|
||||
}
|
||||
|
||||
if len(count_data) == 0 or count_data[0]['count'] == 0:
|
||||
count = [0]
|
||||
if 'group_by' in query_parms:
|
||||
for field in query_parms['group_by']:
|
||||
result['columns'].append(field)
|
||||
count.append(None)
|
||||
result['counts'] = [count]
|
||||
return result
|
||||
|
||||
if len(count_data) > limit:
|
||||
result['links'].append({'rel': 'next',
|
||||
'href': helpers.create_alarms_count_next_link(req_uri, offset, limit)})
|
||||
count_data = count_data[:limit]
|
||||
|
||||
result['columns'].extend(group_by)
|
||||
|
||||
result['counts'] = []
|
||||
for row in count_data:
|
||||
count_result = [row['count']]
|
||||
for field in group_by:
|
||||
count_result.append(row[field])
|
||||
result['counts'].append(count_result)
|
||||
|
||||
return result
|
||||
|
||||
def _validate_group_by(self, group_by):
|
||||
allowed_values = {'alarm_definition_id', 'name', 'state', 'severity',
|
||||
'link', 'lifecycle_state', 'metric_name',
|
||||
'dimension_name', 'dimension_value'}
|
||||
if not set(group_by).issubset(allowed_values):
|
||||
raise HTTPUnprocessableEntityError(
|
||||
"Unprocessable Entity",
|
||||
"One or more group-by values from {} are not in {}".format(group_by, allowed_values))
|
||||
|
||||
|
||||
class AlarmsStateHistory(alarms_api_v2.AlarmsStateHistoryV2API,
|
||||
alarming.Alarming):
|
||||
def __init__(self):
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# Copyright 2014 Hewlett-Packard
|
||||
# Copyright 2015 Cray Inc. All Rights Reserved.
|
||||
# Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
|
@ -472,6 +473,21 @@ def paginate_statistics(statistic, uri, limit):
|
|||
return resource
|
||||
|
||||
|
||||
def create_alarms_count_next_link(uri, offset, limit):
|
||||
if offset is None:
|
||||
offset = 0
|
||||
parsed_url = urlparse.urlparse(uri)
|
||||
base_url = build_base_uri(parsed_url)
|
||||
new_query_params = [u'offset=' + urlparse.quote(str(offset + limit))]
|
||||
_get_old_query_params_except_offset(new_query_params, parsed_url)
|
||||
|
||||
next_link = base_url
|
||||
if new_query_params:
|
||||
next_link += '?' + '&'.join(new_query_params)
|
||||
|
||||
return next_link
|
||||
|
||||
|
||||
def build_base_uri(parsed_uri):
|
||||
return parsed_uri.scheme + '://' + parsed_uri.netloc + parsed_uri.path
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# (C) Copyright 2015 Hewlett Packard Enterprise Development Company LP
|
||||
# (C) Copyright 2015-2016 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
|
@ -260,6 +260,13 @@ class MonascaClient(rest_client.RestClient):
|
|||
resp, response_body = self.patch(uri, json.dumps(request_body))
|
||||
return resp, json.loads(response_body)
|
||||
|
||||
def count_alarms(self, query_params=None):
|
||||
uri = 'alarms/count'
|
||||
if query_params is not None:
|
||||
uri += query_params
|
||||
resp, response_body = self.get(uri)
|
||||
return resp, json.loads(response_body)
|
||||
|
||||
def list_alarms_state_history(self, query_params=None):
|
||||
uri = 'alarms/state-history'
|
||||
if query_params is not None:
|
||||
|
|
|
@ -0,0 +1,345 @@
|
|||
# (C) Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import time
|
||||
|
||||
from monasca_tempest_tests.tests.api import base
|
||||
from monasca_tempest_tests.tests.api import helpers
|
||||
from tempest.common.utils import data_utils
|
||||
from tempest import test
|
||||
from tempest_lib import exceptions
|
||||
|
||||
|
||||
GROUP_BY_ALLOWED_PARAMS = {'alarm_definition_id', 'name', 'state', 'severity',
|
||||
'link', 'lifecycle_state', 'metric_name',
|
||||
'dimension_name', 'dimension_value'}
|
||||
|
||||
class TestAlarmsCount(base.BaseMonascaTest):
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
super(TestAlarmsCount, cls).resource_setup()
|
||||
|
||||
num_hosts = 20
|
||||
|
||||
alarm_definitions = []
|
||||
expected_alarm_counts = []
|
||||
metrics_to_send = []
|
||||
|
||||
# OK, LOW
|
||||
expression = "max(test_metric_01) > 10"
|
||||
name = data_utils.rand_name('test-counts-01')
|
||||
alarm_definitions.append(helpers.create_alarm_definition(
|
||||
name=name,
|
||||
expression=expression,
|
||||
severity='LOW',
|
||||
match_by=['hostname', 'unique']))
|
||||
for i in xrange(100):
|
||||
metrics_to_send.append(helpers.create_metric(
|
||||
name='test_metric_01',
|
||||
dimensions={'hostname': 'test_' + str(i % num_hosts),
|
||||
'unique': str(i)},
|
||||
value=1
|
||||
))
|
||||
expected_alarm_counts.append(100)
|
||||
|
||||
# ALARM, MEDIUM
|
||||
expression = "max(test_metric_02) > 10"
|
||||
name = data_utils.rand_name('test-counts-02')
|
||||
alarm_definitions.append(helpers.create_alarm_definition(
|
||||
name=name,
|
||||
expression=expression,
|
||||
severity='MEDIUM',
|
||||
match_by=['hostname', 'unique']))
|
||||
for i in xrange(75):
|
||||
metrics_to_send.append(helpers.create_metric(
|
||||
name='test_metric_02',
|
||||
dimensions={'hostname': 'test_' + str(i % num_hosts),
|
||||
'unique': str(i)},
|
||||
value=11
|
||||
))
|
||||
# append again to move from undetermined to alarm
|
||||
metrics_to_send.append(helpers.create_metric(
|
||||
name='test_metric_02',
|
||||
dimensions={'hostname': 'test_' + str(i % num_hosts),
|
||||
'unique': str(i)},
|
||||
value=11
|
||||
))
|
||||
expected_alarm_counts.append(75)
|
||||
|
||||
# OK, HIGH, shared dimension
|
||||
expression = "max(test_metric_03) > 100"
|
||||
name = data_utils.rand_name('test_counts-03')
|
||||
alarm_definitions.append(helpers.create_alarm_definition(
|
||||
name=name,
|
||||
expression=expression,
|
||||
severity='HIGH',
|
||||
match_by=['hostname', 'unique']))
|
||||
for i in xrange(50):
|
||||
metrics_to_send.append(helpers.create_metric(
|
||||
name='test_metric_03',
|
||||
dimensions={'hostname': 'test_' + str(i % num_hosts),
|
||||
'unique': str(i),
|
||||
'height': '55'},
|
||||
value=i
|
||||
))
|
||||
expected_alarm_counts.append(50)
|
||||
|
||||
# UNDERTERMINED, CRITICAL
|
||||
expression = "max(test_metric_undet) > 100"
|
||||
name = data_utils.rand_name('test-counts-04')
|
||||
alarm_definitions.append(helpers.create_alarm_definition(
|
||||
name=name,
|
||||
expression=expression,
|
||||
severity='CRITICAL',
|
||||
match_by=['hostname', 'unique']))
|
||||
for i in xrange(25):
|
||||
metrics_to_send.append(helpers.create_metric(
|
||||
name='test_metric_undet',
|
||||
dimensions={'hostname': 'test_' + str(i % num_hosts),
|
||||
'unique': str(i)},
|
||||
value=1
|
||||
))
|
||||
expected_alarm_counts.append(25)
|
||||
|
||||
# create alarm definitions
|
||||
cls.alarm_definition_ids = []
|
||||
for definition in alarm_definitions:
|
||||
resp, response_body = cls.monasca_client.create_alarm_definitions(
|
||||
definition)
|
||||
if resp.status == 201:
|
||||
cls.alarm_definition_ids.append(response_body['id'])
|
||||
else:
|
||||
msg = "Failed to create alarm_definition during setup: {} {}".format(resp.status, response_body)
|
||||
assert False, msg
|
||||
|
||||
# create alarms
|
||||
for metric in metrics_to_send:
|
||||
metric['timestamp'] = int(time.time() * 1000)
|
||||
cls.monasca_client.create_metrics(metric)
|
||||
# ensure metric timestamps are unique
|
||||
time.sleep(0.01)
|
||||
|
||||
# check that alarms exist
|
||||
time_out = time.time() + 70
|
||||
while time.time() < time_out:
|
||||
setup_complete = True
|
||||
alarm_count = 0
|
||||
for i in xrange(len(cls.alarm_definition_ids)):
|
||||
resp, response_body = cls.monasca_client.list_alarms(
|
||||
'?alarm_definition_id=' + cls.alarm_definition_ids[i])
|
||||
if resp.status != 200:
|
||||
msg = "Error listing alarms: {} {}".format(resp.status, response_body)
|
||||
assert False, msg
|
||||
if len(response_body['elements']) < expected_alarm_counts[i]:
|
||||
setup_complete = False
|
||||
alarm_count += len(response_body['elements'])
|
||||
break
|
||||
|
||||
if setup_complete:
|
||||
# allow alarm transitions to occur
|
||||
# time.sleep(15)
|
||||
return
|
||||
|
||||
msg = "Failed to create all specified alarms during setup, alarm_count was {}".format(alarm_count)
|
||||
assert False, msg
|
||||
|
||||
|
||||
@classmethod
|
||||
def resource_cleanup(cls):
|
||||
for definition_id in cls.alarm_definition_ids:
|
||||
cls.monasca_client.delete_alarm_definition(definition_id)
|
||||
|
||||
def _verify_counts_format(self, response_body, group_by=None, expected_length=None):
|
||||
expected_keys = ['links', 'counts', 'columns']
|
||||
for key in expected_keys:
|
||||
self.assertIn(key, response_body)
|
||||
self.assertIsInstance(response_body[key], list)
|
||||
|
||||
expected_columns = ['count']
|
||||
if isinstance(group_by, list):
|
||||
expected_columns.extend(group_by)
|
||||
self.assertEqual(expected_columns, response_body['columns'])
|
||||
|
||||
if expected_length is not None:
|
||||
self.assertEqual(expected_length, len(response_body['counts']))
|
||||
else:
|
||||
expected_length = len(response_body['counts'])
|
||||
|
||||
for i in xrange(expected_length):
|
||||
self.assertEqual(len(expected_columns), len(response_body['counts'][i]))
|
||||
|
||||
# test with no params
|
||||
@test.attr(type='gate')
|
||||
def test_count(self):
|
||||
resp, response_body = self.monasca_client.count_alarms()
|
||||
self.assertEqual(200, resp.status)
|
||||
self._verify_counts_format(response_body)
|
||||
self.assertEqual(250, response_body['counts'][0][0])
|
||||
|
||||
# test with each group_by parameter singularly
|
||||
@test.attr(type='gate')
|
||||
def test_group_by_singular(self):
|
||||
resp, response_body = self.monasca_client.list_alarms("?state=ALARM")
|
||||
self.assertEqual(200, resp.status)
|
||||
alarm_state_count = len(response_body['elements'])
|
||||
resp, response_body = self.monasca_client.list_alarms("?state=UNDETERMINED")
|
||||
self.assertEqual(200, resp.status)
|
||||
undet_state_count = len(response_body['elements'])
|
||||
|
||||
resp, response_body = self.monasca_client.count_alarms("?group_by=state")
|
||||
self.assertEqual(200, resp.status)
|
||||
self._verify_counts_format(response_body, group_by=['state'])
|
||||
|
||||
self.assertEquals('ALARM', response_body['counts'][0][1])
|
||||
self.assertEqual(alarm_state_count, response_body['counts'][0][0])
|
||||
self.assertEquals('UNDETERMINED', response_body['counts'][-1][1])
|
||||
self.assertEqual(undet_state_count, response_body['counts'][-1][0])
|
||||
|
||||
resp, response_body = self.monasca_client.count_alarms("?group_by=name")
|
||||
self.assertEqual(200, resp.status)
|
||||
self._verify_counts_format(response_body, group_by=['name'], expected_length=4)
|
||||
|
||||
# test with group by a parameter that is not allowed
|
||||
@test.attr(type="gate")
|
||||
@test.attr(type=['negative'])
|
||||
def test_group_by_not_allowed(self):
|
||||
self.assertRaises(exceptions.UnprocessableEntity,
|
||||
self.monasca_client.count_alarms, "?group_by=not_allowed")
|
||||
|
||||
# test with a few group_by fields
|
||||
@test.attr(type='gate')
|
||||
def test_group_by_multiple(self):
|
||||
resp, response_body = self.monasca_client.list_alarms()
|
||||
alarm_low_count = 0
|
||||
for alarm in response_body['elements']:
|
||||
if alarm['state'] is 'ALARM' and alarm['severity'] is 'LOW':
|
||||
alarm_low_count += 1
|
||||
|
||||
|
||||
resp, response_body = self.monasca_client.count_alarms("?group_by=state,severity")
|
||||
self._verify_counts_format(response_body, group_by=['state', 'severity'])
|
||||
|
||||
|
||||
# test with filter parameters
|
||||
@test.attr(type='gate')
|
||||
def test_filter_params(self):
|
||||
resp, response_body = self.monasca_client.list_alarms("?severity=LOW")
|
||||
self.assertEqual(200, resp.status)
|
||||
expected_count = len(response_body['elements'])
|
||||
|
||||
resp, response_body = self.monasca_client.count_alarms("?severity=LOW")
|
||||
self.assertEqual(200, resp.status)
|
||||
self._verify_counts_format(response_body, expected_length=1)
|
||||
self.assertEqual(expected_count, response_body['counts'][0][0])
|
||||
|
||||
# test with multiple metric dimensions
|
||||
@test.attr(type='gate')
|
||||
def test_filter_multiple_dimensions(self):
|
||||
resp, response_body = self.monasca_client.list_alarms("?metric_dimensions=hostname:test_1,unique:1")
|
||||
self.assertEqual(200, resp.status)
|
||||
expected_count = len(response_body['elements'])
|
||||
|
||||
resp, response_body = self.monasca_client.count_alarms("?metric_dimensions=hostname:test_1,unique:1")
|
||||
self.assertEqual(200, resp.status)
|
||||
self._verify_counts_format(response_body, expected_length=1)
|
||||
self.assertEqual(expected_count, response_body['counts'][0][0])
|
||||
|
||||
# test with filter and group_by parameters
|
||||
@test.attr(type='gate')
|
||||
def test_filter_and_group_by_params(self):
|
||||
resp, response_body = self.monasca_client.list_alarms("?state=ALARM")
|
||||
self.assertEqual(200, resp.status)
|
||||
expected_count = 0
|
||||
for element in response_body['elements']:
|
||||
if element['alarm_definition']['severity'] == 'MEDIUM':
|
||||
expected_count += 1
|
||||
|
||||
resp, response_body = self.monasca_client.count_alarms("?state=ALARM&group_by=severity")
|
||||
self.assertEqual(200, resp.status)
|
||||
self._verify_counts_format(response_body, group_by=['severity'])
|
||||
self.assertEqual(expected_count, response_body['counts'][0][0])
|
||||
|
||||
@test.attr(type='gate')
|
||||
def test_with_all_group_by_params(self):
|
||||
resp, response_body = self.monasca_client.list_alarms()
|
||||
self.assertEqual(200, resp.status)
|
||||
expected_num_count = len(response_body['elements'])
|
||||
|
||||
query_params = "?group_by=" + ','.join(GROUP_BY_ALLOWED_PARAMS)
|
||||
resp, response_body = self.monasca_client.count_alarms(query_params)
|
||||
self.assertEqual(200, resp.status)
|
||||
self._verify_counts_format(response_body, group_by=list(GROUP_BY_ALLOWED_PARAMS))
|
||||
|
||||
# Expect duplicates
|
||||
msg = "Not enough distinct counts. Expected at least {}, found {}".format(expected_num_count,
|
||||
len(response_body['counts']))
|
||||
assert expected_num_count <= len(response_body['counts']), msg
|
||||
|
||||
@test.attr(type='gate')
|
||||
def test_limit(self):
|
||||
resp, response_body = self.monasca_client.count_alarms(
|
||||
"?group_by=metric_name,dimension_name,dimension_value")
|
||||
self.assertEqual(200, resp.status)
|
||||
self._verify_counts_format(response_body,
|
||||
group_by=['metric_name', 'dimension_name', 'dimension_value'])
|
||||
assert len(response_body['counts']) > 1, "Too few counts to test limit, found 1"
|
||||
|
||||
resp, response_body = self.monasca_client.count_alarms(
|
||||
"?group_by=metric_name,dimension_name,dimension_value&limit=1")
|
||||
self.assertEqual(200, resp.status)
|
||||
self._verify_counts_format(response_body,
|
||||
group_by=['metric_name', 'dimension_name', 'dimension_value'],
|
||||
expected_length=1)
|
||||
|
||||
|
||||
|
||||
@test.attr(type='gate')
|
||||
def test_offset(self):
|
||||
resp, response_body = self.monasca_client.count_alarms(
|
||||
"?group_by=metric_name,dimension_name,dimension_value")
|
||||
self.assertEqual(200, resp.status)
|
||||
self._verify_counts_format(response_body,
|
||||
group_by=['metric_name', 'dimension_name', 'dimension_value'])
|
||||
expected_counts = len(response_body['counts']) - 1
|
||||
|
||||
resp, response_body = self.monasca_client.count_alarms(
|
||||
"?group_by=metric_name,dimension_name,dimension_value&offset=1")
|
||||
self.assertEqual(200, resp.status)
|
||||
self._verify_counts_format(response_body,
|
||||
group_by=['metric_name', 'dimension_name', 'dimension_value'],
|
||||
expected_length=expected_counts)
|
||||
|
||||
@test.attr(type='gate')
|
||||
@test.attr(type=['negative'])
|
||||
def test_invalid_offset(self):
|
||||
self.assertRaises(exceptions.UnprocessableEntity,
|
||||
self.monasca_client.count_alarms, "?group_by=metric_name&offset=not_an_int")
|
||||
|
||||
@test.attr(type='gate')
|
||||
def test_limit_and_offset(self):
|
||||
resp, response_body = self.monasca_client.count_alarms(
|
||||
"?group_by=metric_name,dimension_name,dimension_value")
|
||||
self.assertEqual(200, resp.status)
|
||||
self._verify_counts_format(response_body,
|
||||
group_by=['metric_name', 'dimension_name', 'dimension_value'])
|
||||
expected_first_result = response_body['counts'][1]
|
||||
|
||||
resp, response_body = self.monasca_client.count_alarms(
|
||||
"?group_by=metric_name,dimension_name,dimension_value&offset=1&limit=5")
|
||||
self.assertEqual(200, resp.status)
|
||||
self._verify_counts_format(response_body,
|
||||
group_by=['metric_name', 'dimension_name', 'dimension_value'],
|
||||
expected_length=5)
|
||||
self.assertEqual(expected_first_result, response_body['counts'][0])
|
Loading…
Reference in New Issue