Merge "Fix dimension validation of alarms"
This commit is contained in:
commit
37e275c4bd
|
@ -108,7 +108,7 @@ public final class DimensionValidation {
|
|||
|
||||
/**
|
||||
* Validates that the given {@code dimensions} are valid.
|
||||
*
|
||||
*
|
||||
* @throws WebApplicationException if validation fails
|
||||
*/
|
||||
public static void validate(Map<String, String> dimensions) {
|
||||
|
@ -118,27 +118,8 @@ public final class DimensionValidation {
|
|||
String value = dimension.getValue();
|
||||
|
||||
// General validations
|
||||
if (Strings.isNullOrEmpty(name))
|
||||
throw Exceptions.unprocessableEntity("Dimension name cannot be empty");
|
||||
if (Strings.isNullOrEmpty(value))
|
||||
throw Exceptions.unprocessableEntity("Dimension %s cannot have an empty value", name);
|
||||
if (name.length() > 255)
|
||||
throw Exceptions.unprocessableEntity("Dimension name %s must be 255 characters or less",
|
||||
name);
|
||||
if (value.length() > 255)
|
||||
throw Exceptions.unprocessableEntity("Dimension value %s must be 255 characters or less",
|
||||
value);
|
||||
// Dimension names that start with underscores are reserved for internal use only.
|
||||
if (name.startsWith("_")) {
|
||||
throw Exceptions.unprocessableEntity("Dimension name cannot start with underscore (_)",
|
||||
name);
|
||||
}
|
||||
if (!VALID_DIMENSION_NAME.matcher(name).matches())
|
||||
throw Exceptions.unprocessableEntity(
|
||||
"Dimension name %s may not contain: %s", name, INVALID_CHAR_STRING);
|
||||
if (!VALID_DIMENSION_NAME.matcher(value).matches())
|
||||
throw Exceptions.unprocessableEntity(
|
||||
"Dimension value %s may not contain: %s", value, INVALID_CHAR_STRING);
|
||||
validateDimensionName(name);
|
||||
validateDimensionValue(value, name, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -146,26 +127,77 @@ public final class DimensionValidation {
|
|||
* Validates a list of dimension names
|
||||
* @param names
|
||||
*/
|
||||
|
||||
public static void validateNames(List<String> names) {
|
||||
if(names != null) {
|
||||
if (names != null) {
|
||||
for (String name : names) {
|
||||
if (Strings.isNullOrEmpty(name)) {
|
||||
throw Exceptions.unprocessableEntity("Dimension name cannot be empty");
|
||||
}
|
||||
if (name.length() > 255) {
|
||||
throw Exceptions.unprocessableEntity("Dimension name '%s' must be 255 characters or less",
|
||||
name);
|
||||
}
|
||||
// Dimension names that start with underscores are reserved for internal use only.
|
||||
if (name.startsWith("_")) {
|
||||
throw Exceptions.unprocessableEntity("Dimension name '%s' cannot start with underscore (_)",
|
||||
name);
|
||||
}
|
||||
if (!VALID_DIMENSION_NAME.matcher(name).matches())
|
||||
throw Exceptions.unprocessableEntity(
|
||||
"Dimension name '%s' may not contain: %s", name, INVALID_CHAR_STRING);
|
||||
validateDimensionName(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a dimension name
|
||||
* @param name Dimension name
|
||||
*/
|
||||
public static void validateName(String name) {
|
||||
validateDimensionName(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a dimension value
|
||||
* @param value Dimension value
|
||||
* @param name Dimension name of the value
|
||||
*/
|
||||
public static void validateValue(String value, String name) {
|
||||
validateDimensionValue(value, name, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a dimension name
|
||||
* @param name Dimension name
|
||||
*/
|
||||
public static void validateDimensionName(String name) {
|
||||
if (Strings.isNullOrEmpty(name)) {
|
||||
throw Exceptions.unprocessableEntity("Dimension name cannot be empty");
|
||||
}
|
||||
if (name.length() > 255) {
|
||||
throw Exceptions.unprocessableEntity("Dimension name '%s' must be 255 characters or less",
|
||||
name);
|
||||
}
|
||||
// Dimension name that start with underscores are reserved for internal use only.
|
||||
if (name.startsWith("_")) {
|
||||
throw Exceptions.unprocessableEntity("Dimension name '%s' cannot start with underscore (_)",
|
||||
name);
|
||||
}
|
||||
|
||||
if (!VALID_DIMENSION_NAME.matcher(name).matches()) {
|
||||
throw Exceptions.unprocessableEntity(
|
||||
"Dimension name '%s' may not contain: %s", name, INVALID_CHAR_STRING);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a dimension value
|
||||
* @param value Dimension value
|
||||
* @param name Dimension name of the value
|
||||
* @param nullValueOk whether or not a null value is valid
|
||||
*/
|
||||
public static void validateDimensionValue(String value, String name, boolean nullValueOk) {
|
||||
if (value == null && nullValueOk) {
|
||||
return;
|
||||
}
|
||||
if (Strings.isNullOrEmpty(value)) {
|
||||
throw Exceptions.unprocessableEntity("Dimension '%s' cannot have an empty value", name);
|
||||
}
|
||||
if (value.length() > 255) {
|
||||
throw Exceptions.unprocessableEntity("Dimension '%s' value '%s' must be 255 characters or less",
|
||||
name, value);
|
||||
}
|
||||
|
||||
if (!VALID_DIMENSION_NAME.matcher(value).matches()) {
|
||||
throw Exceptions.unprocessableEntity(
|
||||
"Dimension '%s' value '%s' may not contain: %s", name, value,
|
||||
INVALID_CHAR_STRING);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ 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 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(' ');
|
||||
private static final DateTimeFormatter ISO_8601_FORMATTER = ISODateTimeFormat
|
||||
.dateOptionalTimeParser().withZoneUTC();
|
||||
|
@ -95,10 +96,21 @@ public final class Validation {
|
|||
Map<String, String> dimensions = new HashMap<String, String>();
|
||||
for (String dimensionStr : COMMA_SPLITTER.split(dimensionsStr)) {
|
||||
String[] dimensionArr = Iterables.toArray(COLON_SPLITTER.split(dimensionStr), String.class);
|
||||
if (dimensionArr.length == 2)
|
||||
dimensions.put(dimensionArr[0], dimensionArr[1]);
|
||||
if (dimensionArr.length == 1)
|
||||
if (dimensionArr.length == 1) {
|
||||
DimensionValidation.validateName(dimensionArr[0]);
|
||||
dimensions.put(dimensionArr[0], "");
|
||||
} else if (dimensionArr.length > 1) {
|
||||
DimensionValidation.validateName(dimensionArr[0]);
|
||||
if (dimensionArr[1].contains("|")) {
|
||||
List<String> dimensionValueArr = VERTICAL_BAR_SPLITTER.splitToList(dimensionArr[1]);
|
||||
for (String dimensionValue : dimensionValueArr) {
|
||||
DimensionValidation.validateValue(dimensionValue, dimensionArr[0]);
|
||||
}
|
||||
} else {
|
||||
DimensionValidation.validateValue(dimensionArr[1], dimensionArr[0]);
|
||||
}
|
||||
dimensions.put(dimensionArr[0], dimensionArr[1]);
|
||||
}
|
||||
}
|
||||
|
||||
//DimensionValidation.validate(dimensions);
|
||||
|
|
|
@ -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
|
||||
|
@ -21,6 +21,7 @@ import java.util.Map;
|
|||
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
@Test
|
||||
|
@ -52,4 +53,49 @@ public class DimensionsTest {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void shouldValidateKey() {
|
||||
DimensionValidation.validateName("this.is_a.valid-key");
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = WebApplicationException.class)
|
||||
public void shouldErrorOnValidateKeyWithEmptyKey() {
|
||||
DimensionValidation.validateName("");
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = WebApplicationException.class)
|
||||
public void shouldErrorOnValidateKeyWithLongKey() {
|
||||
String key = StringUtils.repeat("A", 256);
|
||||
DimensionValidation.validateName(key);
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = WebApplicationException.class)
|
||||
public void shouldErrorOnValidateKeyWithStartingUnderscore() {
|
||||
DimensionValidation.validateName("_key");
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = WebApplicationException.class)
|
||||
public void shouldErrorOnValidateKeyWithInvalidCharKey() {
|
||||
DimensionValidation.validateName("this{}that");
|
||||
}
|
||||
|
||||
public void shouldValidateValue() {
|
||||
DimensionValidation.validateValue("this.is_a.valid-value", "valid_name");
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = WebApplicationException.class)
|
||||
public void shouldErrorOnValidateValueWithEmptyValue() {
|
||||
DimensionValidation.validateValue("", "valid_name");
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = WebApplicationException.class)
|
||||
public void shouldErrorOnValidateValueWithLongValue() {
|
||||
String value = StringUtils.repeat("A", 256);
|
||||
DimensionValidation.validateValue(value, "valid_name");
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = WebApplicationException.class)
|
||||
public void shouldErrorOnValidateValueWithInvalidCharValue() {
|
||||
DimensionValidation.validateValue("this{}that", "valid_name");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
@ -221,7 +221,7 @@ public class MetricResourceTest extends AbstractMonApiResourceTest {
|
|||
valueMeta));
|
||||
|
||||
ErrorMessages.assertThat(response.getEntity(String.class)).matches("unprocessable_entity", 422,
|
||||
"Dimension blah cannot have an empty value");
|
||||
"Dimension 'blah' cannot have an empty value");
|
||||
}
|
||||
|
||||
public void shouldErrorOnCreateWithMissingDimensionValue() {
|
||||
|
@ -235,7 +235,7 @@ public class MetricResourceTest extends AbstractMonApiResourceTest {
|
|||
valueMeta));
|
||||
|
||||
ErrorMessages.assertThat(response.getEntity(String.class)).matches("unprocessable_entity", 422,
|
||||
"Dimension flavor_id cannot have an empty value");
|
||||
"Dimension 'flavor_id' cannot have an empty value");
|
||||
}
|
||||
|
||||
public void shouldErrorOnCreateWithTooLongDimensionName() {
|
||||
|
@ -257,7 +257,7 @@ public class MetricResourceTest extends AbstractMonApiResourceTest {
|
|||
.matches(
|
||||
"unprocessable_entity",
|
||||
422,
|
||||
"Dimension name 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789abc must be 255 characters or less");
|
||||
"Dimension name '012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789abc' must be 255 characters or less");
|
||||
}
|
||||
|
||||
public void shouldErrorOnCreateWithTooLongDimensionValue() {
|
||||
|
@ -279,7 +279,7 @@ public class MetricResourceTest extends AbstractMonApiResourceTest {
|
|||
.matches(
|
||||
"unprocessable_entity",
|
||||
422,
|
||||
"Dimension value 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789abc must be 255 characters or less");
|
||||
"Dimension 'abc' value '012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789abc' must be 255 characters or less");
|
||||
}
|
||||
|
||||
public void shouldErrorOnCreateWithHighTimestamp() {
|
||||
|
|
|
@ -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
|
||||
|
@ -52,7 +52,8 @@ public final class ErrorMessages {
|
|||
@Nullable String detailsPrefix) {
|
||||
assertEquals(rootKey, faultType);
|
||||
assertEquals(message.code, code);
|
||||
assertTrue(message.message.startsWith(messagePrefix), message.message);
|
||||
assertTrue(message.message.startsWith(messagePrefix),
|
||||
String.format("String '%s' does not start with '%s'", message.message, messagePrefix));
|
||||
if (detailsPrefix != null)
|
||||
assertTrue(message.details.startsWith(detailsPrefix), message.details);
|
||||
}
|
||||
|
|
|
@ -330,9 +330,9 @@ class AlarmsRepository(sql_repository.SQLRepository,
|
|||
if '|' in parsed_dimension[1]:
|
||||
values = parsed_dimension[1].encode('utf8').split('|')
|
||||
sub_values_cond = []
|
||||
for j, value in values:
|
||||
for j, value in enumerate(values):
|
||||
sub_md_value = "b_md_value_{}_{}".format(i, j)
|
||||
sub_values_cond.append = (md.c.value == bindparam(sub_md_value))
|
||||
sub_values_cond.append(md.c.value == bindparam(sub_md_value))
|
||||
parms[sub_md_value] = value
|
||||
values_cond = or_(*sub_values_cond)
|
||||
values_cond_flag = True
|
||||
|
|
|
@ -68,6 +68,10 @@ class TestDimensionValidation(unittest.TestCase):
|
|||
"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz")
|
||||
self.assertRaises(AssertionError, validation.dimension_key, dim_key)
|
||||
|
||||
def test_key_starts_with_underscore(self):
|
||||
dim_key = '_key'
|
||||
self.assertRaises(AssertionError, validation.dimension_key, dim_key)
|
||||
|
||||
def test_invalid_chars_key(self):
|
||||
for c in invalid_chars:
|
||||
dim_key = "this{}that".format(c)
|
||||
|
|
|
@ -35,6 +35,7 @@ def dimension_key(dkey):
|
|||
assert isinstance(dkey, (str, unicode)), "Dimension key must be a string"
|
||||
assert len(dkey) <= 255, "Dimension key must be 255 characters or less"
|
||||
assert len(dkey) >= 1, "Dimension key cannot be empty"
|
||||
assert dkey[0] != '_', "Dimension key cannot start with underscore (_)"
|
||||
assert not restricted_chars.search(dkey), "Invalid characters in dimension name " + dkey
|
||||
|
||||
|
||||
|
|
|
@ -165,12 +165,15 @@ class Alarms(alarms_api_v2.AlarmsV2API,
|
|||
try:
|
||||
assert isinstance(dimensions, list)
|
||||
for dimension in dimensions:
|
||||
name_value = dimension.split('=')
|
||||
name_value = dimension.split(':')
|
||||
validation.dimension_key(name_value[0])
|
||||
if len(name_value) > 1 and '|' in name_value[1]:
|
||||
values = name_value[1].split('|')
|
||||
for value in values:
|
||||
validation.dimension_value(value)
|
||||
if len(name_value) > 1:
|
||||
if '|' in name_value[1]:
|
||||
values = name_value[1].split('|')
|
||||
for value in values:
|
||||
validation.dimension_value(value)
|
||||
else:
|
||||
validation.dimension_value(name_value[1])
|
||||
except Exception as e:
|
||||
raise HTTPUnprocessableEntityError("Unprocessable Entity", e.message)
|
||||
|
||||
|
|
|
@ -80,8 +80,8 @@ class TestAlarms(base.BaseMonascaTest):
|
|||
alarm_definition_ids, expected_metric \
|
||||
= self._create_alarms_for_test_alarms(num=1)
|
||||
for key in expected_metric['dimensions']:
|
||||
value = expected_metric['dimensions'][key]
|
||||
query_parms = '?metric_dimensions=' + key + ':' + value
|
||||
value = expected_metric['dimensions'][key]
|
||||
query_parms = '?metric_dimensions=' + key + ':' + value
|
||||
resp, response_body = self.monasca_client.list_alarms(query_parms)
|
||||
self._verify_list_alarms_elements(resp, response_body,
|
||||
expect_num_elements=1)
|
||||
|
@ -91,6 +91,22 @@ class TestAlarms(base.BaseMonascaTest):
|
|||
self.assertEqual(alarm_definition_ids[0],
|
||||
element['alarm_definition']['id'])
|
||||
|
||||
@test.attr(type="gate")
|
||||
@test.attr(type=['negative'])
|
||||
def test_list_alarms_by_metric_dimensions_key_exceeds_max_length(self):
|
||||
key = 'x' * (constants.MAX_ALARM_METRIC_DIMENSIONS_KEY_LENGTH + 1)
|
||||
query_parms = '?metric_dimensions=' + key
|
||||
self.assertRaises(exceptions.UnprocessableEntity,
|
||||
self.monasca_client.list_alarms, query_parms)
|
||||
|
||||
@test.attr(type="gate")
|
||||
@test.attr(type=['negative'])
|
||||
def test_list_alarms_by_metric_dimensions_value_exceeds_max_length(self):
|
||||
value = 'x' * (constants.MAX_ALARM_METRIC_DIMENSIONS_VALUE_LENGTH + 1)
|
||||
query_parms = '?metric_dimensions=key:' + value
|
||||
self.assertRaises(exceptions.UnprocessableEntity,
|
||||
self.monasca_client.list_alarms, query_parms)
|
||||
|
||||
@test.attr(type="gate")
|
||||
def test_list_alarms_by_multiple_metric_dimensions(self):
|
||||
metric = helpers.create_metric(
|
||||
|
@ -196,7 +212,7 @@ class TestAlarms(base.BaseMonascaTest):
|
|||
resp, response_body = self.monasca_client.list_alarms('?metric_name=' + metric_name)
|
||||
self.assertEqual(200, resp.status)
|
||||
if len(response_body['elements']) >= 3:
|
||||
return
|
||||
break
|
||||
time.sleep(constants.RETRY_WAIT_SECS)
|
||||
if i >= constants.MAX_RETRIES - 1:
|
||||
self.fail("Timeout creating alarms, required 3 but found {}".format(
|
||||
|
|
Loading…
Reference in New Issue