Validate value meta key and value in python api

Add validation for value meta in python api
Add unit test and tempest tests on value meta validation
Change the documentation

Change-Id: I4633785211e83d22f016ecfbdcbca3896043cf00
This commit is contained in:
Kaiyan Sheng 2016-03-16 15:18:00 -06:00
parent 9e6598c079
commit 02bf7a9858
6 changed files with 102 additions and 8 deletions

View File

@ -361,7 +361,7 @@ Optionally, a measurement may also contain extra data about the value which is k
For an example of how value meta is used, imagine this metric: http_status{url: http://localhost:8080/healthcheck, hostname=devstack, service=object-storage}. The measurements for this metric have a value of either 1 or 0 depending if the status check succeeded. If the check fails, it would be helpful to have the actual http status code and error message if possible. So instead of just a value, the measurement will be something like:
{Timestamp=now(), value=1, value_meta{http_rc=500, error=“Error accessing MySQL”}}
Up to 16 separate key/value pairs of value meta are allowed per measurement. The keys are required and are trimmed of leading and trailing whitespace and have a maximum length of 255 characters. The value is a string and has a maximum length of 2048 characters. The value can be an empty string. Whitespace is not trimmed from the values.
Up to 16 separate key/value pairs of value meta are allowed per measurement. The keys are required and are trimmed of leading and trailing whitespace and have a maximum length of 255 characters. The value is a string and value meta (with key, value and '{"":""}' combined) has a maximum length of 2048 characters. The value can be an empty string. Whitespace is not trimmed from the values.
## Alarm Definitions and Alarms
@ -881,7 +881,7 @@ Consists of a single metric object or an array of metric objects. A metric has t
* dimensions ({string(255): string(255)}, optional) - A dictionary consisting of (key, value) pairs used to uniquely identify a metric.
* timestamp (string, required) - The timestamp in milliseconds from the Epoch.
* value (float, required) - Value of the metric. Values with base-10 exponents greater than 126 or less than -130 are truncated.
* value_meta ({string(255): string(2048)}, optional) - A dictionary consisting of (key, value) pairs used to add information about the value.
* value_meta ({string(255): string}(2048), optional) - A dictionary consisting of (key, value) pairs used to add information about the value. Value_meta key value combinations must be 2048 characters or less including '{"":""}' 7 characters total from every json string.
The name and dimensions are used to uniquely identify a metric.

View File

@ -1,6 +1,5 @@
# Copyright 2015 Hewlett-Packard
# (C) Copyright 2015-2016 Hewlett Packard Enterprise Development Company LP
# Copyright 2015 Cray Inc. All Rights Reserved.
# Copyright 2016 Hewlett Packard Enterprise Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@ -95,6 +94,44 @@ class TestDimensionValidation(unittest.TestCase):
self.assertRaises(AssertionError, validation.dimension_value, dim_value)
class TestValueMetaValidation(unittest.TestCase):
def test_valid_name(self):
value_meta_name = "this.is_a.valid-name"
value_meta = {value_meta_name: 'value_meta_value'}
validation.validate_value_meta(value_meta)
self.assertTrue(True)
def test_nonstring_name(self):
value_meta_name = 123456
value_meta = {value_meta_name: 'value_meta_value'}
self.assertRaises(AssertionError, validation.validate_value_meta,
value_meta)
def test_long_name(self):
value_meta_name = "x" * 256
value_meta = {value_meta_name: 'value_meta_value'}
self.assertRaises(AssertionError, validation.validate_value_meta,
value_meta)
def test_valid_value(self):
value_meta_value = "this.is_a.valid-value"
value_meta = {'value_meta_name': value_meta_value}
validation.validate_value_meta(value_meta)
self.assertTrue(True)
def test_nonstring_value(self):
value_meta_value = 123456
value_meta = {'value_meta_name': value_meta_value}
self.assertRaises(AssertionError, validation.validate_value_meta,
value_meta)
def test_long_value_meta(self):
value_meta_value = "x" * 2048
value_meta = {'value_meta_name': value_meta_value}
self.assertRaises(AssertionError, validation.validate_value_meta,
value_meta)
class TestRoleValidation(unittest.TestCase):
def test_role_valid(self):

View File

@ -14,6 +14,7 @@
from monasca_api.v2.common.exceptions import HTTPUnprocessableEntityError
import json
import re
invalid_chars = "<>={}(),\"\\\\|;&"
@ -23,6 +24,12 @@ VALID_ALARM_STATES = ["ALARM", "OK", "UNDETERMINED"]
VALID_ALARM_DEFINITION_SEVERITIES = ["LOW", "MEDIUM", "HIGH", "CRITICAL"]
VALUE_META_MAX_NUMBER = 16
VALUE_META_MAX_LENGTH = 2048
VALUE_META_NAME_MAX_LENGTH = 255
def metric_name(name):
assert isinstance(name, (str, unicode)), "Metric name must be a string"
@ -74,3 +81,23 @@ def validate_sort_by(sort_by_list, allowed_sort_by):
raise HTTPUnprocessableEntityError("Unprocessable Entity",
"sort_by value {} must be 'asc' or 'desc'".format(
sort_by_values[1]))
def validate_value_meta(value_meta):
value_meta_string = json.dumps(value_meta)
# entries
assert len(value_meta) <= VALUE_META_MAX_NUMBER, "ValueMeta entries must be {} or less".format(
VALUE_META_MAX_NUMBER)
# total length
assert len(value_meta_string) <= VALUE_META_MAX_LENGTH, \
"ValueMeta name value combinations must be {} characters or less".format(
VALUE_META_MAX_LENGTH)
for name in value_meta:
# name
assert isinstance(name, (str, unicode)), "ValueMeta name must be a string"
assert len(name) <= VALUE_META_NAME_MAX_LENGTH, "ValueMeta name must be {} characters or less".format(
VALUE_META_NAME_MAX_LENGTH)
assert len(name) >= 1, "ValueMeta name cannot be empty"
# 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"

View File

@ -1,4 +1,4 @@
# Copyright 2014 Hewlett-Packard
# (C) Copyright 2014, 2016 Hewlett Packard Enterprise Development Company LP
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@ -78,7 +78,7 @@ class Metrics(metrics_api_v2.MetricsV2API):
else:
self._validate_single_metric(metrics)
except Exception as ex:
LOG.debug(ex)
LOG.exception(ex)
raise HTTPUnprocessableEntityError('Unprocessable Entity', ex.message)
def _validate_single_metric(self, metric):
@ -89,9 +89,10 @@ class Metrics(metrics_api_v2.MetricsV2API):
for dimension_key in metric['dimensions']:
validation.dimension_key(dimension_key)
validation.dimension_value(metric['dimensions'][dimension_key])
if "value_meta" in metric:
validation.validate_value_meta(metric['value_meta'])
def _send_metrics(self, metrics):
try:
self._message_queue.send_message_batch(metrics)
except message_queue_exceptions.MessageQueueException as ex:

View File

@ -1,4 +1,4 @@
# (C) Copyright 2015 Hewlett Packard Enterprise Development Company LP
# (C) Copyright 2015-2016 Hewlett Packard Enterprise Development Company LP
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@ -39,3 +39,6 @@ MAX_ALARM_METRIC_NAME_LENGTH = 255
MAX_ALARM_METRIC_DIMENSIONS_KEY_LENGTH = 255
MAX_ALARM_METRIC_DIMENSIONS_VALUE_LENGTH = 255
MAX_ALARM_LINK_LENGTH = 512
MAX_VALUE_META_NAME_LENGTH = 255
MAX_VALUE_META_TOTAL_LENGTH = 2048

View File

@ -282,6 +282,32 @@ class TestMetrics(base.BaseMonascaTest):
self.monasca_client.create_metrics,
metric)
@test.attr(type='gate')
@test.attr(type=['negative'])
def test_create_metric_with_value_meta_name_exceeds_max_length(self):
long_value_meta_name = "x" * (constants.MAX_VALUE_META_NAME_LENGTH + 1)
metric = helpers.create_metric(name='name',
value_meta=
{long_value_meta_name:
"value_meta_value"}
)
self.assertRaises(exceptions.UnprocessableEntity,
self.monasca_client.create_metrics,
metric)
@test.attr(type='gate')
@test.attr(type=['negative'])
def test_create_metric_with_value_meta_exceeds_max_length(self):
value_meta_name = "x"
long_value_meta_value = "y" * constants.MAX_VALUE_META_TOTAL_LENGTH
metric = helpers.create_metric(name='name',
value_meta=
{value_meta_name: long_value_meta_value}
)
self.assertRaises(exceptions.UnprocessableEntity,
self.monasca_client.create_metrics,
metric)
@test.attr(type='gate')
def test_list_metrics(self):
resp, response_body = self.monasca_client.list_metrics()