
The type 'long' no longer exists under python 3. The runtime execution of the code using it in the metrics module already detects the python version, but when the linter is run under python 3 it does not apply the same check. Add a noqa pragma to tell the linter to ignore the line where 'long' is referenced to avoid an error. Change-Id: If806e6461358ea523708eceed6fdcc3dfb8d75a9 Signed-off-by: Doug Hellmann <doug@doughellmann.com>
178 lines
5.9 KiB
Python
178 lines
5.9 KiB
Python
# (C) Copyright 2016-2017 Hewlett Packard Enterprise Development 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 a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
# implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import math
|
|
import re
|
|
|
|
import six
|
|
import ujson
|
|
|
|
# This is used to ensure that metrics with a timestamp older than
|
|
# RECENT_POINT_THRESHOLD_DEFAULT seconds (or the value passed in to
|
|
# the MetricsAggregator constructor) get discarded rather than being
|
|
# input into the incorrect bucket. Currently, the MetricsAggregator
|
|
# does not support submitting values for the past, and all values get
|
|
# submitted for the timestamp passed into the flush() function.
|
|
RECENT_POINT_THRESHOLD_DEFAULT = 3600
|
|
VALUE_META_MAX_NUMBER = 16
|
|
VALUE_META_VALUE_MAX_LENGTH = 2048
|
|
VALUE_META_NAME_MAX_LENGTH = 255
|
|
|
|
INVALID_CHARS = "<>={},\"\\\\;&"
|
|
RESTRICTED_DIMENSION_CHARS = re.compile('[' + INVALID_CHARS + ']')
|
|
RESTRICTED_NAME_CHARS = re.compile('[' + INVALID_CHARS + '() ' + ']')
|
|
|
|
NUMERIC_VALUES = [int, float]
|
|
if six.PY2:
|
|
# according to PEP537 long was renamed to int in PY3
|
|
# need to add long, as possible value, for PY2
|
|
NUMERIC_VALUES += [long] # noqa
|
|
|
|
NUMERIC_VALUES = tuple(NUMERIC_VALUES) # convert to tuple for instance call
|
|
|
|
|
|
class InvalidMetricName(Exception):
|
|
pass
|
|
|
|
|
|
class InvalidDimensionKey(Exception):
|
|
pass
|
|
|
|
|
|
class InvalidDimensionValue(Exception):
|
|
pass
|
|
|
|
|
|
class InvalidValue(Exception):
|
|
pass
|
|
|
|
|
|
class InvalidValueMeta(Exception):
|
|
pass
|
|
|
|
|
|
class InvalidTimeStamp(Exception):
|
|
pass
|
|
|
|
|
|
def validate(metrics):
|
|
if isinstance(metrics, list):
|
|
for metric in metrics:
|
|
validate_metric(metric)
|
|
else:
|
|
validate_metric(metrics)
|
|
|
|
|
|
def validate_metric(metric):
|
|
validate_name(metric['name'])
|
|
validate_value(metric['value'])
|
|
validate_timestamp(metric['timestamp'])
|
|
if "dimensions" in metric:
|
|
validate_dimensions(metric['dimensions'])
|
|
if "value_meta" in metric:
|
|
validate_value_meta(metric['value_meta'])
|
|
|
|
|
|
def validate_value_meta(value_meta):
|
|
if value_meta is None:
|
|
return
|
|
if len(value_meta) > VALUE_META_MAX_NUMBER:
|
|
msg = "Too many valueMeta entries {0}, limit is {1}: valueMeta {2}".\
|
|
format(len(value_meta), VALUE_META_MAX_NUMBER, value_meta)
|
|
raise InvalidValueMeta(msg)
|
|
for key, value in six.iteritems(value_meta):
|
|
if not key:
|
|
raise InvalidValueMeta("valueMeta name cannot be empty: key={}, "
|
|
"value={}".format(key, value))
|
|
if len(key) > VALUE_META_NAME_MAX_LENGTH:
|
|
msg = "valueMeta name too long: {0} must be {1} characters or " \
|
|
"less".format(key, VALUE_META_NAME_MAX_LENGTH)
|
|
raise InvalidValueMeta(msg)
|
|
|
|
try:
|
|
value_meta_json = ujson.dumps(value_meta)
|
|
if len(value_meta_json) > VALUE_META_VALUE_MAX_LENGTH:
|
|
msg = "valueMeta name value combinations must be {0} characters " \
|
|
"or less: valueMeta {1}".format(VALUE_META_VALUE_MAX_LENGTH,
|
|
value_meta)
|
|
raise InvalidValueMeta(msg)
|
|
except Exception:
|
|
raise InvalidValueMeta("Unable to serialize valueMeta into JSON")
|
|
|
|
|
|
def validate_dimension_key(k):
|
|
if not isinstance(k, (str, six.text_type)):
|
|
msg = "invalid dimension key type: " \
|
|
"{0} is not a string type".format(k)
|
|
raise InvalidDimensionKey(msg)
|
|
if len(k) > 255 or len(k) < 1:
|
|
msg = "invalid length ({0}) for dimension key {1}". \
|
|
format(len(k), k)
|
|
raise InvalidDimensionKey(msg)
|
|
if RESTRICTED_DIMENSION_CHARS.search(k) or re.match('^_', k):
|
|
msg = "invalid characters in dimension key {0}". \
|
|
format(k)
|
|
raise InvalidDimensionKey(msg)
|
|
|
|
|
|
def validate_dimension_value(k, v):
|
|
if not isinstance(v, (str, six.text_type)):
|
|
msg = "invalid dimension value type: {0} must be a " \
|
|
"string (from key {1})".format(v, k)
|
|
raise InvalidDimensionValue(msg)
|
|
if len(v) > 255 or len(v) < 1:
|
|
msg = "invalid length ({0}) for dimension value {1} from key {2}". \
|
|
format(len(v), v, k)
|
|
raise InvalidDimensionValue(msg)
|
|
if RESTRICTED_DIMENSION_CHARS.search(v):
|
|
msg = "invalid characters in dimension value {0} from key {1}".format(v, k)
|
|
raise InvalidDimensionValue(msg)
|
|
|
|
|
|
def validate_dimensions(dimensions):
|
|
for k, v in six.iteritems(dimensions):
|
|
validate_dimension_key(k)
|
|
validate_dimension_value(k, v)
|
|
|
|
|
|
def validate_name(name):
|
|
if not isinstance(name, (str, six.text_type)):
|
|
msg = "invalid metric name type: {0} is not a string type ".format(
|
|
name)
|
|
raise InvalidMetricName(msg)
|
|
if len(name) > 255 or len(name) < 1:
|
|
msg = "invalid length for metric name: {0}".format(name)
|
|
raise InvalidMetricName(msg)
|
|
if RESTRICTED_NAME_CHARS.search(name):
|
|
msg = "invalid characters in metric name: {0}".format(name)
|
|
raise InvalidMetricName(msg)
|
|
|
|
|
|
def validate_value(value):
|
|
if not isinstance(value, NUMERIC_VALUES):
|
|
msg = "invalid value type: {0} is not a number type for metric".\
|
|
format(value)
|
|
raise InvalidValue(msg)
|
|
if math.isnan(value) or math.isinf(value):
|
|
msg = "invalid value: {0} is not a valid value for metric".format(value)
|
|
raise InvalidValue(msg)
|
|
|
|
|
|
def validate_timestamp(timestamp):
|
|
if not isinstance(timestamp, NUMERIC_VALUES):
|
|
msg = "invalid timestamp type: {0} is not a number type for " \
|
|
"metric".format(timestamp)
|
|
raise InvalidTimeStamp(msg)
|