Allow alarm definition and alarm lists to be sorted

Change offsets to integers instead of ids

Change-Id: I58712dfe2ae8e6c40f52eb4753fc39e6cbfdd3b9
This commit is contained in:
Ryan Brandt 2015-12-15 15:20:00 -07:00
parent b0ff5227b0
commit 0195c9162d
27 changed files with 467 additions and 173 deletions

View File

@ -1786,8 +1786,10 @@ None.
#### Query Parameters
* name (string(255), optional) - Name of alarm to filter by.
* dimensions (string, optional) - Dimensions of metrics to filter by specified as a comma separated array of (key, value) pairs as `key1:value1,key1:value1, ...`
* offset (string, optional)
* offset (integer, optional)
* limit (integer, optional)
* sort_by (string, optional) - Comma separated list of fields to sort by, defaults to 'id', 'created_at'. Fields may be followed by 'asc' or 'desc' to set the direction, ex 'severity desc'
Allowed fields for sort_by are: 'id', 'name', 'severity', 'updated_at', 'created_at'
#### Request Body
None.
@ -2254,8 +2256,10 @@ None.
* 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 (string, optional)
* offset (integer, optional)
* limit (integer, optional)
* sort_by (string, optional) - Comma separated list of fields to sort by, defaults to 'alarm_id'. Fields may be followed by 'asc' or 'desc' to set the direction, ex 'severity desc'
Allowed fields for sort_by are: 'alarm_id', 'alarm_definition_id', 'state', 'severity', 'lifecycle_state', 'link', 'state_updated_timestamp', 'updated_timestamp', 'created_timestamp'
#### Request Body
None.

View File

