267 lines
8.5 KiB
Python
267 lines
8.5 KiB
Python
# -*- encoding: utf-8 -*-
|
|
#
|
|
# 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 numbers
|
|
import re
|
|
import six
|
|
import stevedore
|
|
import voluptuous
|
|
|
|
from gnocchi import utils
|
|
|
|
|
|
INVALID_NAMES = [
|
|
"id", "type", "metrics",
|
|
"revision", "revision_start", "revision_end",
|
|
"started_at", "ended_at",
|
|
"user_id", "project_id",
|
|
"created_by_user_id", "created_by_project_id", "get_metric",
|
|
"creator",
|
|
]
|
|
|
|
VALID_CHARS = re.compile("[a-zA-Z0-9][a-zA-Z0-9_]*")
|
|
|
|
|
|
class InvalidResourceAttribute(ValueError):
|
|
pass
|
|
|
|
|
|
class InvalidResourceAttributeName(InvalidResourceAttribute):
|
|
"""Error raised when the resource attribute name is invalid."""
|
|
def __init__(self, name):
|
|
super(InvalidResourceAttributeName, self).__init__(
|
|
"Resource attribute name %s is invalid" % str(name))
|
|
self.name = name
|
|
|
|
|
|
class InvalidResourceAttributeValue(InvalidResourceAttribute):
|
|
"""Error raised when the resource attribute min is greater than max"""
|
|
def __init__(self, min, max):
|
|
super(InvalidResourceAttributeValue, self).__init__(
|
|
"Resource attribute value min (or min_length) %s must be less "
|
|
"than or equal to max (or max_length) %s!" % (str(min), str(max)))
|
|
self.min = min
|
|
self.max = max
|
|
|
|
|
|
class InvalidResourceAttributeOption(InvalidResourceAttribute):
|
|
"""Error raised when the resource attribute name is invalid."""
|
|
def __init__(self, name, option, reason):
|
|
super(InvalidResourceAttributeOption, self).__init__(
|
|
"Option '%s' of resource attribute %s is invalid: %s" %
|
|
(option, str(name), str(reason)))
|
|
self.name = name
|
|
self.option = option
|
|
self.reason = reason
|
|
|
|
|
|
# NOTE(sileht): This is to store the behavior of some operations:
|
|
# * fill, to set a default value to all existing resource type
|
|
#
|
|
# in the future for example, we can allow to change the length of
|
|
# a string attribute, if the new one is shorter, we can add a option
|
|
# to define the behavior like:
|
|
# * resize = trunc or reject
|
|
OperationOptions = {
|
|
voluptuous.Optional('fill'): object
|
|
}
|
|
|
|
|
|
class CommonAttributeSchema(object):
|
|
meta_schema_ext = {}
|
|
schema_ext = None
|
|
|
|
def __init__(self, type, name, required, options=None):
|
|
if (len(name) > 63 or name in INVALID_NAMES
|
|
or not VALID_CHARS.match(name)):
|
|
raise InvalidResourceAttributeName(name)
|
|
|
|
self.name = name
|
|
self.required = required
|
|
self.fill = None
|
|
|
|
# options is set only when we update a resource type
|
|
if options is not None:
|
|
fill = options.get("fill")
|
|
if fill is None and required:
|
|
raise InvalidResourceAttributeOption(
|
|
name, "fill", "must not be empty if required=True")
|
|
elif fill is not None:
|
|
# Ensure fill have the correct attribute type
|
|
try:
|
|
self.fill = voluptuous.Schema(self.schema_ext)(fill)
|
|
except voluptuous.Error as e:
|
|
raise InvalidResourceAttributeOption(name, "fill", e)
|
|
|
|
@classmethod
|
|
def meta_schema(cls, for_update=False):
|
|
d = {
|
|
voluptuous.Required('type'): cls.typename,
|
|
voluptuous.Required('required', default=True): bool
|
|
}
|
|
if for_update:
|
|
d[voluptuous.Required('options', default={})] = OperationOptions
|
|
if callable(cls.meta_schema_ext):
|
|
d.update(cls.meta_schema_ext())
|
|
else:
|
|
d.update(cls.meta_schema_ext)
|
|
return d
|
|
|
|
def schema(self):
|
|
if self.required:
|
|
return {self.name: self.schema_ext}
|
|
else:
|
|
return {voluptuous.Optional(self.name): self.schema_ext}
|
|
|
|
def jsonify(self):
|
|
return {"type": self.typename,
|
|
"required": self.required}
|
|
|
|
|
|
class StringSchema(CommonAttributeSchema):
|
|
typename = "string"
|
|
|
|
def __init__(self, min_length, max_length, *args, **kwargs):
|
|
if min_length > max_length:
|
|
raise InvalidResourceAttributeValue(min_length, max_length)
|
|
|
|
self.min_length = min_length
|
|
self.max_length = max_length
|
|
super(StringSchema, self).__init__(*args, **kwargs)
|
|
|
|
meta_schema_ext = {
|
|
voluptuous.Required('min_length', default=0):
|
|
voluptuous.All(int, voluptuous.Range(min=0, max=255)),
|
|
voluptuous.Required('max_length', default=255):
|
|
voluptuous.All(int, voluptuous.Range(min=1, max=255))
|
|
}
|
|
|
|
@property
|
|
def schema_ext(self):
|
|
return voluptuous.All(six.text_type,
|
|
voluptuous.Length(
|
|
min=self.min_length,
|
|
max=self.max_length))
|
|
|
|
def jsonify(self):
|
|
d = super(StringSchema, self).jsonify()
|
|
d.update({"max_length": self.max_length,
|
|
"min_length": self.min_length})
|
|
return d
|
|
|
|
|
|
class UUIDSchema(CommonAttributeSchema):
|
|
typename = "uuid"
|
|
schema_ext = staticmethod(utils.UUID)
|
|
|
|
|
|
class NumberSchema(CommonAttributeSchema):
|
|
typename = "number"
|
|
|
|
def __init__(self, min, max, *args, **kwargs):
|
|
if max is not None and min is not None and min > max:
|
|
raise InvalidResourceAttributeValue(min, max)
|
|
self.min = min
|
|
self.max = max
|
|
super(NumberSchema, self).__init__(*args, **kwargs)
|
|
|
|
meta_schema_ext = {
|
|
voluptuous.Required('min', default=None): voluptuous.Any(
|
|
None, numbers.Real),
|
|
voluptuous.Required('max', default=None): voluptuous.Any(
|
|
None, numbers.Real)
|
|
}
|
|
|
|
@property
|
|
def schema_ext(self):
|
|
return voluptuous.All(numbers.Real,
|
|
voluptuous.Range(min=self.min,
|
|
max=self.max))
|
|
|
|
def jsonify(self):
|
|
d = super(NumberSchema, self).jsonify()
|
|
d.update({"min": self.min, "max": self.max})
|
|
return d
|
|
|
|
|
|
class BoolSchema(CommonAttributeSchema):
|
|
typename = "bool"
|
|
schema_ext = bool
|
|
|
|
|
|
class ResourceTypeAttributes(list):
|
|
def jsonify(self):
|
|
d = {}
|
|
for attr in self:
|
|
d[attr.name] = attr.jsonify()
|
|
return d
|
|
|
|
|
|
class ResourceTypeSchemaManager(stevedore.ExtensionManager):
|
|
def __init__(self, *args, **kwargs):
|
|
super(ResourceTypeSchemaManager, self).__init__(*args, **kwargs)
|
|
type_schemas = tuple([ext.plugin.meta_schema()
|
|
for ext in self.extensions])
|
|
self._schema = voluptuous.Schema({
|
|
"name": six.text_type,
|
|
voluptuous.Required("attributes", default={}): {
|
|
six.text_type: voluptuous.Any(*tuple(type_schemas))
|
|
}
|
|
})
|
|
|
|
type_schemas = tuple([ext.plugin.meta_schema(for_update=True)
|
|
for ext in self.extensions])
|
|
self._schema_for_update = voluptuous.Schema({
|
|
"name": six.text_type,
|
|
voluptuous.Required("attributes", default={}): {
|
|
six.text_type: voluptuous.Any(*tuple(type_schemas))
|
|
}
|
|
})
|
|
|
|
def __call__(self, definition):
|
|
return self._schema(definition)
|
|
|
|
def for_update(self, definition):
|
|
return self._schema_for_update(definition)
|
|
|
|
def attributes_from_dict(self, attributes):
|
|
return ResourceTypeAttributes(
|
|
self[attr["type"]].plugin(name=name, **attr)
|
|
for name, attr in attributes.items())
|
|
|
|
def resource_type_from_dict(self, name, attributes, state):
|
|
return ResourceType(name, self.attributes_from_dict(attributes), state)
|
|
|
|
|
|
class ResourceType(object):
|
|
def __init__(self, name, attributes, state):
|
|
self.name = name
|
|
self.attributes = attributes
|
|
self.state = state
|
|
|
|
@property
|
|
def schema(self):
|
|
schema = {}
|
|
for attr in self.attributes:
|
|
schema.update(attr.schema())
|
|
return schema
|
|
|
|
def __eq__(self, other):
|
|
return self.name == other.name
|
|
|
|
def jsonify(self):
|
|
return {"name": self.name,
|
|
"attributes": self.attributes.jsonify(),
|
|
"state": self.state}
|