Doug Hellmann 2a362bea97 ignore python2-specific code under python3 for pep8
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>
2018-06-06 17:03:33 -04:00

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)