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:
Ryan Brandt 2015-12-08 08:29:44 -07:00
parent 50f2310bf0
commit b0ff5227b0
17 changed files with 1088 additions and 38 deletions

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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__()

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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):

View File

@ -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

View File

@ -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:

View File

@ -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])