config/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/types.py

268 lines
8.2 KiB
Python

# vim: tabstop=4 shiftwidth=4 softtabstop=4
# coding: utf-8
#
# Copyright 2013 Red Hat, Inc.
# All Rights Reserved.
#
# 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.
#
# Copyright (c) 2013-2015 Wind River Systems, Inc.
#
from oslo_utils import strutils
import re
import six
import wsme
from wsme import types as wtypes
from sysinv.common import exception
from sysinv.common import utils
from sysinv.api.controllers.v1 import utils as apiutils
from sysinv.openstack.common.gettextutils import _
class MACAddressType(wtypes.UserType):
"""A simple MAC address type."""
basetype = wtypes.text
name = 'macaddress'
@staticmethod
def validate(value):
return utils.validate_and_normalize_mac(value)
@staticmethod
def frombasetype(value):
return MACAddressType.validate(value)
class UUIDType(wtypes.UserType):
"""A simple UUID type."""
basetype = wtypes.text
name = 'uuid'
# FIXME(lucasagomes): When used with wsexpose decorator WSME will try
# to get the name of the type by accessing it's __name__ attribute.
# Remove this __name__ attribute once it's fixed in WSME.
# https://bugs.launchpad.net/wsme/+bug/1265590
__name__ = name
@staticmethod
def validate(value):
if not utils.is_uuid_like(value):
raise exception.InvalidUUID(uuid=value)
return value
@staticmethod
def frombasetype(value):
if value is None:
return None
return UUIDType.validate(value)
class BooleanType(wtypes.UserType):
"""A simple boolean type."""
basetype = wtypes.text
name = 'boolean'
@staticmethod
def validate(value):
try:
return strutils.bool_from_string(value, strict=True)
except ValueError as e:
# raise Invalid to return 400 (BadRequest) in the API
raise exception.Invalid(six.text_type(e))
@staticmethod
def frombasetype(value):
if value is None:
return None
return BooleanType.validate(value)
class IPAddressType(wtypes.UserType):
"""A generic IP address type that supports both IPv4 and IPv6."""
basetype = wtypes.text
name = 'ipaddress'
# FIXME(lucasagomes): When used with wsexpose decorator WSME will try
# to get the name of the type by accessing it's __name__ attribute.
# Remove this __name__ attribute once it's fixed in WSME.
# https://bugs.launchpad.net/wsme/+bug/1265590
__name__ = name
@staticmethod
def validate(value):
if not utils.is_valid_ip(value):
raise exception.InvalidIPAddress(address=value)
return value
@staticmethod
def frombasetype(value):
if value is None:
return None
return IPAddressType.validate(value)
macaddress = MACAddressType()
uuid = UUIDType()
boolean = BooleanType()
ipaddress = IPAddressType()
class ApiDictType(wtypes.UserType):
name = 'apidict'
__name__ = name
basetype = {wtypes.text: apiutils.ValidTypes(wtypes.text, six.integer_types)}
apidict = ApiDictType()
# TODO(lucasagomes): WSME already has this StringType implementation on trunk,
# so remove it on the next WSME release (> 0.5b6)
class StringType(wtypes.UserType):
"""A simple string type. Can validate a length and a pattern.
:param min_length: Possible minimum length
:param max_length: Possible maximum length
:param pattern: Possible string pattern
Example::
Name = StringType(min_length=1, pattern='^[a-zA-Z ]*$')
"""
basetype = six.string_types
name = "string"
def __init__(self, min_length=None, max_length=None, pattern=None):
self.min_length = min_length
self.max_length = max_length
if isinstance(pattern, six.string_types):
self.pattern = re.compile(pattern)
else:
self.pattern = pattern
def validate(self, value):
if not isinstance(value, self.basetype):
error = 'Value should be string'
raise ValueError(error)
if self.min_length is not None and len(value) < self.min_length:
error = 'Value should have a minimum character requirement of %s' \
% self.min_length
raise ValueError(error)
if self.max_length is not None and len(value) > self.max_length:
error = 'Value should have a maximum character requirement of %s' \
% self.max_length
raise ValueError(error)
if value != 'nameservers=NC' or value != 'ntpservers=NC':
if self.pattern is not None and not self.pattern.match(value):
error = 'Value should match the pattern %s' % self.pattern.pattern
raise ValueError(error)
return value
class JsonPatchType(wtypes.Base):
"""A complex type that represents a single json-patch operation."""
path = wtypes.wsattr(StringType(pattern='^(/[\w-]+)+$'), mandatory=True)
op = wtypes.wsattr(wtypes.Enum(str, 'add', 'replace', 'remove'),
mandatory=True)
# TODO(jgauld): Should get most recent ironic JsonType/value_types
value = apiutils.ValidTypes(wtypes.text, six.integer_types, float)
@staticmethod
def internal_attrs():
"""Returns a list of internal attributes.
Internal attributes can't be added, replaced or removed. This
method may be overwritten by derived class.
"""
return ['/created_at', '/id', '/links', '/updated_at', '/uuid']
@staticmethod
def mandatory_attrs():
"""Retruns a list of mandatory attributes.
Mandatory attributes can't be removed from the document. This
method should be overwritten by derived class.
"""
return []
@staticmethod
def validate(patch):
if patch.path in patch.internal_attrs():
msg = _("'%s' is an internal attribute and can not be updated")
raise wsme.exc.ClientSideError(msg % patch.path)
if patch.path in patch.mandatory_attrs() and patch.op == 'remove':
msg = _("'%s' is a mandatory attribute and can not be removed")
raise wsme.exc.ClientSideError(msg % patch.path)
if patch.op == 'add':
if patch.path.count('/') == 1:
msg = _('Adding a new attribute (%s) to the root of '
' the resource is not allowed')
raise wsme.exc.ClientSideError(msg % patch.path)
if patch.op != 'remove':
if not patch.value:
msg = _("Edit and Add operation of the field requires "
"non-empty value.")
raise wsme.exc.ClientSideError(msg)
ret = {'path': patch.path, 'op': patch.op}
if patch.value:
ret['value'] = patch.value
return ret
class MultiType(wtypes.UserType):
"""A complex type that represents one or more types.
Used for validating that a value is an instance of one of the types.
:param *types: Variable-length list of types.
"""
def __init__(self, types):
self.types = types
def validate(self, value):
for t in self.types:
if t is wsme.types.text and isinstance(value, wsme.types.bytes):
value = value.decode()
if isinstance(t, list):
if isinstance(value, list):
for v in value:
if not isinstance(v, t[0]):
break
else:
return value
elif isinstance(value, t):
return value
else:
raise ValueError(
_("Wrong type. Expected '%(type)s', got '%(value)s'")
% {'type': self.types, 'value': type(value)})