From d8556ae8602cc68bef15815d5d1440a6d3dfa391 Mon Sep 17 00:00:00 2001 From: Ryan Brandt Date: Mon, 8 Feb 2016 12:55:15 -0700 Subject: [PATCH] Allow filtering by multiple severities Specify OR with '|' ex. severity=LOW|MEDIUM Change-Id: Ib5fed2202107828efc526cc62c4516274b3d00b5 --- docs/monasca-api-spec.md | 3 + .../api/app/validation/Validation.java | 26 ++++- .../api/domain/model/alarm/AlarmRepo.java | 4 +- .../alarmdefinition/AlarmDefinitionRepo.java | 3 +- .../hibernate/AlarmDefinitionSqlRepoImpl.java | 4 +- .../hibernate/AlarmSqlRepoImpl.java | 6 +- .../mysql/AlarmDefinitionMySqlRepoImpl.java | 10 +- .../persistence/mysql/AlarmMySqlRepoImpl.java | 20 +--- .../persistence/mysql/MySQLUtils.java | 24 ++++ .../api/resource/AlarmDefinitionResource.java | 6 +- .../monasca/api/resource/AlarmResource.java | 10 +- ...larmDefinitionMySqlRepositoryImplTest.java | 31 +++++- .../mysql/AlarmMySqlRepositoryImplTest.java | 2 +- .../resource/AlarmDefinitionResourceTest.java | 11 +- .../mysql/alarm_definitions_repository.py | 7 +- .../repositories/mysql/alarms_repository.py | 10 +- .../sqla/alarm_definitions_repository.py | 8 +- .../repositories/sqla/alarms_repository.py | 15 ++- monasca_api/v2/common/validation.py | 12 ++ monasca_api/v2/reference/alarm_definitions.py | 4 +- monasca_api/v2/reference/alarms.py | 10 +- .../tests/api/test_alarm_definitions.py | 105 ++++++++++++++---- .../tests/api/test_alarms.py | 72 ++++++++++++ 23 files changed, 319 insertions(+), 84 deletions(-) diff --git a/docs/monasca-api-spec.md b/docs/monasca-api-spec.md index d2813509f..4115f9fd0 100644 --- a/docs/monasca-api-spec.md +++ b/docs/monasca-api-spec.md @@ -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. diff --git a/java/src/main/java/monasca/api/app/validation/Validation.java b/java/src/main/java/monasca/api/app/validation/Validation.java index 914c263ed..5dd12c8dc 100644 --- a/java/src/main/java/monasca/api/app/validation/Validation.java +++ b/java/src/main/java/monasca/api/app/validation/Validation.java @@ -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 parseAndValidateSeverity(String severityStr) { + List severityList = null; + if (severityStr != null && !severityStr.isEmpty()) { + severityList = new ArrayList<>(); + List 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 parseAndValidateSortBy(String sortBy, final List allowed_sort_by) { List sortByList = new ArrayList<>(); if (sortBy != null && !sortBy.isEmpty()) { diff --git a/java/src/main/java/monasca/api/domain/model/alarm/AlarmRepo.java b/java/src/main/java/monasca/api/domain/model/alarm/AlarmRepo.java index cc390cd7c..a96fef876 100644 --- a/java/src/main/java/monasca/api/domain/model/alarm/AlarmRepo.java +++ b/java/src/main/java/monasca/api/domain/model/alarm/AlarmRepo.java @@ -33,7 +33,7 @@ public interface AlarmRepo { * Returns alarms for the given criteria. */ List find(String tenantId, String alarmDefId, String metricName, Map metricDimensions, AlarmState state, AlarmSeverity severity, String lifecycleState, String link, DateTime stateUpdatedStart, + String> metricDimensions, AlarmState state, List severities, String lifecycleState, String link, DateTime stateUpdatedStart, List sort_by, String offset, int limit, boolean enforceLimit); /** @@ -64,7 +64,7 @@ public interface AlarmRepo { */ AlarmCount getAlarmsCount(String tenantId, String alarmDefId, String metricName, Map metricDimensions, AlarmState state, - AlarmSeverity severity, String lifecycleState, String link, + List severities, String lifecycleState, String link, DateTime stateUpdatedStart, List groupBy, String offset, int limit); } diff --git a/java/src/main/java/monasca/api/domain/model/alarmdefinition/AlarmDefinitionRepo.java b/java/src/main/java/monasca/api/domain/model/alarmdefinition/AlarmDefinitionRepo.java index a70443d77..07b17f733 100644 --- a/java/src/main/java/monasca/api/domain/model/alarmdefinition/AlarmDefinitionRepo.java +++ b/java/src/main/java/monasca/api/domain/model/alarmdefinition/AlarmDefinitionRepo.java @@ -49,7 +49,8 @@ public interface AlarmDefinitionRepo { * Returns alarms for the given criteria. */ List find(String tenantId, String name, Map dimensions, - AlarmSeverity severity, List sortBy, String offset, int limit); + List severities, List sortBy, String offset, + int limit); /** * @throws EntityNotFoundException if an alarm cannot be found for the {@code alarmDefId} diff --git a/java/src/main/java/monasca/api/infrastructure/persistence/hibernate/AlarmDefinitionSqlRepoImpl.java b/java/src/main/java/monasca/api/infrastructure/persistence/hibernate/AlarmDefinitionSqlRepoImpl.java index 9437966c8..e3ad7bd8d 100644 --- a/java/src/main/java/monasca/api/infrastructure/persistence/hibernate/AlarmDefinitionSqlRepoImpl.java +++ b/java/src/main/java/monasca/api/infrastructure/persistence/hibernate/AlarmDefinitionSqlRepoImpl.java @@ -228,14 +228,14 @@ public class AlarmDefinitionSqlRepoImpl @Override @SuppressWarnings("unchecked") public List find(String tenantId, String name, Map dimensions, - AlarmSeverity severity, List sortBy, + List severities, List 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"); } diff --git a/java/src/main/java/monasca/api/infrastructure/persistence/hibernate/AlarmSqlRepoImpl.java b/java/src/main/java/monasca/api/infrastructure/persistence/hibernate/AlarmSqlRepoImpl.java index 4d2943a7f..c27eaf6c1 100644 --- a/java/src/main/java/monasca/api/infrastructure/persistence/hibernate/AlarmSqlRepoImpl.java +++ b/java/src/main/java/monasca/api/infrastructure/persistence/hibernate/AlarmSqlRepoImpl.java @@ -160,7 +160,7 @@ public class AlarmSqlRepoImpl final String metricName, final Map metricDimensions, final AlarmState state, - final AlarmSeverity severity, + final List 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 metricDimensions, AlarmState state, - AlarmSeverity severity, String lifecycleState, String link, + List severities, String lifecycleState, String link, DateTime stateUpdatedStart, List groupBy, String offset, int limit) { // Not Implemented diff --git a/java/src/main/java/monasca/api/infrastructure/persistence/mysql/AlarmDefinitionMySqlRepoImpl.java b/java/src/main/java/monasca/api/infrastructure/persistence/mysql/AlarmDefinitionMySqlRepoImpl.java index e927629e4..0edf18c24 100644 --- a/java/src/main/java/monasca/api/infrastructure/persistence/mysql/AlarmDefinitionMySqlRepoImpl.java +++ b/java/src/main/java/monasca/api/infrastructure/persistence/mysql/AlarmDefinitionMySqlRepoImpl.java @@ -147,7 +147,7 @@ public class AlarmDefinitionMySqlRepoImpl implements AlarmDefinitionRepo { @SuppressWarnings("unchecked") @Override public List find(String tenantId, String name, - Map dimensions, AlarmSeverity severity, + Map dimensions, List severities, List 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); diff --git a/java/src/main/java/monasca/api/infrastructure/persistence/mysql/AlarmMySqlRepoImpl.java b/java/src/main/java/monasca/api/infrastructure/persistence/mysql/AlarmMySqlRepoImpl.java index 241913ca8..d80af4524 100644 --- a/java/src/main/java/monasca/api/infrastructure/persistence/mysql/AlarmMySqlRepoImpl.java +++ b/java/src/main/java/monasca/api/infrastructure/persistence/mysql/AlarmMySqlRepoImpl.java @@ -137,7 +137,7 @@ public class AlarmMySqlRepoImpl implements AlarmRepo { @Override public List find(String tenantId, String alarmDefId, String metricName, Map metricDimensions, AlarmState state, - AlarmSeverity severity, String lifecycleState, String link, + List severities, String lifecycleState, String link, DateTime stateUpdatedStart, List 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 metricDimensions, AlarmState state, - AlarmSeverity severity, String lifecycleState, String link, + List severities, String lifecycleState, String link, DateTime stateUpdatedStart, List 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); diff --git a/java/src/main/java/monasca/api/infrastructure/persistence/mysql/MySQLUtils.java b/java/src/main/java/monasca/api/infrastructure/persistence/mysql/MySQLUtils.java index 97e997b93..81772b696 100644 --- a/java/src/main/java/monasca/api/infrastructure/persistence/mysql/MySQLUtils.java +++ b/java/src/main/java/monasca/api/infrastructure/persistence/mysql/MySQLUtils.java @@ -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 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 severities) { + if (severities != null && !severities.isEmpty()) { + for (int i = 0; i < severities.size(); i++) { + query.bind("severity" + String.valueOf(i), severities.get(i).name()); + } + } + } + } diff --git a/java/src/main/java/monasca/api/resource/AlarmDefinitionResource.java b/java/src/main/java/monasca/api/resource/AlarmDefinitionResource.java index 57693d29a..47180ec3d 100644 --- a/java/src/main/java/monasca/api/resource/AlarmDefinitionResource.java +++ b/java/src/main/java/monasca/api/resource/AlarmDefinitionResource.java @@ -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 severityList = Validation.parseAndValidateSeverity(severityStr); + final int paging_limit = this.persistUtils.getLimit(limit); final List resources = repo.find(tenantId, name, dimensions, - severity, + severityList, sortByList, offset, paging_limit diff --git a/java/src/main/java/monasca/api/resource/AlarmResource.java b/java/src/main/java/monasca/api/resource/AlarmResource.java index 9996641b3..6208807e6 100644 --- a/java/src/main/java/monasca/api/resource/AlarmResource.java +++ b/java/src/main/java/monasca/api/resource/AlarmResource.java @@ -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 severityList = Validation.parseAndValidateSeverity(severity); final int paging_limit = this.persistUtils.getLimit(limit); final List 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 severityList = Validation.parseAndValidateSeverity(severity); List groupBy = (Strings.isNullOrEmpty(groupByStr)) ? null : parseAndValidateGroupBy( groupByStr); @@ -287,7 +289,7 @@ public class AlarmResource { metricName, metricDimensions, state, - severity, + severityList, lifecycleState, link, stateUpdatedStart, diff --git a/java/src/test/java/monasca/api/infrastructure/persistence/mysql/AlarmDefinitionMySqlRepositoryImplTest.java b/java/src/test/java/monasca/api/infrastructure/persistence/mysql/AlarmDefinitionMySqlRepositoryImplTest.java index b270012f8..a0fb19d4c 100644 --- a/java/src/test/java/monasca/api/infrastructure/persistence/mysql/AlarmDefinitionMySqlRepositoryImplTest.java +++ b/java/src/test/java/monasca/api/infrastructure/persistence/mysql/AlarmDefinitionMySqlRepositoryImplTest.java @@ -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 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.emptyList(), Collections.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.emptyList(), Collections.emptyList()); } public void shouldCreate() { @@ -267,7 +288,7 @@ public class AlarmDefinitionMySqlRepositoryImplTest { final Map 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() { diff --git a/java/src/test/java/monasca/api/infrastructure/persistence/mysql/AlarmMySqlRepositoryImplTest.java b/java/src/test/java/monasca/api/infrastructure/persistence/mysql/AlarmMySqlRepositoryImplTest.java index 75dcb86b2..7e151f698 100644 --- a/java/src/test/java/monasca/api/infrastructure/persistence/mysql/AlarmMySqlRepositoryImplTest.java +++ b/java/src/test/java/monasca/api/infrastructure/persistence/mysql/AlarmMySqlRepositoryImplTest.java @@ -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); } diff --git a/java/src/test/java/monasca/api/resource/AlarmDefinitionResourceTest.java b/java/src/test/java/monasca/api/resource/AlarmDefinitionResourceTest.java index 7be374e29..9174275f5 100644 --- a/java/src/test/java/monasca/api/resource/AlarmDefinitionResourceTest.java +++ b/java/src/test/java/monasca/api/resource/AlarmDefinitionResourceTest.java @@ -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) anyMap(), AlarmSeverity.fromString(anyString()), - (List) anyList(), anyString(), anyInt())).thenReturn( + when(repo.find(anyString(), anyString(), (Map) anyMap(), anyListOf( + AlarmSeverity.class), (List) 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) anyMap(), AlarmSeverity.fromString(anyString()), + verify(repo).find(eq("abc"), anyString(), (Map) anyMap(), anyListOf(AlarmSeverity.class), (List) anyList(), anyString(), anyInt()); } @@ -350,7 +351,7 @@ public class AlarmDefinitionResourceTest extends AbstractMonApiResourceTest { List alarms = Arrays.asList(ad); assertEquals(alarms, Arrays.asList(alarmItem)); - verify(repo).find(eq("abc"), eq("foo bar baz"), (Map) anyMap(), AlarmSeverity.fromString(anyString()), (List) anyList(), + verify(repo).find(eq("abc"), eq("foo bar baz"), (Map) anyMap(), anyListOf(AlarmSeverity.class), (List) anyList(), anyString(), anyInt()); } @@ -396,7 +397,7 @@ public class AlarmDefinitionResourceTest extends AbstractMonApiResourceTest { public void should500OnInternalException() { doThrow(new RuntimeException("")).when(repo).find(anyString(), anyString(), - (Map) anyObject(), AlarmSeverity.fromString(anyString()), (List) anyList(), anyString(), anyInt()); + (Map) anyObject(), anyListOf(AlarmSeverity.class), (List) anyList(), anyString(), anyInt()); try { client().resource("/v2.0/alarm-definitions").header("X-Tenant-Id", "abc").get(List.class); diff --git a/monasca_api/common/repositories/mysql/alarm_definitions_repository.py b/monasca_api/common/repositories/mysql/alarm_definitions_repository.py index b090ab7d3..1d8e9c469 100644 --- a/monasca_api/common/repositories/mysql/alarm_definitions_repository.py +++ b/monasca_api/common/repositories/mysql/alarm_definitions_repository.py @@ -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 diff --git a/monasca_api/common/repositories/mysql/alarms_repository.py b/monasca_api/common/repositories/mysql/alarms_repository.py index 2aeb7b068..fc7a01ada 100644 --- a/monasca_api/common/repositories/mysql/alarms_repository.py +++ b/monasca_api/common/repositories/mysql/alarms_repository.py @@ -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')) diff --git a/monasca_api/common/repositories/sqla/alarm_definitions_repository.py b/monasca_api/common/repositories/sqla/alarm_definitions_repository.py index 1f5757eee..1b3026552 100644 --- a/monasca_api/common/repositories/sqla/alarm_definitions_repository.py +++ b/monasca_api/common/repositories/sqla/alarm_definitions_repository.py @@ -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: diff --git a/monasca_api/common/repositories/sqla/alarms_repository.py b/monasca_api/common/repositories/sqla/alarms_repository.py index 3532c0b18..548da0e83 100644 --- a/monasca_api/common/repositories/sqla/alarms_repository.py +++ b/monasca_api/common/repositories/sqla/alarms_repository.py @@ -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') diff --git a/monasca_api/v2/common/validation.py b/monasca_api/v2/common/validation.py index 508022ba5..93ad81598 100644 --- a/monasca_api/v2/common/validation.py +++ b/monasca_api/v2/common/validation.py @@ -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)) diff --git a/monasca_api/v2/reference/alarm_definitions.py b/monasca_api/v2/reference/alarm_definitions.py index 5311fe5de..edc581ec4 100644 --- a/monasca_api/v2/reference/alarm_definitions.py +++ b/monasca_api/v2/reference/alarm_definitions.py @@ -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): diff --git a/monasca_api/v2/reference/alarms.py b/monasca_api/v2/reference/alarms.py index 763dcf346..f21cfd21e 100644 --- a/monasca_api/v2/reference/alarms.py +++ b/monasca_api/v2/reference/alarms.py @@ -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): diff --git a/monasca_tempest_tests/tests/api/test_alarm_definitions.py b/monasca_tempest_tests/tests/api/test_alarm_definitions.py index 40ab7839d..4d75a8b02 100644 --- a/monasca_tempest_tests/tests/api/test_alarm_definitions.py +++ b/monasca_tempest_tests/tests/api/test_alarm_definitions.py @@ -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): diff --git a/monasca_tempest_tests/tests/api/test_alarms.py b/monasca_tempest_tests/tests/api/test_alarms.py index 000f1a738..f7bfd8c8c 100644 --- a/monasca_tempest_tests/tests/api/test_alarms.py +++ b/monasca_tempest_tests/tests/api/test_alarms.py @@ -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 \