@ -152,7 +152,7 @@ public class AlarmDefinitionService {
// Have to get information about the Alarms before they are deleted. They will be deleted
// by the database as a cascade delete from the Alarm Definition delete
final List<Alarm> alarms = alarmRepo.find(tenantId, alarmDefId, null, null, null, null, null, null, null, 1, false);
final List<Alarm> alarms = alarmRepo.find(tenantId, alarmDefId, null, null, null, null, null, null, null, null, 1, false);
final Map<String, Map<String, AlarmSubExpression>> alarmSubExpressions =
alarmRepo.findAlarmSubExpressionsForAlarmDefinition(alarmDefId);

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,9 +13,11 @@
*/
package monasca.api.app.validation;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.fasterxml.jackson.databind.JsonMappingException;
@ -220,4 +222,26 @@ public final class Validation {
public static boolean isCrossProjectRequest(String crossTenantId, String tenantId) {
return !Strings.isNullOrEmpty(crossTenantId) && !crossTenantId.equals(tenantId);
}
public static List<String> parseAndValidateSortBy(String sortBy, final List<String> allowed_sort_by) {
List<String> sortByList = new ArrayList<>();
if (sortBy != null && !sortBy.isEmpty()) {
List<String> fieldList = Lists
.newArrayList(Splitter.on(',').omitEmptyStrings().trimResults().split(sortBy));
for (String sortByField: fieldList) {
List<String> field = Lists.newArrayList(Splitter.on(' ').omitEmptyStrings().trimResults().split(sortByField));
if (field.size() > 2) {
throw Exceptions.unprocessableEntity(String.format("Invalid sort_by format %s", sortByField));
}
if (!allowed_sort_by.contains(field.get(0))) {
throw Exceptions.unprocessableEntity(String.format("Sort_by field %s must be one of %s", field.get(0), allowed_sort_by));
}
if (field.size() > 1 && !field.get(1).equals("desc") && !field.get(1).equals("asc")) {
throw Exceptions.unprocessableEntity(String.format("Sort_by value %s must be 'asc' or 'desc'", field.get(1)));
}
sortByList.add(Joiner.on(' ').join(field));
}
}
return sortByList;
}
}

View File

@ -32,7 +32,8 @@ public interface AlarmRepo {
* Returns alarms for the given criteria.
*/
List<Alarm> find(String tenantId, String alarmDefId, String metricName, Map<String,
String> metricDimensions, AlarmState state, String lifecycleState, String link, DateTime stateUpdatedStart, String offset, int limit, boolean enforceLimit);
String> metricDimensions, AlarmState state, String lifecycleState, String link, DateTime stateUpdatedStart,
List<String> sort_by, String offset, int limit, boolean enforceLimit);
/**
* @throws EntityNotFoundException if an alarm cannot be found for the {@code id}

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
@ -48,7 +48,7 @@ public interface AlarmDefinitionRepo {
* Returns alarms for the given criteria.
*/
List<AlarmDefinition> find(String tenantId, String name, Map<String, String> dimensions,
String offset, int limit);
List<String> sortBy, String offset, int limit);
/**
* @throws EntityNotFoundException if an alarm cannot be found for the {@code alarmDefId}

View File

@ -1,5 +1,6 @@
/*
* Copyright 2015 FUJITSU LIMITED
* Copyright 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
@ -50,6 +51,7 @@ import monasca.api.domain.exception.EntityNotFoundException;
import monasca.api.domain.model.alarmdefinition.AlarmDefinition;
import monasca.api.domain.model.alarmdefinition.AlarmDefinitionRepo;
import monasca.api.infrastructure.persistence.SubAlarmDefinitionQueries;
import monasca.api.resource.exception.Exceptions;
import monasca.common.hibernate.db.AlarmActionDb;
import monasca.common.hibernate.db.AlarmDb;
import monasca.common.hibernate.db.AlarmDefinitionDb;
@ -225,8 +227,11 @@ public class AlarmDefinitionSqlRepoImpl
@Override
@SuppressWarnings("unchecked")
public List<AlarmDefinition> find(String tenantId, String name, Map<String, String> dimensions, String offset, int limit) {
public List<AlarmDefinition> find(String tenantId, String name, Map<String, String> dimensions, List<String> sortBy, String offset, int limit) {
logger.trace(ORM_LOG_MARKER, "find(...) entering...");
if (sortBy != null && !sortBy.isEmpty()) {
throw Exceptions.unprocessableEntity("Sort_by is not implemented for the hibernate database type");
}
Session session = null;
List<AlarmDefinition> resultSet = Lists.newArrayList();

View File

@ -1,5 +1,6 @@
/*
* Copyright 2015 FUJITSU LIMITED
* Copyright 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
@ -150,8 +151,12 @@ public class AlarmSqlRepoImpl
public List<Alarm> find(String tenantId, String alarmDefId, String metricName,
Map<String, String> metricDimensions, AlarmState state,
String lifecycleState, String link, DateTime stateUpdatedStart,
List<String> sortBy,
String offset, int limit, boolean enforceLimit) {
logger.trace(ORM_LOG_MARKER, "find(...) entering");
if (sortBy != null && !sortBy.isEmpty()) {
throw Exceptions.unprocessableEntity("Sort_by is not implemented for the hibernate database type");
}
List<Alarm> alarms = this.findInternal(tenantId, alarmDefId, metricName, metricDimensions, state,
lifecycleState, link, stateUpdatedStart, offset, (3 * limit / 2), enforceLimit);

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
@ -139,7 +139,7 @@ public class AlarmDefinitionMySqlRepoImpl implements AlarmDefinitionRepo {
@SuppressWarnings("unchecked")
@Override
public List<AlarmDefinition> find(String tenantId, String name,
Map<String, String> dimensions, String offset, int limit) {
Map<String, String> dimensions, List<String> sortBy, String offset, int limit) {
try (Handle h = db.open()) {
@ -155,10 +155,9 @@ public class AlarmDefinitionMySqlRepoImpl implements AlarmDefinitionRepo {
+ " FROM alarm_definition AS ad "
+ " LEFT OUTER JOIN sub_alarm_definition AS sad ON ad.id = sad.alarm_definition_id "
+ " LEFT OUTER JOIN sub_alarm_definition_dimension AS dim ON sad.id = dim.sub_alarm_definition_id %1$s "
+ " WHERE ad.tenant_id = :tenantId AND ad.deleted_at IS NULL %2$s %3$s) AS t "
+ " WHERE ad.tenant_id = :tenantId AND ad.deleted_at IS NULL %2$s) AS t "
+ "LEFT OUTER JOIN alarm_action AS aa ON t.id = aa.alarm_definition_id "
+ "GROUP BY t.id ORDER BY t.id, t.created_at";
+ "GROUP BY t.id %3$s %4$s %5$s";
StringBuilder sbWhere = new StringBuilder();
@ -166,8 +165,14 @@ public class AlarmDefinitionMySqlRepoImpl implements AlarmDefinitionRepo {
sbWhere.append(" and ad.name = :name");
}
if (offset != null) {
sbWhere.append(" and ad.id > :offset");
String orderByPart = "";
if (sortBy != null && !sortBy.isEmpty()) {
orderByPart = " order by " + COMMA_JOINER.join(sortBy);
if (!orderByPart.contains("id")) {
orderByPart = orderByPart + ",id";
}
} else {
orderByPart = " order by id ";
}
String limitPart = "";
@ -175,8 +180,14 @@ public class AlarmDefinitionMySqlRepoImpl implements AlarmDefinitionRepo {
limitPart = " limit :limit";
}
String offsetPart = "";
if (offset != null) {
offsetPart = " offset " + offset + ' ';
}
String sql = String.format(query,
SubAlarmDefinitionQueries.buildJoinClauseFor(dimensions), sbWhere, limitPart);
SubAlarmDefinitionQueries.buildJoinClauseFor(dimensions), sbWhere, orderByPart,
limitPart, offsetPart);
Query<?> q = h.createQuery(sql);
@ -186,10 +197,6 @@ public class AlarmDefinitionMySqlRepoImpl implements AlarmDefinitionRepo {
q.bind("name", name);
}
if (offset != null) {
q.bind("offset", offset);
}
if (limit > 0) {
q.bind("limit", limit + 1);
}

View File

@ -50,16 +50,15 @@ import javax.inject.Named;
*/
public class AlarmMySqlRepoImpl implements AlarmRepo {
private static final Joiner COMMA_JOINER = Joiner.on(',');
private static final Logger logger = LoggerFactory.getLogger(AlarmMySqlRepoImpl.class);
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, "
+ "a.id as alarm_id, a.state, a.lifecycle_state, a.link, a.state_updated_at as state_updated_timestamp, "
+ "a.updated_at as updated_timestamp, a.created_at as created_timestamp, "
+ "md.name as metric_name, mdg.dimensions as metric_dimensions from alarm as a "
+ "inner join alarm_definition ad on ad.id = a.alarm_definition_id "
@ -72,18 +71,18 @@ public class AlarmMySqlRepoImpl implements AlarmRepo {
private static final String FIND_ALARMS_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, "
+ "a.id as alarm_id, a.state, a.lifecycle_state, a.link, a.state_updated_at as state_updated_timestamp, "
+ "a.updated_at as updated_timestamp, a.created_at as created_timestamp, "
+ "md.name as metric_name, group_concat(mdim.name, '=', mdim.value order by mdim.name) as metric_dimensions "
+ "from alarm as a "
+ "inner join %s as alarm_id_list on alarm_id_list.id = a.id "
+ "inner join %1$s as alarm_id_list on alarm_id_list.id = a.id "
+ "inner join alarm_definition ad on ad.id = a.alarm_definition_id "
+ "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 metric_definition as md on md.id = mdd.metric_definition_id "
+ "left outer join metric_dimension as mdim on mdim.dimension_set_id = mdd.metric_dimension_set_id "
+ "group by a.id, md.name, mdim.dimension_set_id "
+ "order by a.id ASC";
+ "%2$s";
@Inject
public AlarmMySqlRepoImpl(@Named("mysql") DBI db, PersistUtils persistUtils) {
@ -122,8 +121,8 @@ public class AlarmMySqlRepoImpl implements AlarmRepo {
@Override
public List<Alarm> find(String tenantId, String alarmDefId, String metricName,
Map<String, String> metricDimensions, AlarmState state,
String lifecycleState, String link, DateTime stateUpdatedStart, String offset,
int limit, boolean enforceLimit) {
String lifecycleState, String link, DateTime stateUpdatedStart,
List<String> sortBy, String offset, int limit, boolean enforceLimit) {
StringBuilder
sbWhere =
@ -180,19 +179,32 @@ public class AlarmMySqlRepoImpl implements AlarmRepo {
sbWhere.append(" and a.state_updated_at >= :stateUpdatedStart");
}
if (offset != null) {
sbWhere.append(" and a.id > :offset");
StringBuilder sortByClause = new StringBuilder();
if (sortBy != null && !sortBy.isEmpty()) {
sortByClause.append(" order by ");
sortByClause.append(COMMA_JOINER.join(sortBy));
// if alarm_id is not in the list, add it
if (sortByClause.indexOf("alarm_id") == -1) {
sortByClause.append(",alarm_id ASC");
}
sortByClause.append(' ');
} else {
sortByClause.append(" order by alarm_id ASC ");
}
sbWhere.append(" order by a.id ASC ");
if (enforceLimit && limit > 0) {
sbWhere.append(" limit :limit");
}
if (offset != null) {
sbWhere.append(" offset ");
sbWhere.append(offset);
sbWhere.append(' ');
}
sbWhere.append(")");
String sql = String.format(FIND_ALARMS_SQL, sbWhere);
String sql = String.format(FIND_ALARMS_SQL, sbWhere, sortByClause);
try (Handle h = db.open()) {
@ -222,10 +234,6 @@ public class AlarmMySqlRepoImpl implements AlarmRepo {
q.bind("stateUpdatedStart", stateUpdatedStart.toString());
}
if (offset != null) {
q.bind("offset", offset);
}
if (enforceLimit && limit > 0) {
q.bind("limit", limit + 1);
}
@ -267,7 +275,7 @@ public class AlarmMySqlRepoImpl implements AlarmRepo {
final List<Alarm> alarms = new LinkedList<>();
List<MetricDefinition> alarmedMetrics = null;
for (final Map<String, Object> row : rows) {
final String alarmId = (String) row.get("id");
final String alarmId = (String) row.get("alarm_id");
if (!alarmId.equals(previousAlarmId)) {
alarmedMetrics = new ArrayList<>();
alarm =

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
@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.JsonMappingException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@ -62,6 +63,8 @@ public class AlarmDefinitionResource {
private final PersistUtils persistUtils;
public final static String ALARM_DEFINITIONS = "alarm-definitions";
public final static String ALARM_DEFINITIONS_PATH = "/v2.0/" + ALARM_DEFINITIONS;
private final static List<String> ALLOWED_SORT_BY = Arrays.asList("id", "name", "severity",
"updated_at", "created_at");
@Inject
public AlarmDefinitionResource(AlarmDefinitionService service,
@ -93,20 +96,27 @@ public class AlarmDefinitionResource {
public Object list(@Context UriInfo uriInfo,
@HeaderParam("X-Tenant-Id") String tenantId, @QueryParam("name") String name,
@QueryParam("dimensions") String dimensionsStr,
@QueryParam("sort_by") String sortByStr,
@QueryParam("offset") String offset,
@QueryParam("limit") String limit) throws UnsupportedEncodingException {
Map<String, String> dimensions =
Strings.isNullOrEmpty(dimensionsStr) ? null : Validation
.parseAndValidateDimensions(dimensionsStr);
List<String> sortByList = Validation.parseAndValidateSortBy(sortByStr, ALLOWED_SORT_BY);
if (!Strings.isNullOrEmpty(offset)) {
Validation.parseAndValidateNumber(offset, "offset");
}
final int paging_limit = this.persistUtils.getLimit(limit);
final List<AlarmDefinition> resources = repo.find(tenantId,
name,
dimensions,
offset,
paging_limit
name,
dimensions,
sortByList,
offset,
paging_limit
);
return Links.paginate(paging_limit, Links.hydrate(resources, uriInfo), uriInfo);
return Links.paginateAlarming(paging_limit, Links.hydrate(resources, uriInfo), uriInfo);
}
@GET

View File

@ -71,6 +71,10 @@ public class AlarmResource {
"lifecycle_state",
"metric_name", "dimension_name",
"dimension_value");
private final static List<String> ALLOWED_SORT_BY = Arrays.asList("alarm_id", "alarm_definition_id", "state",
"severity", "lifecycle_state", "link",
"state_updated_timestamp", "updated_timestamp",
"created_timestamp");
@Inject
public AlarmResource(AlarmService service, AlarmRepo repo,
@ -153,11 +157,11 @@ public class AlarmResource {
final int paging_limit = this.persistUtils.getLimit(limit);
final List<AlarmStateHistory> resources = stateHistoryRepo.find(tenantId,
dimensions,
startTime,
endTime,
offset,
paging_limit
dimensions,
startTime,
endTime,
offset,
paging_limit
);
return Links.paginate(paging_limit, resources, uriInfo);
}
@ -173,6 +177,7 @@ public class AlarmResource {
@QueryParam("lifecycle_state") String lifecycleState,
@QueryParam("link") String link,
@QueryParam("state_updated_start_time") String stateUpdatedStartStr,
@QueryParam("sort_by") String sortBy,
@QueryParam("offset") String offset,
@QueryParam("limit") String limit)
throws Exception {
@ -185,9 +190,14 @@ public class AlarmResource {
Validation.parseAndValidateDate(stateUpdatedStartStr,
"state_updated_start_time", false);
List<String> sortByList = Validation.parseAndValidateSortBy(sortBy, ALLOWED_SORT_BY);
if (!Strings.isNullOrEmpty(offset)) {
Validation.parseAndValidateNumber(offset, "offset");
}
final int paging_limit = this.persistUtils.getLimit(limit);
final List<Alarm> alarms = repo.find(tenantId, alarmDefId, metricName, metricDimensions, state,
lifecycleState, link, stateUpdatedStart,
lifecycleState, link, stateUpdatedStart, sortByList,
offset, paging_limit, true);
for (final Alarm alarm : alarms) {
Links.hydrate(
@ -196,9 +206,11 @@ public class AlarmResource {
AlarmDefinitionResource.ALARM_DEFINITIONS_PATH
);
}
return Links.paginate(paging_limit, Links.hydrate(alarms, uriInfo), uriInfo);
return Links.paginateAlarming(paging_limit, Links.hydrate(alarms, uriInfo), uriInfo);
}
@PATCH
@Timed
@Path("/{alarm_id}")

View File

@ -197,6 +197,44 @@ public final class Links {
}
public static Object paginateAlarming(int limit, List<? extends AbstractEntity> elements, UriInfo uriInfo)
throws UnsupportedEncodingException {
// Check for paging turned off. Happens if maxQueryLimit is not set or is set to zero.
if (limit == 0) {
Paged paged = new Paged();
paged.elements = elements != null ? elements : new ArrayList<>();
return paged;
}
Paged paged = new Paged();
paged.links.add(getSelfLink(uriInfo));
if (elements != null) {
if (elements.size() > limit) {
String offset = String.valueOf(limit);
paged.links.add(getNextLink(offset, uriInfo));
// Truncate the list. Normally this will just truncate one extra element.
elements = elements.subList(0, limit);
}
paged.elements = elements;
} else {
paged.elements = new ArrayList<>();
}
return paged;
}
public static Object paginateMeasurements(int limit, List<Measurements> elements, UriInfo uriInfo)
throws UnsupportedEncodingException {

View File

@ -1,5 +1,6 @@
/*
* Copyright 2015 FUJITSU LIMITED
* Copyright 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
@ -49,6 +50,8 @@ import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import javax.ws.rs.WebApplicationException;
@Test(groups = "orm")
public class AlarmDefinitionSqlRepositoryImplTest {
@ -340,33 +343,38 @@ public class AlarmDefinitionSqlRepositoryImplTest {
fail();
} catch (EntityNotFoundException expected) {
}
assertEquals(Arrays.asList(alarmDef_234), repo.find("bob", null, null, null, 1));
assertEquals(Arrays.asList(alarmDef_234), repo.find("bob", null, null, null, null, 1));
}
public void shouldFindByDimension() {
final Map<String, String> dimensions = new HashMap<>();
dimensions.put("image_id", "888");
List<AlarmDefinition> result = repo.find("bob", null, dimensions, null, 1);
List<AlarmDefinition> result = repo.find("bob", null, dimensions, null, null, 1);
assertEquals(Arrays.asList(alarmDef_123, alarmDef_234), result);
dimensions.clear();
dimensions.put("device", "1");
assertEquals(Arrays.asList(alarmDef_123), repo.find("bob", null, dimensions, null, 1));
assertEquals(Arrays.asList(alarmDef_123), repo.find("bob", null, dimensions, null, null, 1));
dimensions.clear();
dimensions.put("Not real", "AA");
assertEquals(0, repo.find("bob", null, dimensions, null, 1).size());
assertEquals(0, repo.find("bob", null, dimensions, null, null, 1).size());
}
public void shouldFindByName() {
final Map<String, String> dimensions = new HashMap<>();
dimensions.put("image_id", "888");
List<AlarmDefinition> result = repo.find("bob", "90% CPU", dimensions, null, 1);
List<AlarmDefinition> result = repo.find("bob", "90% CPU", dimensions, null, null, 1);
assertEquals(Arrays.asList(alarmDef_123), result);
}
@Test(groups = "orm", expectedExceptions = WebApplicationException.class)
public void shouldFindThrowException() {
repo.find("bob", null, null, Arrays.asList("severity", "state"), null, 1);
}
}

View File

@ -1,5 +1,6 @@
/*
* Copyright 2015 FUJITSU LIMITED
* Copyright 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
@ -24,6 +25,7 @@ import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import javax.ws.rs.WebApplicationException;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
@ -346,55 +348,60 @@ public class AlarmSqlRepositoryImplTest {
@Test(groups = "orm")
public void shouldFind() {
checkList(repo.find("Not a tenant id", null, null, null, null, null, null, null, null, 1, false));
checkList(repo.find("Not a tenant id", null, null, null, null, null, null, null, null, null, 1, false));
checkList(repo.find(TENANT_ID, null, null, null, null, null, null, null, null, 1, false), alarm1, alarm2, alarm3, compoundAlarm);
checkList(repo.find(TENANT_ID, null, null, null, null, null, null, null, null, null, 1, false), alarm1, alarm2, alarm3, compoundAlarm);
checkList(repo.find(TENANT_ID, compoundAlarm.getAlarmDefinition().getId(), null, null, null, null, null, null, null, 1, false), compoundAlarm);
checkList(repo.find(TENANT_ID, compoundAlarm.getAlarmDefinition().getId(), null, null, null, null, null, null, null, null, 1, false), compoundAlarm);
checkList(repo.find(TENANT_ID, null, "cpu.sys_mem", null, null, null, null, null, null, 1, false), compoundAlarm);
checkList(repo.find(TENANT_ID, null, "cpu.sys_mem", null, null, null, null, null, null, null, 1, false), compoundAlarm);
checkList(repo.find(TENANT_ID, null, "cpu.idle_perc", null, null, null, null, null, null, 1, false), alarm1, alarm2, alarm3, compoundAlarm);
checkList(repo.find(TENANT_ID, null, "cpu.idle_perc", null, null, null, null, null, null, null, 1, false), alarm1, alarm2, alarm3, compoundAlarm);
checkList(repo.find(TENANT_ID, null, "cpu.idle_perc", ImmutableMap.<String, String>builder().put("flavor_id", "222").build(), null, null, null,
null, null, 1, false), alarm1, alarm3);
null, null, null, 1, false), alarm1, alarm3);
checkList(
repo.find(TENANT_ID, null, "cpu.idle_perc", ImmutableMap.<String, String>builder().put("service", "monitoring").put("hostname", "roland")
.build(), null, null, null, null, null, 1, false), compoundAlarm);
.build(), null, null, null, null, null, null, 1, false), compoundAlarm);
checkList(repo.find(TENANT_ID, null, null, null, AlarmState.UNDETERMINED, null, null, null, null, 1, false), alarm2, compoundAlarm);
checkList(repo.find(TENANT_ID, null, null, null, AlarmState.UNDETERMINED, null, null, null, null, null, 1, false), alarm2, compoundAlarm);
checkList(
repo.find(TENANT_ID, alarm1.getAlarmDefinition().getId(), "cpu.idle_perc", ImmutableMap.<String, String>builder()
.put("service", "monitoring").build(), null, null, null, null, null, 1, false), alarm1, alarm2);
.put("service", "monitoring").build(), null, null, null, null, null, null, 1, false), alarm1, alarm2);
checkList(repo.find(TENANT_ID, alarm1.getAlarmDefinition().getId(), "cpu.idle_perc", null, null, null, null, null, null, 1, false), alarm1,
checkList(repo.find(TENANT_ID, alarm1.getAlarmDefinition().getId(), "cpu.idle_perc", null, null, null, null, null, null, null, 1, false), alarm1,
alarm2, alarm3);
checkList(
repo.find(TENANT_ID, compoundAlarm.getAlarmDefinition().getId(), null, null, AlarmState.UNDETERMINED, null, null, null, null, 1, false),
repo.find(TENANT_ID, compoundAlarm.getAlarmDefinition().getId(), null, null, AlarmState.UNDETERMINED, null, null, null, null, null, 1, false),
compoundAlarm);
checkList(repo.find(TENANT_ID, null, "cpu.sys_mem", null, AlarmState.UNDETERMINED, null, null, null, null, 1, false), compoundAlarm);
checkList(repo.find(TENANT_ID, null, "cpu.sys_mem", null, AlarmState.UNDETERMINED, null, null, null, null, null, 1, false), compoundAlarm);
checkList(repo.find(TENANT_ID, null, "cpu.idle_perc", ImmutableMap.<String, String>builder().put("service", "monitoring").build(),
AlarmState.UNDETERMINED, null, null, null, null, 1, false), alarm2, compoundAlarm);
AlarmState.UNDETERMINED, null, null, null, null, null, 1, false), alarm2, compoundAlarm);
checkList(
repo.find(TENANT_ID, alarm1.getAlarmDefinition().getId(), "cpu.idle_perc", ImmutableMap.<String, String>builder()
.put("service", "monitoring").build(), AlarmState.UNDETERMINED, null, null, null, null, 1, false), alarm2);
.put("service", "monitoring").build(), AlarmState.UNDETERMINED, null, null, null, null, null, 1, false), alarm2);
checkList(repo.find(TENANT_ID, null, null, null, null, null, null, DateTime.now(UTC_TIMEZONE), null, 0, false));
checkList(repo.find(TENANT_ID, null, null, null, null, null, null, DateTime.now(UTC_TIMEZONE), null, null, 0, false));
//checkList(repo.find(TENANT_ID, null, null, null, null, null, null, ISO_8601_FORMATTER.parseDateTime("2015-03-15T00:00:00Z"), null, 0, false),
// compoundAlarm);
checkList(repo.find(TENANT_ID, null, null, null, null, null, null, ISO_8601_FORMATTER.parseDateTime("2015-03-14T00:00:00Z"), null, 1, false),
checkList(repo.find(TENANT_ID, null, null, null, null, null, null, ISO_8601_FORMATTER.parseDateTime("2015-03-14T00:00:00Z"), null, null, 1, false),
alarm1, alarm2, alarm3, compoundAlarm);
}
@Test(groups = "orm", expectedExceptions = WebApplicationException.class)
public void shouldFindThrowException() {
repo.find(TENANT_ID, null, null, null, null, null, null, null, Arrays.asList("severity", "state"), null, 1, true);
}
@Test(groups = "orm")
public void shouldFindById() {

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
@ -245,36 +245,42 @@ public class AlarmDefinitionMySqlRepositoryImplTest {
}
public void shouldFind() {
assertEquals(Arrays.asList(alarmDef_123, alarmDef_234), repo.find("bob", null, null, null, 1));
assertEquals(Arrays.asList(alarmDef_123, alarmDef_234), repo.find("bob", null, null, null, null, 1));
// Make sure it still finds AlarmDefinitions with no notifications
handle.execute("delete from alarm_action");
alarmDef_123.setAlarmActions(new ArrayList<String>(0));
alarmDef_234.setAlarmActions(new ArrayList<String>(0));
assertEquals(Arrays.asList(alarmDef_123, alarmDef_234), repo.find("bob", null, null, null, 1));
assertEquals(Arrays.asList(alarmDef_123, alarmDef_234), repo.find("bob", null, null, null, null, 1));
assertEquals(0, repo.find("bill", null, null, null, 1).size());
assertEquals(0, repo.find("bill", null, null, null, null, 1).size());
assertEquals(Arrays.asList(alarmDef_234, alarmDef_123),
repo.find("bob", null, null, Arrays.asList("name"), null, 1));
assertEquals(Arrays.asList(alarmDef_234, alarmDef_123),
repo.find("bob", null, null, Arrays.asList("id desc"), null, 1));
}
public void shouldFindByDimension() {
final Map<String, String> dimensions = new HashMap<>();
dimensions.put("image_id", "888");
assertEquals(Arrays.asList(alarmDef_123, alarmDef_234),
repo.find("bob", null, dimensions, null, 1));
repo.find("bob", null, dimensions, null, null, 1));
dimensions.clear();
dimensions.put("device", "1");
assertEquals(Arrays.asList(alarmDef_123), repo.find("bob", null, dimensions, null, 1));
assertEquals(Arrays.asList(alarmDef_123), repo.find("bob", null, dimensions, null, null, 1));
dimensions.clear();
dimensions.put("Not real", "AA");
assertEquals(0, repo.find("bob", null, dimensions, null, 1).size());
assertEquals(0, repo.find("bob", null, dimensions, null, null, 1).size());
}
public void shouldFindByName() {
assertEquals(Arrays.asList(alarmDef_123), repo.find("bob", "90% CPU", null, null, 1));
assertEquals(Arrays.asList(alarmDef_123), repo.find("bob", "90% CPU", null, null, null, 1));
assertEquals(0, repo.find("bob", "Does not exist", null, null, 1).size());
assertEquals(0, repo.find("bob", "Does not exist", null, null, null, 1).size());
}
public void shouldDeleteById() {
@ -285,6 +291,6 @@ public class AlarmDefinitionMySqlRepositoryImplTest {
fail();
} catch (EntityNotFoundException expected) {
}
assertEquals(Arrays.asList(alarmDef_234), repo.find("bob", null, null, null, 1));
assertEquals(Arrays.asList(alarmDef_234), repo.find("bob", null, null, null, null, 1));
}
}

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
@ -166,7 +166,7 @@ public class AlarmMySqlRepositoryImplTest {
handle
.execute(
"insert into alarm_definition (id, tenant_id, name, severity, expression, match_by, actions_enabled, created_at, updated_at, deleted_at) "
+ "values ('234', 'bob', '50% CPU', 'LOW', 'avg(cpu.sys_mem{service=monitoring}) > 20 and avg(cpu.idle_perc{service=monitoring}) < 10', 'hostname,region', 1, NOW(), NOW(), NULL)");
+ "values ('234', 'bob', '50% CPU', 'HIGH', 'avg(cpu.sys_mem{service=monitoring}) > 20 and avg(cpu.idle_perc{service=monitoring}) < 10', 'hostname,region', 1, NOW(), NOW(), NULL)");
handle
.execute("insert into alarm (id, alarm_definition_id, state, created_at, updated_at, state_updated_at) values ('234111', '234', 'UNDETERMINED', '"+timestamp4.toString().replace('Z', ' ')+"', '"+timestamp4.toString().replace('Z', ' ')+"', '"+timestamp4.toString().replace('Z', ' ')+"')");
handle
@ -202,7 +202,7 @@ public class AlarmMySqlRepositoryImplTest {
.execute("insert into metric_dimension (dimension_set_id, name, value) values (22, 'extra', 'vivi')");
compoundAlarm =
new Alarm("234111", "234", "50% CPU", "LOW", buildAlarmMetrics(
new Alarm("234111", "234", "50% CPU", "HIGH", buildAlarmMetrics(
buildMetricDefinition("cpu.sys_mem", "service", "monitoring", "hostname", "roland",
"region", "colorado"),
buildMetricDefinition("cpu.idle_perc", "service", "monitoring", "hostname", "roland",
@ -271,58 +271,64 @@ public class AlarmMySqlRepositoryImplTest {
@Test(groups = "database")
public void shouldFind() {
checkList(repo.find("Not a tenant id", null, null, null, null, null, null, null, null, 1, false));
checkList(repo.find("Not a tenant id", null, null, null, null, null, null, null, null, null, 1, false));
checkList(repo.find(TENANT_ID, null, null, null, null, null, null, null, null, 1, false), alarm1, alarm2, alarm3, compoundAlarm);
checkList(repo.find(TENANT_ID, null, null, null, null, null, null, null, null, null, 1, false), alarm1, alarm2, alarm3, compoundAlarm);
checkList(repo.find(TENANT_ID, compoundAlarm.getAlarmDefinition().getId(), null, null, null, null, null, null, null, 1, false), compoundAlarm);
checkList(repo.find(TENANT_ID, compoundAlarm.getAlarmDefinition().getId(), null, null, null, null, null, null, null, null, 1, false), compoundAlarm);
checkList(repo.find(TENANT_ID, null, "cpu.sys_mem", null, null, null, null, null, null, 1, false), compoundAlarm);
checkList(repo.find(TENANT_ID, null, "cpu.sys_mem", null, null, null, null, null, null, null, 1, false), compoundAlarm);
checkList(repo.find(TENANT_ID, null, "cpu.idle_perc", null, null, null, null, null, null, 1, false), alarm1, alarm2, alarm3, compoundAlarm);
checkList(repo.find(TENANT_ID, null, "cpu.idle_perc", null, null, null, null, null, null, null, 1, false), alarm1, alarm2, alarm3, compoundAlarm);
checkList(
repo.find(TENANT_ID, null, "cpu.idle_perc",
ImmutableMap.<String, String>builder().put("flavor_id", "222").build(), null, null, null, null, null, 1, false), alarm1,
ImmutableMap.<String, String>builder().put("flavor_id", "222").build(), null, null, null, null, null, null, 1, false), alarm1,
alarm3);
checkList(
repo.find(TENANT_ID, null, "cpu.idle_perc",
ImmutableMap.<String, String>builder().put("service", "monitoring")
.put("hostname", "roland").build(), null, null, null, null, null, 1, false), compoundAlarm);
.put("hostname", "roland").build(), null, null, null, null, null, null, 1, false), compoundAlarm);
checkList(repo.find(TENANT_ID, null, null, null, AlarmState.UNDETERMINED, null, null, null, null, 1, false),
checkList(repo.find(TENANT_ID, null, null, null, AlarmState.UNDETERMINED, null, null, null, null, null, 1, false),
alarm2,
compoundAlarm);
checkList(
repo.find(TENANT_ID, alarm1.getAlarmDefinition().getId(), "cpu.idle_perc", ImmutableMap
.<String, String>builder().put("service", "monitoring").build(), null, null, null, null, null, 1, false), alarm1, alarm2);
.<String, String>builder().put("service", "monitoring").build(), null, null, null, null, null, null, 1, false), alarm1, alarm2);
checkList(
repo.find(TENANT_ID, alarm1.getAlarmDefinition().getId(), "cpu.idle_perc", null, null, null, null, null, null, 1, false),
repo.find(TENANT_ID, alarm1.getAlarmDefinition().getId(), "cpu.idle_perc", null, null, null, null, null, null, null, 1, false),
alarm1, alarm2, alarm3);
checkList(repo.find(TENANT_ID, compoundAlarm.getAlarmDefinition().getId(), null, null,
AlarmState.UNDETERMINED, null, null, null, null, 1, false), compoundAlarm);
AlarmState.UNDETERMINED, null, null, null, null, null, 1, false), compoundAlarm);
checkList(repo.find(TENANT_ID, null, "cpu.sys_mem", null, AlarmState.UNDETERMINED, null, null, null, null, 1, false),
checkList(repo.find(TENANT_ID, null, "cpu.sys_mem", null, AlarmState.UNDETERMINED, null, null, null, null, null, 1, false),
compoundAlarm);
checkList(repo.find(TENANT_ID, null, "cpu.idle_perc", ImmutableMap.<String, String>builder()
.put("service", "monitoring").build(), AlarmState.UNDETERMINED, null, null, null, null, 1,false), alarm2, compoundAlarm);
.put("service", "monitoring").build(), AlarmState.UNDETERMINED, null, null, null, null, null, 1,false), alarm2, compoundAlarm);
checkList(repo.find(TENANT_ID, alarm1.getAlarmDefinition().getId(), "cpu.idle_perc",
ImmutableMap.<String, String>builder().put("service", "monitoring").build(),
AlarmState.UNDETERMINED, null, null, null, null, 1, false), alarm2);
AlarmState.UNDETERMINED, null, null, null, null, null, 1, false), alarm2);
checkList(repo.find(TENANT_ID, null, null, null, null, null, null, DateTime.now(DateTimeZone.forID("UTC")), null, 0, false));
checkList(repo.find(TENANT_ID, null, null, null, null, null, null, DateTime.now(DateTimeZone.forID("UTC")), null, null, 0, false));
checkList(repo.find(TENANT_ID, null, null, null, null, null, null, ISO_8601_FORMATTER.parseDateTime("2015-03-15T00:00:00Z"), null, 0, false), compoundAlarm);
checkList(repo.find(TENANT_ID, null, null, null, null, null, null, ISO_8601_FORMATTER.parseDateTime("2015-03-15T00:00:00Z"), null, null, 0, false), compoundAlarm);
checkList(
repo.find(TENANT_ID, null, null, null, null, null, null, ISO_8601_FORMATTER.parseDateTime("2015-03-14T00:00:00Z"), null,
repo.find(TENANT_ID, null, null, null, null, null, null, ISO_8601_FORMATTER.parseDateTime("2015-03-14T00:00:00Z"), null, null,
1, false), alarm1, alarm2, alarm3, compoundAlarm);
checkList(repo.find(TENANT_ID, null, null, null, null, null, null, null, Arrays.asList("state","severity"), null, 1, false),
alarm1, alarm2, compoundAlarm, alarm3);
checkList(repo.find(TENANT_ID, null, null, null, null, null, null, null, Arrays.asList("state desc","severity"), null, 1, false),
compoundAlarm, alarm3, alarm2, alarm1);
}
private DateTime getAlarmStateUpdatedDate(final String alarmId) {

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
@ -16,6 +16,7 @@ package monasca.api.resource;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyList;
import static org.mockito.Matchers.anyMap;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.anyString;
@ -85,7 +86,8 @@ public class AlarmDefinitionResourceTest extends AbstractMonApiResourceTest {
repo = mock(AlarmDefinitionRepo.class);
when(repo.findById(eq("abc"), eq("123"))).thenReturn(alarm);
when(repo.find(anyString(), anyString(), (Map<String, String>) anyMap(), anyString(), anyInt())).thenReturn(
when(repo.find(anyString(), anyString(), (Map<String, String>) anyMap(), (List<String>) anyList(),
anyString(), anyInt())).thenReturn(
Arrays.asList(alarmItem));
addResources(new AlarmDefinitionResource(service, repo, new PersistUtils()));
@ -275,7 +277,8 @@ public class AlarmDefinitionResourceTest extends AbstractMonApiResourceTest {
assertEquals(alarms, Arrays.asList(alarmItem));
verify(repo).find(eq("abc"), anyString(), (Map<String, String>) anyMap(), anyString(), anyInt());
verify(repo).find(eq("abc"), anyString(), (Map<String, String>) anyMap(), (List<String>) anyList(),
anyString(), anyInt());
}
@SuppressWarnings("unchecked")
@ -306,8 +309,8 @@ public class AlarmDefinitionResourceTest extends AbstractMonApiResourceTest {
List<AlarmDefinition> alarms = Arrays.asList(ad);
assertEquals(alarms, Arrays.asList(alarmItem));
verify(repo).find(eq("abc"), eq("foo bar baz"), (Map<String, String>) anyMap(), anyString(),
anyInt());
verify(repo).find(eq("abc"), eq("foo bar baz"), (Map<String, String>) anyMap(), (List<String>) anyList(),
anyString(), anyInt());
}
public void shouldGet() {
@ -352,13 +355,13 @@ public class AlarmDefinitionResourceTest extends AbstractMonApiResourceTest {
public void should500OnInternalException() {
doThrow(new RuntimeException("")).when(repo).find(anyString(), anyString(),
(Map<String, String>) anyObject(), anyString(), anyInt());
(Map<String, String>) anyObject(), (List<String>) anyList(), anyString(), anyInt());
try {
client().resource("/v2.0/alarm-definitions").header("X-Tenant-Id", "abc").get(List.class);
fail();
} catch (Exception e) {
assertTrue(e.getMessage().contains("500"));
assertTrue(e.getMessage().contains("500"), e.getMessage());
}
}

View File

@ -1,4 +1,4 @@
# Copyright 2014 Hewlett-Packard
# Copyright 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
@ -50,8 +50,8 @@ class AlarmDefinitionsRepository(object):
pass
@abc.abstractmethod
def get_alarm_definitions(self, tenant_id, name, dimensions, offset,
limit):
def get_alarm_definitions(self, tenant_id, name, dimensions, sort_by,
offset, limit):
pass
@abc.abstractmethod

View File

@ -1,4 +1,4 @@
# Copyright 2014 Hewlett-Packard
# Copyright 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
@ -76,8 +76,8 @@ class AlarmDefinitionsRepository(mysql_repository.MySQLRepository,
raise exceptions.DoesNotExistException
@mysql_repository.mysql_try_catch_block
def get_alarm_definitions(self, tenant_id, name, dimensions, offset,
limit):
def get_alarm_definitions(self, tenant_id, name, dimensions,
sort_by, offset, limit):
parms = [tenant_id]
@ -89,14 +89,20 @@ class AlarmDefinitionsRepository(mysql_repository.MySQLRepository,
where_clause += " and ad.name = %s "
parms.append(name.encode('utf8'))
order_by_clause = " order by ad.id, ad.created_at "
if sort_by is not None:
order_by_clause = " order by " + ','.join(sort_by)
if 'id' not in sort_by:
order_by_clause += ",ad.id "
else:
order_by_clause += " "
else:
order_by_clause = " order by ad.id "
limit_offset_clause = " limit %s "
parms.append(limit + 1)
if offset:
where_clause += " and ad.id > %s "
parms.append(offset.encode('utf8'))
limit_clause = " limit %s "
parms.append(limit + 1)
limit_offset_clause += ' offset {}'.format(offset)
if dimensions:
inner_join = """ inner join sub_alarm_definition as sad
@ -119,7 +125,7 @@ class AlarmDefinitionsRepository(mysql_repository.MySQLRepository,
select_clause += inner_join
parms = inner_join_parms + parms
query = select_clause + where_clause + order_by_clause + limit_clause
query = select_clause + where_clause + order_by_clause + limit_offset_clause
return self._execute_query(query, parms)

View File

@ -214,14 +214,8 @@ class AlarmsRepository(mysql_repository.MySQLRepository,
select_clause = AlarmsRepository.base_query
order_by_clause = " order by a.id "
where_clause = " where ad.tenant_id = %s "
if offset:
where_clause += " and a.id > %s"
parms.append(offset.encode('utf8'))
if 'alarm_definition_id' in query_parms:
parms.append(query_parms['alarm_definition_id'])
where_clause += " and ad.id = %s "
@ -287,13 +281,29 @@ class AlarmsRepository(mysql_repository.MySQLRepository,
parms += sub_select_parms
where_clause += sub_select_clause
if 'sort_by' in query_parms:
order_by_clause = " order by " + ','.join(query_parms['sort_by'])
if 'alarm_id' not in query_parms['sort_by']:
order_by_clause += ",alarm_id "
else:
order_by_clause += " "
else:
order_by_clause = " order by a.id "
if offset:
offset_clause = " offset {}".format(offset)
else:
offset_clause = ""
if limit:
limit_clause = " limit %s "
parms.append(limit + 1)
else:
limit_clause = ""
query = select_clause + where_clause + order_by_clause + limit_clause
query = select_clause + where_clause + order_by_clause + limit_clause + offset_clause
LOG.debug("Query: {}".format(query))
return self._execute_query(query, parms)

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

View File

@ -1,4 +1,4 @@
# Copyright 2015 Hewlett-Packard
# Copyright 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
@ -12,9 +12,11 @@
# License for the specific language governing permissions and limitations
# under the License.
from monasca_api.v2.common.exceptions import HTTPUnprocessableEntityError
import re
invalid_chars = "<>={}(),\"\\\\;&"
invalid_chars = "<>={}(),'\"\\\\;&"
restricted_chars = re.compile('[' + invalid_chars + ']')
@ -34,3 +36,20 @@ def dimension_value(value):
assert isinstance(value, (str, unicode)), "Dimension value must be a string"
assert len(value) <= 255, "Dimension value must be 255 characters or less"
assert not restricted_chars.search(value), "Invalid characters in dimension value " + value
def validate_sort_by(sort_by_list, allowed_sort_by):
for sort_by_field in sort_by_list:
sort_by_values = sort_by_field.split()
if len(sort_by_values) > 2:
raise HTTPUnprocessableEntityError("Unprocessable Entity",
"Invalid sort_by {}".format(sort_by_field))
if sort_by_values[0] not in allowed_sort_by:
raise HTTPUnprocessableEntityError("Unprocessable Entity",
"sort_by field {} must be one of [{}]".format(
sort_by_values[0],
','.join(list(allowed_sort_by))))
if len(sort_by_values) > 1 and sort_by_values[1] not in ['asc', 'desc']:
raise HTTPUnprocessableEntityError("Unprocessable Entity",
"sort_by value {} must be 'asc' or 'desc'".format(
sort_by_values[1]))

View File

@ -1,4 +1,4 @@
# Copyright 2014 Hewlett-Packard
# Copyright 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
@ -85,11 +85,27 @@ class AlarmDefinitions(alarm_definitions_api_v2.AlarmDefinitionsV2API,
tenant_id = helpers.get_tenant_id(req)
name = helpers.get_query_name(req)
dimensions = helpers.get_query_dimensions(req)
sort_by = helpers.get_query_param(req, 'sort_by', default_val=None)
if sort_by is not None:
if isinstance(sort_by, basestring):
sort_by = [sort_by]
allowed_sort_by = {'id', 'name', 'severity',
'updated_at', 'created_at'}
validation.validate_sort_by(sort_by, allowed_sort_by)
offset = helpers.get_query_param(req, 'offset')
if offset is not None and not isinstance(offset, int):
try:
offset = int(offset)
except Exception:
raise HTTPUnprocessableEntityError('Unprocessable Entity',
'Offset value {} must be an integer'.format(offset))
limit = helpers.get_limit(req)
result = self._alarm_definition_list(tenant_id, name, dimensions,
req.uri, offset, limit)
req.uri, sort_by, offset, limit)
res.body = helpers.dumpit_utf8(result)
res.status = falcon.HTTP_200
@ -203,6 +219,7 @@ class AlarmDefinitions(alarm_definitions_api_v2.AlarmDefinitionsV2API,
definitions = self._alarm_definitions_repo.get_alarm_definitions(tenant_id=tenant_id,
name=name,
dimensions=None,
sort_by=None,
offset=None,
limit=0)
if definitions:
@ -280,12 +297,12 @@ class AlarmDefinitions(alarm_definitions_api_v2.AlarmDefinitionsV2API,
alarm_metric_rows, sub_alarm_rows)
@resource.resource_try_catch_block
def _alarm_definition_list(self, tenant_id, name, dimensions, req_uri,
def _alarm_definition_list(self, tenant_id, name, dimensions, req_uri, sort_by,
offset, limit):
alarm_definition_rows = (
self._alarm_definitions_repo.get_alarm_definitions(tenant_id, name,
dimensions,
dimensions, sort_by,
offset, limit))
result = []

View File

@ -1,4 +1,4 @@
# Copyright 2014 Hewlett-Packard
# Copyright 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

View File

@ -23,6 +23,7 @@ from monasca_api.api import alarms_api_v2
from monasca_api.common.repositories import exceptions
from monasca_api.v2.common.exceptions import HTTPUnprocessableEntityError
from monasca_api.v2.common.schemas import alarm_update_schema as schema_alarm
from monasca_api.v2.common import validation
from monasca_api.v2.reference import alarming
from monasca_api.v2.reference import helpers
from monasca_api.v2.reference import resource
@ -116,12 +117,26 @@ class Alarms(alarms_api_v2.AlarmsV2API,
if alarm_id is None:
query_parms = falcon.uri.parse_query_string(req.query_string)
if 'sort_by' in query_parms:
if isinstance(query_parms['sort_by'], basestring):
query_parms['sort_by'] = [query_parms['sort_by']]
allowed_sort_by = {'alarm_id', 'alarm_definition_id', 'state', 'severity', 'lifecycle_state', 'link',
'state_updated_timestamp', 'updated_timestamp', 'created_timestamp'}
validation.validate_sort_by(query_parms['sort_by'], allowed_sort_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 and not isinstance(offset, int):
try:
offset = int(offset)
except Exception as ex:
LOG.exception(ex)
raise HTTPUnprocessableEntityError("Unprocessable Entity",
"Offset value {} must be an integer".format(offset))
limit = helpers.get_limit(req)

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
@ -326,6 +326,48 @@ class TestAlarmDefinitions(base.BaseMonascaTest):
links = response_body['links']
self._verify_list_alarm_definitions_links(links)
@test.attr(type='gate')
def test_list_alarm_definitions_sort_by(self):
alarm_definitions = []
alarm_definitions.append(helpers.create_alarm_definition(
name='alarm def sort by 01',
expression='test_metric_01 > 1',
severity='HIGH'
))
alarm_definitions.append(helpers.create_alarm_definition(
name='alarm def sort by 04',
expression='test_metric_04 > 1',
severity='LOW'
))
alarm_definitions.append(helpers.create_alarm_definition(
name='alarm def sort by 02',
expression='test_metric_02 > 1',
severity='CRITICAL'
))
alarm_definitions.append(helpers.create_alarm_definition(
name='alarm def sort by 03',
expression='test_metric_03 > 1',
severity='MEDIUM'
))
for definition in alarm_definitions:
self.monasca_client.create_alarm_definitions(definition)
resp, response_body = self.monasca_client.list_alarm_definitions('?sort_by=severity')
self.assertEqual(200, resp.status)
prev_severity = 'CRITICAL'
for alarm_definition in response_body['elements']:
assert prev_severity <= alarm_definition['severity'],\
"Severity {} came after {}".format(alarm_definition['severity'], prev_severity)
prev_severity = alarm_definition['severity']
@test.attr(type='gate')
@test.attr(type=['negative'])
def test_list_alarm_definitions_invalid_sort_by(self):
query_parms = '?sort_by=random'
self.assertRaises(exceptions.UnprocessableEntity,
self.monasca_client.list_alarm_definitions, query_parms)
@test.attr(type="gate")
def test_list_alarm_definitions_with_offset_limit(self):
helpers.delete_alarm_definitions(self.monasca_client)
@ -347,31 +389,15 @@ class TestAlarmDefinitions(base.BaseMonascaTest):
self.assertEqual(first_element, elements[0])
self.assertEqual(last_element, elements[1])
timeout = time.time() + 60 * 1 # 1 minute timeout
for limit in xrange(1, 3):
next_element = elements[limit - 1]
while True:
if time.time() < timeout:
query_parms = '?offset=' + str(next_element['id']) + \
'&limit=' + str(limit)
resp, response_body = self.monasca_client.\
list_alarm_definitions(query_parms)
self.assertEqual(200, resp.status)
new_elements = response_body['elements']
if len(new_elements) > limit - 1:
self.assertEqual(limit, len(new_elements))
next_element = new_elements[limit - 1]
elif 0 < len(new_elements) <= limit - 1:
self.assertEqual(last_element, new_elements[0])
break
else:
self.assertEqual(last_element, next_element)
break
else:
msg = "Failed " \
"test_list_alarm_definitions_with_offset_limit: " \
"one minute timeout"
raise exceptions.TimeoutException(msg)
for offset in xrange(0, 2):
for limit in xrange(1, 3 - offset):
query_parms = '?offset=' + str(offset) + '&limit=' + str(limit)
resp, response_body = self.monasca_client.list_alarm_definitions(query_parms)
self.assertEqual(200, resp.status)
new_elements = response_body['elements']
self.assertEqual(limit, len(new_elements))
self.assertEqual(elements[offset], new_elements[0])
self.assertEqual(elements[offset+limit-1], new_elements[-1])
# Get

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
@ -176,21 +176,18 @@ class TestAlarms(base.BaseMonascaTest):
@test.attr(type="gate")
def test_list_alarms_by_offset_limit(self):
helpers.delete_alarm_definitions(self.monasca_client)
self._create_alarms_for_test_alarms(num=2)
resp, response_body = self.monasca_client.list_alarms()
definition_ids, expected_metric = self._create_alarms_for_test_alarms(num=2)
resp, response_body = self.monasca_client.list_alarms('?metric_name=' + expected_metric['name'])
self._verify_list_alarms_elements(resp, response_body,
expect_num_elements=2)
elements = response_body['elements']
first_element = elements[0]
next_element = elements[1]
id_first_element = first_element['id']
query_parms = '?offset=' + str(id_first_element) + '&limit=1'
second_element = elements[1]
query_parms = '?metric_name=' + expected_metric['name'] + '&offset=1&limit=1'
resp, response_body1 = self.monasca_client.list_alarms(query_parms)
elements = response_body1['elements']
self.assertEqual(1, len(elements))
self.assertEqual(elements[0]['id'], next_element['id'])
self.assertEqual(elements[0], next_element)
self.assertEqual(elements[0]['id'], second_element['id'])
self.assertEqual(elements[0], second_element)
@test.attr(type="gate")
def test_get_alarm(self):
@ -207,6 +204,66 @@ class TestAlarms(base.BaseMonascaTest):
metric = element['metrics'][0]
self._verify_metric_in_alarm(metric, expected_metric)
@test.attr(type="gate")
def test_list_alarms_sort_by(self):
alarm_definition_ids, expected_metric = self._create_alarms_for_test_alarms(num=3)
resp, response_body = self.monasca_client.list_alarms('?metric_name=' + expected_metric['name'])
self._verify_list_alarms_elements(resp, response_body,
expect_num_elements=3)
resp, response_body = self.monasca_client.list_alarms('?metric_name=' + expected_metric['name'] +
'&sort_by=created_timestamp')
self._verify_list_alarms_elements(resp, response_body,
expect_num_elements=3)
elements = response_body['elements']
last_timestamp = elements[0]['created_timestamp']
for element in elements:
assert element['state'] >= last_timestamp,\
"Created_timestamps are not in sorted order {} came before {}".format(last_timestamp,
element['created_timestamp'])
last_timestamp = element['created_timestamp']
@test.attr(type='gate')
def test_list_alarms_sort_by_asc_desc(self):
alarm_definition_ids, expected_metric = self._create_alarms_for_test_alarms(num=3)
resp, response_body = self.monasca_client.list_alarms('?metric_name=' + expected_metric['name'])
self._verify_list_alarms_elements(resp, response_body,
expect_num_elements=3)
resp, response_body = self.monasca_client.list_alarms('?metric_name=' + expected_metric['name'] +
'&sort_by=created_timestamp')
self._verify_list_alarms_elements(resp, response_body,
expect_num_elements=3)
elements = response_body['elements']
last_timestamp = elements[0]['created_timestamp']
for element in elements:
assert element['state'] >= last_timestamp,\
"Created_timestamps are not in ascending order {} came before {}".format(last_timestamp,
element['created_timestamp'])
last_timestamp = element['created_timestamp']
resp, response_body = self.monasca_client.list_alarms('?metric_name=' + expected_metric['name'] +
'&sort_by=created_timestamp')
self._verify_list_alarms_elements(resp, response_body,
expect_num_elements=3)
elements = response_body['elements']
last_timestamp = elements[0]['created_timestamp']
for element in elements:
assert element['state'] >= last_timestamp,\
"Created_timestamps are not in descending order {} came before {}".format(last_timestamp,
element['created_timestamp'])
last_timestamp = element['created_timestamp']
@test.attr(type="gate")
def test_list_alarms_invalid_sort_by(self):
query_parms = '?sort_by=not_valid_field'
self.assertRaises(exceptions.UnprocessableEntity,
self.monasca_client.list_alarms, query_parms)
@test.attr(type="gate")
@test.attr(type=['negative'])
def test_get_alarm_with_invalid_id(self):