Merge "Allow filtering by multiple severities"

This commit is contained in:
Jenkins 2016-06-15 03:43:16 +00:00 committed by Gerrit Code Review
commit 537e062cef
23 changed files with 319 additions and 84 deletions

View File

@ -1905,6 +1905,7 @@ 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, ...`, leaving the value empty `key1,key2:value2` will return all values for that key, multiple values for a key may be specified as `key1:value1|value2|...,key2:value4,...`
* severity (string, optional) - One or more severities to filter by, separated with `|`, ex. `severity=LOW|MEDIUM`.
* 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'
@ -2412,6 +2413,7 @@ None.
* 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, ...`, leaving the value empty `key1,key2:value2` will return all values for that key, multiple values for a key may be specified as `key1:value1|value2|...,key2:value4,...`
* state (string, optional) - State of alarm to filter by, either `OK`, `ALARM` or `UNDETERMINED`.
* severity (string, optional) - One or more severities to filter by, separated with `|`, ex. `severity=LOW|MEDIUM`.
* 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.
@ -2525,6 +2527,7 @@ None
* 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`.
* severity (string, optional) - One or more severities to filter by, separated with `|`, ex. `severity=LOW|MEDIUM`.
* 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.

View File

@ -33,14 +33,17 @@ import java.util.Map;
import javax.ws.rs.WebApplicationException;
import monasca.api.domain.model.alarm.Alarm;
import monasca.api.resource.exception.Exceptions;
import monasca.common.model.alarm.AlarmSeverity;
/**
* Validation related utilities.
*/
public final class Validation {
private static final Splitter COMMA_SPLITTER = Splitter.on(',').omitEmptyStrings().trimResults();
private static final Splitter COLON_SPLITTER = Splitter.on(':').omitEmptyStrings().trimResults().limit(2);
private static final Splitter COLON_SPLITTER = Splitter.on(':').omitEmptyStrings().trimResults().limit(
2);
private static final Splitter SPACE_SPLITTER = Splitter.on(' ').omitEmptyStrings().trimResults();
private static final Splitter VERTICAL_BAR_SPLITTER = Splitter.on('|').omitEmptyStrings().trimResults();
private static final Joiner SPACE_JOINER = Joiner.on(' ');
@ -169,7 +172,8 @@ public final class Validation {
*/
public static void validateTimes(DateTime startTime, DateTime endTime) {
if (endTime != null && !startTime.isBefore(endTime))
throw Exceptions.badRequest("start_time (%s) must be before end_time (%s)", startTime, endTime);
throw Exceptions.badRequest("start_time (%s) must be before end_time (%s)", startTime,
endTime);
}
public static Boolean validateAndParseMergeMetricsFlag(String mergeMetricsFlag) {
@ -246,6 +250,24 @@ public final class Validation {
return !Strings.isNullOrEmpty(crossTenantId) && !crossTenantId.equals(tenantId);
}
public static List<AlarmSeverity> parseAndValidateSeverity(String severityStr) {
List<AlarmSeverity> severityList = null;
if (severityStr != null && !severityStr.isEmpty()) {
severityList = new ArrayList<>();
List<String> severities = Lists.newArrayList(VERTICAL_BAR_SPLITTER.split(severityStr));
for (String severity : severities) {
AlarmSeverity s = AlarmSeverity.fromString(severity);
if (s != null) {
severityList.add(s);
} else {
throw Exceptions.unprocessableEntity(String.format("Invalid severity %s",
severity));
}
}
}
return severityList;
}
public static List<String> parseAndValidateSortBy(String sortBy, final List<String> allowed_sort_by) {
List<String> sortByList = new ArrayList<>();
if (sortBy != null && !sortBy.isEmpty()) {

View File

@ -33,7 +33,7 @@ public interface AlarmRepo {
* Returns alarms for the given criteria.
*/
List<Alarm> find(String tenantId, String alarmDefId, String metricName, Map<String,
String> metricDimensions, AlarmState state, AlarmSeverity severity, String lifecycleState, String link, DateTime stateUpdatedStart,
String> metricDimensions, AlarmState state, List<AlarmSeverity> severities, String lifecycleState, String link, DateTime stateUpdatedStart,
List<String> sort_by, String offset, int limit, boolean enforceLimit);
/**
@ -64,7 +64,7 @@ public interface AlarmRepo {
*/
AlarmCount getAlarmsCount(String tenantId, String alarmDefId, String metricName,
Map<String, String> metricDimensions, AlarmState state,
AlarmSeverity severity, String lifecycleState, String link,
List<AlarmSeverity> severities, String lifecycleState, String link,
DateTime stateUpdatedStart, List<String> groupBy,
String offset, int limit);
}

View File

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

View File

@ -228,14 +228,14 @@ public class AlarmDefinitionSqlRepoImpl
@Override
@SuppressWarnings("unchecked")
public List<AlarmDefinition> find(String tenantId, String name, Map<String, String> dimensions,
AlarmSeverity severity, List<String> sortBy,
List<AlarmSeverity> severities, 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");
}
if (severity != null) {
if (severities != null && !severities.isEmpty()) {
throw Exceptions.unprocessableEntity(
"Severity is not implemented for the hibernate database type");
}

View File

@ -160,7 +160,7 @@ public class AlarmSqlRepoImpl
final String metricName,
final Map<String, String> metricDimensions,
final AlarmState state,
final AlarmSeverity severity,
final List<AlarmSeverity> severities,
final String lifecycleState,
final String link,
final DateTime stateUpdatedStart,
@ -174,7 +174,7 @@ public class AlarmSqlRepoImpl
throw Exceptions.unprocessableEntity(
"Sort_by is not implemented for the hibernate database type");
}
if (severity != null) {
if (severities != null && !severities.isEmpty()) {
throw Exceptions.unprocessableEntity(
"Severity filter is not implemented for the hibernate database type");
}
@ -586,7 +586,7 @@ public class AlarmSqlRepoImpl
@Override
public AlarmCount getAlarmsCount(String tenantId, String alarmDefId, String metricName,
Map<String, String> metricDimensions, AlarmState state,
AlarmSeverity severity, String lifecycleState, String link,
List<AlarmSeverity> severities, String lifecycleState, String link,
DateTime stateUpdatedStart, List<String> groupBy,
String offset, int limit) {
// Not Implemented

View File

@ -147,7 +147,7 @@ public class AlarmDefinitionMySqlRepoImpl implements AlarmDefinitionRepo {
@SuppressWarnings("unchecked")
@Override
public List<AlarmDefinition> find(String tenantId, String name,
Map<String, String> dimensions, AlarmSeverity severity,
Map<String, String> dimensions, List<AlarmSeverity> severities,
List<String> sortBy, String offset, int limit) {
@ -174,9 +174,7 @@ public class AlarmDefinitionMySqlRepoImpl implements AlarmDefinitionRepo {
sbWhere.append(" and ad.name = :name");
}
if (severity != null) {
sbWhere.append(" and ad.severity = :severity");
}
sbWhere.append(MySQLUtils.buildSeverityAndClause(severities));
String orderByPart = "";
if (sortBy != null && !sortBy.isEmpty()) {
@ -210,9 +208,7 @@ public class AlarmDefinitionMySqlRepoImpl implements AlarmDefinitionRepo {
q.bind("name", name);
}
if (severity != null) {
q.bind("severity", severity.name());
}
MySQLUtils.bindSeverityToQuery(q, severities);
if (limit > 0) {
q.bind("limit", limit + 1);

View File

@ -137,7 +137,7 @@ public class AlarmMySqlRepoImpl implements AlarmRepo {
@Override
public List<Alarm> find(String tenantId, String alarmDefId, String metricName,
Map<String, String> metricDimensions, AlarmState state,
AlarmSeverity severity, String lifecycleState, String link,
List<AlarmSeverity> severities, String lifecycleState, String link,
DateTime stateUpdatedStart, List<String> sortBy,
String offset, int limit, boolean enforceLimit) {
@ -184,9 +184,7 @@ public class AlarmMySqlRepoImpl implements AlarmRepo {
sbWhere.append(" and a.state = :state");
}
if (severity != null) {
sbWhere.append(" and ad.severity = :severity");
}
sbWhere.append(MySQLUtils.buildSeverityAndClause(severities));
if (lifecycleState != null) {
sbWhere.append(" and a.lifecycle_state = :lifecycleState");
@ -256,9 +254,7 @@ public class AlarmMySqlRepoImpl implements AlarmRepo {
q.bind("state", state.name());
}
if (severity != null) {
q.bind("severity", severity.name());
}
MySQLUtils.bindSeverityToQuery(q, severities);
if (lifecycleState != null) {
q.bind("lifecycleState", lifecycleState);
@ -466,7 +462,7 @@ public class AlarmMySqlRepoImpl implements AlarmRepo {
@Override
public AlarmCount getAlarmsCount(String tenantId, String alarmDefId, String metricName,
Map<String, String> metricDimensions, AlarmState state,
AlarmSeverity severity, String lifecycleState, String link,
List<AlarmSeverity> severities, String lifecycleState, String link,
DateTime stateUpdatedStart, List<String> groupBy,
String offset, int limit) {
final String SELECT_CLAUSE = "SELECT count(*) as count%1$s "
@ -548,9 +544,7 @@ public class AlarmMySqlRepoImpl implements AlarmRepo {
queryBuilder.append(" AND a.state = :state");
}
if (severity != null) {
queryBuilder.append(" AND ad.severity = :severity");
}
queryBuilder.append(MySQLUtils.buildSeverityAndClause(severities));
if (lifecycleState != null) {
queryBuilder.append(" AND a.lifecycle_state = :lifecycleState");
@ -602,9 +596,7 @@ public class AlarmMySqlRepoImpl implements AlarmRepo {
q.bind("state", state.name());
}
if (severity != null) {
q.bind("severity", severity.name());
}
MySQLUtils.bindSeverityToQuery(q, severities);
if (lifecycleState != null) {
q.bind("lifecycleState", lifecycleState);

View File

@ -28,6 +28,7 @@ import java.util.Map;
import javax.inject.Named;
import monasca.api.infrastructure.persistence.Utils;
import monasca.common.model.alarm.AlarmSeverity;
public class MySQLUtils
extends Utils {
@ -82,4 +83,27 @@ public class MySQLUtils
}
}
public static String buildSeverityAndClause(List<AlarmSeverity> severities) {
StringBuilder sbWhere = new StringBuilder();
if (severities != null && !severities.isEmpty()) {
sbWhere.append(" and (");
for (int i = 0; i < severities.size(); i++) {
sbWhere.append("ad.severity = :severity").append(i);
if (i < severities.size() - 1) {
sbWhere.append(" or ");
}
}
sbWhere.append(") ");
}
return sbWhere.toString();
}
public static void bindSeverityToQuery(Query query, List<AlarmSeverity> severities) {
if (severities != null && !severities.isEmpty()) {
for (int i = 0; i < severities.size(); i++) {
query.bind("severity" + String.valueOf(i), severities.get(i).name());
}
}
}
}

View File

@ -97,7 +97,7 @@ public class AlarmDefinitionResource {
public Object list(@Context UriInfo uriInfo,
@HeaderParam("X-Tenant-Id") String tenantId, @QueryParam("name") String name,
@QueryParam("dimensions") String dimensionsStr,
@QueryParam("severity") AlarmSeverity severity,
@QueryParam("severity") String severityStr,
@QueryParam("sort_by") String sortByStr,
@QueryParam("offset") String offset,
@QueryParam("limit") String limit) throws UnsupportedEncodingException {
@ -110,11 +110,13 @@ public class AlarmDefinitionResource {
Validation.parseAndValidateNumber(offset, "offset");
}
List<AlarmSeverity> severityList = Validation.parseAndValidateSeverity(severityStr);
final int paging_limit = this.persistUtils.getLimit(limit);
final List<AlarmDefinition> resources = repo.find(tenantId,
name,
dimensions,
severity,
severityList,
sortByList,
offset,
paging_limit

View File

@ -178,7 +178,7 @@ public class AlarmResource {
@QueryParam("metric_name") String metricName,
@QueryParam("metric_dimensions") String metricDimensionsStr,
@QueryParam("state") AlarmState state,
@QueryParam("severity") AlarmSeverity severity,
@QueryParam("severity") String severity,
@QueryParam("lifecycle_state") String lifecycleState,
@QueryParam("link") String link,
@QueryParam("state_updated_start_time") String stateUpdatedStartStr,
@ -199,10 +199,11 @@ public class AlarmResource {
if (!Strings.isNullOrEmpty(offset)) {
Validation.parseAndValidateNumber(offset, "offset");
}
List<AlarmSeverity> severityList = Validation.parseAndValidateSeverity(severity);
final int paging_limit = this.persistUtils.getLimit(limit);
final List<Alarm> alarms = repo.find(tenantId, alarmDefId, metricName, metricDimensions, state,
severity, lifecycleState, link, stateUpdatedStart, sortByList,
severityList, lifecycleState, link, stateUpdatedStart, sortByList,
offset, paging_limit, true);
for (final Alarm alarm : alarms) {
Links.hydrate(
@ -259,7 +260,7 @@ public class AlarmResource {
@QueryParam("metric_name") String metricName,
@QueryParam("metric_dimensions") String metricDimensionsStr,
@QueryParam("state") AlarmState state,
@QueryParam("severity") AlarmSeverity severity,
@QueryParam("severity") String severity,
@QueryParam("lifecycle_state") String lifecycleState,
@QueryParam("link") String link,
@QueryParam("state_updated_start_time") String stateUpdatedStartStr,
@ -274,6 +275,7 @@ public class AlarmResource {
DateTime stateUpdatedStart =
Validation.parseAndValidateDate(stateUpdatedStartStr,
"state_updated_start_time", false);
List<AlarmSeverity> severityList = Validation.parseAndValidateSeverity(severity);
List<String> groupBy = (Strings.isNullOrEmpty(groupByStr)) ? null : parseAndValidateGroupBy(
groupByStr);
@ -287,7 +289,7 @@ public class AlarmResource {
metricName,
metricDimensions,
state,
severity,
severityList,
lifecycleState,
link,
stateUpdatedStart,

View File

@ -36,6 +36,7 @@ import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.io.Resources;
import monasca.api.infrastructure.persistence.PersistUtils;
@ -56,6 +57,7 @@ public class AlarmDefinitionMySqlRepositoryImplTest {
private List<String> alarmActions;
private AlarmDefinition alarmDef_123;
private AlarmDefinition alarmDef_234;
private AlarmDefinition alarmDef_345;
@BeforeClass
protected void setupClass() throws Exception {
@ -112,6 +114,21 @@ public class AlarmDefinitionMySqlRepositoryImplTest {
handle.execute("insert into alarm_action values ('234', 'ALARM', '29387234')");
handle.execute("insert into alarm_action values ('234', 'ALARM', '77778687')");
handle
.execute("insert into alarm_definition (id, tenant_id, name, severity, expression, match_by, actions_enabled, created_at, updated_at, deleted_at) "
+ "values ('345', 'bob', 'Testing Critical', 'CRITICAL', 'avg(test_metric{flavor_id=777, image_id=888, metric_name=mem}) > 20 and avg(test_metric) < 100', 'flavor_id,image_id', 1, NOW(), NOW(), NULL)");
handle
.execute("insert into sub_alarm_definition (id, alarm_definition_id, function, metric_name, operator, threshold, period, periods, created_at, updated_at) "
+ "values ('333', '345', 'avg', 'test_metric', 'GT', 20, 60, 1, NOW(), NOW())");
handle
.execute("insert into sub_alarm_definition (id, alarm_definition_id, function, metric_name, operator, threshold, period, periods, created_at, updated_at) "
+ "values ('334', '345', 'avg', 'test_metric', 'LT', 100, 60, 1, NOW(), NOW())");
handle.execute("insert into sub_alarm_definition_dimension values ('333', 'flavor_id', '777')");
handle.execute("insert into sub_alarm_definition_dimension values ('333', 'image_id', '888')");
handle.execute("insert into sub_alarm_definition_dimension values ('333', 'metric_name', 'mem')");
handle.execute("insert into alarm_action values ('345', 'ALARM', '29387234')");
handle.execute("insert into alarm_action values ('345', 'ALARM', '77778687')");
alarmDef_123 = new AlarmDefinition("123", "90% CPU", null, "LOW",
"avg(hpcs.compute{flavor_id=777, image_id=888, metric_name=cpu, device=1}) > 10",
Arrays.asList("flavor_id", "image_id"), true, Arrays.asList("29387234", "77778687"),
@ -120,6 +137,10 @@ public class AlarmDefinitionMySqlRepositoryImplTest {
"avg(hpcs.compute{flavor_id=777, image_id=888, metric_name=mem}) > 20 and avg(hpcs.compute) < 100",
Arrays.asList("flavor_id", "image_id"), true, Arrays.asList("29387234", "77778687"),
Collections.<String>emptyList(), Collections.<String>emptyList());
alarmDef_345 = new AlarmDefinition("345","Testing Critical", null, "CRITICAL",
"avg(test_metric{flavor_id=777, image_id=888, metric_name=mem}) > 20 and avg(test_metric) < 100",
Arrays.asList("flavor_id", "image_id"), true, Arrays.asList("29387234", "77778687"),
Collections.<String>emptyList(), Collections.<String>emptyList());
}
public void shouldCreate() {
@ -267,7 +288,7 @@ public class AlarmDefinitionMySqlRepositoryImplTest {
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, null, null, 1));
repo.find("bob", null, dimensions, null, null, null, 1));
dimensions.clear();
dimensions.put("device", "1");
@ -285,9 +306,13 @@ public class AlarmDefinitionMySqlRepositoryImplTest {
}
public void shouldFindBySeverity() {
assertEquals(Arrays.asList(alarmDef_234), repo.find("bob", null, null, AlarmSeverity.HIGH, null, null, 1));
assertEquals(Arrays.asList(alarmDef_234), repo.find("bob", null, null, Lists.newArrayList(AlarmSeverity.HIGH), null, null, 1));
assertEquals(0, repo.find("bob", null, null, AlarmSeverity.CRITICAL, null, null, 1).size());
assertEquals(0, repo.find("bob", null, null, Lists.newArrayList(AlarmSeverity.CRITICAL), null, null, 1).size());
assertEquals(Arrays.asList(alarmDef_234, alarmDef_345),
repo.find("bob", null, null, Lists.newArrayList(AlarmSeverity.HIGH, AlarmSeverity.CRITICAL),
null, null, 2));
}
public void shouldDeleteById() {

View File

@ -331,7 +331,7 @@ public class AlarmMySqlRepositoryImplTest {
checkList(repo.find(TENANT_ID, null, null, null, null, null, null, null, null, Arrays.asList("state desc","severity"), null, 1, false),
compoundAlarm, alarm3, alarm2, alarm1);
checkList(repo.find(TENANT_ID, null, null, null, null, AlarmSeverity.HIGH, null, null, null, null, null, 1, false),
checkList(repo.find(TENANT_ID, null, null, null, null, Arrays.asList(AlarmSeverity.HIGH), null, null, null, null, null, 1, false),
compoundAlarm);
}

View File

@ -17,6 +17,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.anyListOf;
import static org.mockito.Matchers.anyMap;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.anyString;
@ -101,8 +102,8 @@ public class AlarmDefinitionResourceTest extends AbstractMonApiResourceTest {
repo = mock(AlarmDefinitionRepo.class);
when(repo.findById(eq("abc"), eq("123"))).thenReturn(alarm);
when(repo.findById(eq("abc"), eq("456"))).thenReturn(detAlarm);
when(repo.find(anyString(), anyString(), (Map<String, String>) anyMap(), AlarmSeverity.fromString(anyString()),
(List<String>) anyList(), anyString(), anyInt())).thenReturn(
when(repo.find(anyString(), anyString(), (Map<String, String>) anyMap(), anyListOf(
AlarmSeverity.class), (List<String>) anyList(), anyString(), anyInt())).thenReturn(
Arrays.asList(alarmItem));
addResources(new AlarmDefinitionResource(service, repo, new PersistUtils()));
@ -317,7 +318,7 @@ public class AlarmDefinitionResourceTest extends AbstractMonApiResourceTest {
assertEquals(alarms, Arrays.asList(alarmItem));
verify(repo).find(eq("abc"), anyString(), (Map<String, String>) anyMap(), AlarmSeverity.fromString(anyString()),
verify(repo).find(eq("abc"), anyString(), (Map<String, String>) anyMap(), anyListOf(AlarmSeverity.class),
(List<String>) anyList(),
anyString(), anyInt());
}
@ -350,7 +351,7 @@ 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(), AlarmSeverity.fromString(anyString()), (List<String>) anyList(),
verify(repo).find(eq("abc"), eq("foo bar baz"), (Map<String, String>) anyMap(), anyListOf(AlarmSeverity.class), (List<String>) anyList(),
anyString(), anyInt());
}
@ -396,7 +397,7 @@ public class AlarmDefinitionResourceTest extends AbstractMonApiResourceTest {
public void should500OnInternalException() {
doThrow(new RuntimeException("")).when(repo).find(anyString(), anyString(),
(Map<String, String>) anyObject(), AlarmSeverity.fromString(anyString()), (List<String>) anyList(), anyString(), anyInt());
(Map<String, String>) anyObject(), anyListOf(AlarmSeverity.class), (List<String>) anyList(), anyString(), anyInt());
try {
client().resource("/v2.0/alarm-definitions").header("X-Tenant-Id", "abc").get(List.class);

View File

@ -90,8 +90,9 @@ class AlarmDefinitionsRepository(mysql_repository.MySQLRepository,
parms.append(name.encode('utf8'))
if severity:
parms.append(severity.encode('utf8'))
where_clause += " and ad.severity = %s "
severities = severity.split('|')
parms.extend([s.encode('utf8') for s in severities])
where_clause += " and (" + " or ".join(["ad.severity = %s" for s in severities]) + ")"
if sort_by is not None:
order_by_clause = " order by ad." + ",ad.".join(sort_by)
@ -131,6 +132,8 @@ class AlarmDefinitionsRepository(mysql_repository.MySQLRepository,
query = select_clause + where_clause + order_by_clause + limit_offset_clause
LOG.debug("Query: {}".format(query))
return self._execute_query(query, parms)
@mysql_repository.mysql_try_catch_block

View File

@ -269,8 +269,9 @@ class AlarmsRepository(mysql_repository.MySQLRepository,
sub_query += " and a.state = %s "
if 'severity' in query_parms:
parms.append(query_parms['severity'].encode('utf8'))
sub_query += " and ad.severity = %s"
severities = query_parms['severity'].split('|')
parms.extend([s.encode('utf8') for s in severities])
sub_query += " and (" + " or ".join(["ad.severity = %s" for s in severities]) + ")"
if 'lifecycle_state' in query_parms:
parms.append(query_parms['lifecycle_state'].encode('utf8'))
@ -428,8 +429,9 @@ class AlarmsRepository(mysql_repository.MySQLRepository,
where_clause += " and a.state = %s "
if 'severity' in query_parms:
parms.append(query_parms['severity'].encode('utf8'))
where_clause += " and ad.severity = %s "
severities = query_parms['severity'].split('|')
parms.extend([s.encode('utf8') for s in severities])
where_clause += " and (" + " or ".join(["ad.severity = %s" for s in severities]) + ")"
if 'lifecycle_state' in query_parms:
parms.append(query_parms['lifecycle_state'].encode('utf8'))

View File

@ -25,6 +25,7 @@ from monasca_api.common.repositories.sqla import models
from monasca_api.common.repositories.sqla import sql_repository
from sqlalchemy import MetaData, update, delete, insert
from sqlalchemy import select, text, bindparam, null, literal_column
from sqlalchemy import or_
LOG = log.getLogger(__name__)
@ -317,8 +318,11 @@ class AlarmDefinitionsRepository(sql_repository.SQLRepository,
parms['b_name'] = name.encode('utf8')
if severity:
query = query.where(ad.c.severity == bindparam('b_severity'))
parms['b_severity'] = severity.encode('utf8')
severities = severity.split('|')
query = query.where(
or_(ad.c.severity == bindparam('b_severity' + str(i)) for i in xrange(len(severities))))
for i, s in enumerate(severities):
parms['b_severity' + str(i)] = s.encode('utf8')
order_columns = []
if sort_by is not None:

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Copyright 2014 Hewlett-Packard
# Copyright 2016 FUJITSU LIMITED
# (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
@ -285,8 +286,11 @@ class AlarmsRepository(sql_repository.SQLRepository,
parms['b_md_name'] = query_parms['metric_name'].encode('utf8')
if 'severity' in query_parms:
query = query.where(ad.c.severity == bindparam('b_severity'))
parms['b_severity'] = query_parms['severity'].encode('utf8')
severities = query_parms['severity'].split('|')
query = query.where(
or_(ad.c.severity == bindparam('b_severity' + str(i)) for i in xrange(len(severities))))
for i, s in enumerate(severities):
parms['b_severity' + str(i)] = s.encode('utf8')
if 'state' in query_parms:
query = query.where(a.c.state == bindparam('b_state'))
@ -493,8 +497,11 @@ class AlarmsRepository(sql_repository.SQLRepository,
query = query.where(a.c.state == bindparam('b_state'))
if 'severity' in query_parms:
query = query.where(ad.c.severity == bindparam('b_severity'))
parms['b_severity'] = query_parms['severity'].encode('utf8')
severities = query_parms['severity'].split('|')
query = query.where(
or_(ad.c.severity == bindparam('b_severity' + str(i)) for i in xrange(len(severities))))
for i, s in enumerate(severities):
parms['b_severity' + str(i)] = s.encode('utf8')
if 'lifecycle_state' in query_parms:
parms['b_lifecycle_state'] = query_parms['lifecycle_state'].encode('utf8')

View File

@ -67,6 +67,12 @@ def validate_alarm_definition_severity(severity):
VALID_ALARM_DEFINITION_SEVERITIES))
def validate_severity_query(severity_str):
severities = severity_str.split('|')
for severity in severities:
validate_alarm_definition_severity(severity)
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()
@ -105,3 +111,9 @@ def validate_value_meta(value_meta):
# value
assert isinstance(value_meta[name], (str, unicode)), "ValueMeta value must be a string"
assert len(value_meta[name]) >= 1, "ValueMeta value cannot be empty"
def validate_state_query(state_str):
if state_str not in VALID_ALARM_STATES:
raise HTTPUnprocessableEntityError("Unprocessable Entity",
"state {} must be one of 'ALARM','OK','UNDETERMINED'".format(state_str))

View File

@ -85,7 +85,9 @@ 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)
severity = helpers.get_query_param(req, "severity")
severity = helpers.get_query_param(req, "severity", default_val=None)
if severity is not None:
validation.validate_severity_query(severity)
sort_by = helpers.get_query_param(req, 'sort_by', default_val=None)
if sort_by is not None:
if isinstance(sort_by, basestring):

View File

@ -121,7 +121,7 @@ class Alarms(alarms_api_v2.AlarmsV2API,
validation.validate_alarm_state(query_parms['state'])
if 'severity' in query_parms:
validation.validate_alarm_definition_severity(query_parms['severity'])
validation.validate_severity_query(query_parms['severity'])
if 'sort_by' in query_parms:
if isinstance(query_parms['sort_by'], basestring):
@ -132,6 +132,12 @@ class Alarms(alarms_api_v2.AlarmsV2API,
'state_updated_timestamp', 'updated_timestamp', 'created_timestamp'}
validation.validate_sort_by(query_parms['sort_by'], allowed_sort_by)
if 'state' in query_parms:
validation.validate_alarm_state(query_parms['state'])
if 'severity' in query_parms:
validation.validate_severity_query(query_parms['severity'])
# 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(',')
@ -396,7 +402,7 @@ class AlarmsCount(alarms_api_v2.AlarmsCountV2API, alarming.Alarming):
validation.validate_alarm_state(query_parms['state'])
if 'severity' in query_parms:
validation.validate_alarm_definition_severity(query_parms['severity'])
validation.validate_severity_query(query_parms['severity'])
if 'group_by' in query_parms:
if not isinstance(query_parms['group_by'], list):

View File

@ -340,8 +340,7 @@ class TestAlarmDefinitions(base.BaseMonascaTest):
# Test list alarm definition response body
elements = response_body['elements']
self._verify_list_get_alarm_definitions_elements(elements, 1,
response_body_list[0])
self._verify_alarm_definitions_list(elements, response_body_list)
links = response_body['links']
self._verify_list_alarm_definitions_links(links)
@ -361,8 +360,8 @@ class TestAlarmDefinitions(base.BaseMonascaTest):
query_parms)
self._verify_list_alarm_definitions_response_body(resp, response_body)
elements = response_body['elements']
self._verify_list_get_alarm_definitions_elements(
elements, 1, res_body_create_alarm_def)
self._verify_alarm_definitions_list(
elements, [res_body_create_alarm_def])
links = response_body['links']
self._verify_list_alarm_definitions_links(links)
@ -386,8 +385,7 @@ class TestAlarmDefinitions(base.BaseMonascaTest):
list_alarm_definitions(query_parms)
self._verify_list_alarm_definitions_response_body(resp, response_body)
elements = response_body['elements']
self._verify_list_get_alarm_definitions_elements(
elements, 1, res_body_create_alarm_def)
self._verify_alarm_definitions_list(elements, [res_body_create_alarm_def])
links = response_body['links']
self._verify_list_alarm_definitions_links(links)
@ -404,7 +402,7 @@ class TestAlarmDefinitions(base.BaseMonascaTest):
name=name,
description="description",
expression=expression)
resp, res_body_create_alarm_def = self.monasca_client.\
resp, res_body_create_alarm_def = self.monasca_client. \
create_alarm_definitions(alarm_definition)
self.assertEqual(201, resp.status)
@ -415,8 +413,7 @@ class TestAlarmDefinitions(base.BaseMonascaTest):
self._verify_list_alarm_definitions_response_body(resp, response_body)
elements = response_body['elements']
self._verify_list_get_alarm_definitions_elements(
elements, 1, res_body_create_alarm_def)
self._verify_alarm_definitions_list(elements, [res_body_create_alarm_def])
links = response_body['links']
self._verify_list_alarm_definitions_links(links)
@ -450,11 +447,78 @@ class TestAlarmDefinitions(base.BaseMonascaTest):
list_alarm_definitions(query_param)
self._verify_list_alarm_definitions_response_body(resp, response_body)
elements = response_body['elements']
self._verify_list_get_alarm_definitions_elements(
elements, 1, res_body_create_alarm_def)
self._verify_alarm_definitions_list(elements, [res_body_create_alarm_def])
links = response_body['links']
self._verify_list_alarm_definitions_links(links)
@test.attr(type="gate")
@test.attr(type=['negative'])
def test_list_alarm_definitions_by_severity_invalid_severity(self):
query_parms = '?severity=false_severity'
self.assertRaises(exceptions.UnprocessableEntity,
self.monasca_client.list_alarm_definitions, query_parms)
@test.attr(type="gate")
def test_list_alarm_definitions_with_multiple_severity(self):
name = data_utils.rand_name('alarm_definition')
expression = 'avg(cpu_utilization{alarm=severity}) >= 1000'
alarm_definition = helpers.create_alarm_definition(
name=name,
description="description",
expression=expression,
severity="LOW")
resp, res_body_create_alarm_def_low = self.monasca_client.\
create_alarm_definitions(alarm_definition)
self.assertEqual(201, resp.status)
name = data_utils.rand_name('alarm_definition')
expression = 'avg(cpu_utilization{alarm=severity}) >= 1000'
alarm_definition = helpers.create_alarm_definition(
name=name,
description="description",
expression=expression,
severity="MEDIUM")
resp, res_body_create_alarm_def_medium = self.monasca_client.\
create_alarm_definitions(alarm_definition)
self.assertEqual(201, resp.status)
name = data_utils.rand_name('alarm_definition')
expression = 'avg(cpu_utilization{alarm=severity}) >= 1000'
alarm_definition = helpers.create_alarm_definition(
name=name,
description="description",
expression=expression,
severity="HIGH")
resp, res_body_create_alarm_def = self.monasca_client.\
create_alarm_definitions(alarm_definition)
self.assertEqual(201, resp.status)
query_param = '?severity=MEDIUM|LOW&dimensions=alarm:severity&sort_by=severity'
resp, response_body = self.monasca_client.\
list_alarm_definitions(query_param)
self._verify_list_alarm_definitions_response_body(resp, response_body)
elements = response_body['elements']
self._verify_alarm_definitions_list(elements, [res_body_create_alarm_def_low,
res_body_create_alarm_def_medium])
links = response_body['links']
self._verify_list_alarm_definitions_links(links)
@test.attr(type="gate")
@test.attr(type=['negative'])
def test_list_alarm_definitions_by_severity_multiple_values_invalid_severity(self):
query_parms = '?severity=false_severity|MEDIUM'
self.assertRaises(exceptions.UnprocessableEntity,
self.monasca_client.list_alarm_definitions, query_parms)
query_parms = '?severity=MEDIUM|false_severity'
self.assertRaises(exceptions.UnprocessableEntity,
self.monasca_client.list_alarm_definitions, query_parms)
query_parms = '?severity=LOW|false_severity|HIGH'
self.assertRaises(exceptions.UnprocessableEntity,
self.monasca_client.list_alarm_definitions, query_parms)
@test.attr(type='gate')
def test_list_alarm_definitions_sort_by(self):
key = data_utils.rand_name('key')
@ -629,8 +693,8 @@ class TestAlarmDefinitions(base.BaseMonascaTest):
response_body_list[0]['id'])
self.assertEqual(200, resp.status)
self._verify_element_set(response_body)
self._verify_list_get_alarm_definitions_elements(response_body, 0,
response_body_list[0])
self._verify_alarm_definitions_element(response_body,
response_body_list[0])
links = response_body['links']
self._verify_list_alarm_definitions_links(links)
@ -853,16 +917,11 @@ class TestAlarmDefinitions(base.BaseMonascaTest):
self.assertIsInstance(response_body, dict)
self.assertTrue(set(['links', 'elements']) == set(response_body))
def _verify_list_get_alarm_definitions_elements(self, elements, num,
res_body_create_alarm_def):
if num > 0:
self.assertEqual(len(elements), num)
for element in elements:
self._verify_alarm_definitions_element(
element, res_body_create_alarm_def)
else:
self._verify_alarm_definitions_element(elements,
res_body_create_alarm_def)
def _verify_alarm_definitions_list(self, observed, reference):
self.assertEqual(len(reference), len(observed))
for i in xrange(len(reference)):
self._verify_alarm_definitions_element(
reference[i], observed[i])
def _verify_alarm_definitions_element(self, response_body,
res_body_create_alarm_def):

View File

@ -310,6 +310,78 @@ class TestAlarms(base.BaseMonascaTest):
for alarm in response_body['elements']:
self.assertEqual('CRITICAL', alarm['alarm_definition']['severity'])
@test.attr(type="gate")
@test.attr(type=['negative'])
def test_list_alarms_by_severity_invalid_severity(self):
query_parms = '?severity=false_severity'
self.assertRaises(exceptions.UnprocessableEntity, self.monasca_client.list_alarms,
query_parms)
@test.attr(type="gate")
def test_list_alarms_by_severity_multiple_values(self):
metric_name = data_utils.rand_name("severity-metric")
alarm_defs = []
alarm_defs.append(helpers.create_alarm_definition(
name=data_utils.rand_name("alarm-severity"),
expression=metric_name + " > 12",
severity='LOW'
))
alarm_defs.append(helpers.create_alarm_definition(
name=data_utils.rand_name("alarm-severity"),
expression=metric_name + " > 12",
severity='MEDIUM'
))
alarm_defs.append(helpers.create_alarm_definition(
name=data_utils.rand_name("alarm-severity"),
expression=metric_name + " > 12",
severity='HIGH'
))
alarm_defs.append(helpers.create_alarm_definition(
name=data_utils.rand_name("alarm-severity"),
expression=metric_name + " > 12",
severity='CRITICAL'
))
alarm_def_ids = []
for definition in alarm_defs:
resp, response_body = self.monasca_client.create_alarm_definitions(definition)
self.assertEqual(201, resp.status)
alarm_def_ids.append(response_body['id'])
metric = helpers.create_metric(name=metric_name,
value=14)
resp, response_body = self.monasca_client.create_metrics(metric)
self.assertEqual(204, resp.status)
for def_id in alarm_def_ids:
self._wait_for_alarms(1, def_id)
query_parms = '?severity=LOW|MEDIUM'
resp, response_body = self.monasca_client.list_alarms(query_parms)
self.assertEqual(200, resp.status)
for alarm in response_body['elements']:
self.assertIn(alarm['alarm_definition']['severity'], ['LOW', 'MEDIUM'])
query_parms = '?severity=HIGH|CRITICAL'
resp, response_body = self.monasca_client.list_alarms(query_parms)
self.assertEqual(200, resp.status)
for alarm in response_body['elements']:
self.assertIn(alarm['alarm_definition']['severity'], ['HIGH', 'CRITICAL'])
@test.attr(type="gate")
@test.attr(type=['negative'])
def test_list_alarms_by_severity_multiple_values_invalid_severity(self):
query_parms = '?severity=false_severity|MEDIUM'
self.assertRaises(exceptions.UnprocessableEntity, self.monasca_client.list_alarms,
query_parms)
query_parms = '?severity=MEDIUM|false_severity'
self.assertRaises(exceptions.UnprocessableEntity, self.monasca_client.list_alarms,
query_parms)
query_parms = '?severity=LOW|false_severity|HIGH'
self.assertRaises(exceptions.UnprocessableEntity, self.monasca_client.list_alarms,
query_parms)
@test.attr(type="gate")
def test_list_alarms_by_lifecycle_state(self):
alarm_definition_ids, expected_metric \