Cloudpulse API handling code
Change-Id: I2b50cf9bd59d96274a96561337d0174ab0aabbdf Implements: blueprint cloudpulse-api-handlers
This commit is contained in:
115
cloudpulse/api/controllers/v1/__init__.py
Normal file
115
cloudpulse/api/controllers/v1/__init__.py
Normal file
@@ -0,0 +1,115 @@
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Version 1 of the Cloudpulse API
|
||||
|
||||
NOTE: IN PROGRESS AND NOT FULLY IMPLEMENTED.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
|
||||
import pecan
|
||||
from pecan import rest
|
||||
import wsme
|
||||
from wsme import types as wtypes
|
||||
import wsmeext.pecan as wsme_pecan
|
||||
|
||||
from cloudpulse.api.controllers import link
|
||||
from cloudpulse.api.controllers.v1 import cpulse
|
||||
|
||||
|
||||
class APIBase(wtypes.Base):
|
||||
|
||||
created_at = wsme.wsattr(datetime.datetime, readonly=True)
|
||||
"""The time in UTC at which the object is created"""
|
||||
|
||||
updated_at = wsme.wsattr(datetime.datetime, readonly=True)
|
||||
"""The time in UTC at which the object is updated"""
|
||||
|
||||
def as_dict(self):
|
||||
"""Render this object as a dict of its fields."""
|
||||
return dict((k, getattr(self, k))
|
||||
for k in self.fields
|
||||
if hasattr(self, k) and
|
||||
getattr(self, k) != wsme.Unset)
|
||||
|
||||
def unset_fields_except(self, except_list=None):
|
||||
"""Unset fields so they don't appear in the message body.
|
||||
|
||||
:param except_list: A list of fields that won't be touched.
|
||||
|
||||
"""
|
||||
if except_list is None:
|
||||
except_list = []
|
||||
|
||||
for k in self.as_dict():
|
||||
if k not in except_list:
|
||||
setattr(self, k, wsme.Unset)
|
||||
|
||||
|
||||
class V1(APIBase):
|
||||
"""The representation of the version 1 of the API."""
|
||||
|
||||
id = wtypes.text
|
||||
"""The ID of the version, also acts as the release number"""
|
||||
|
||||
cpulse = [link.Link]
|
||||
"""Links to the cpulse resource"""
|
||||
|
||||
extcpulse = [link.Link]
|
||||
"""Links to the cpulse extension resource"""
|
||||
|
||||
@staticmethod
|
||||
def convert():
|
||||
v1 = V1()
|
||||
v1.id = "v1"
|
||||
v1.links = [link.Link.make_link('self', pecan.request.host_url,
|
||||
'v1', '', bookmark=True),
|
||||
link.Link.make_link('describedby',
|
||||
'http://docs.openstack.org',
|
||||
'developer/cloudpulse/dev',
|
||||
'api-spec-v1.html',
|
||||
bookmark=True, type='text/html')
|
||||
]
|
||||
v1.cpulse = [link.Link.make_link('self', pecan.request.host_url,
|
||||
'cpulse', ''),
|
||||
link.Link.make_link('bookmark',
|
||||
pecan.request.host_url,
|
||||
'cpulse', '',
|
||||
bookmark=True)
|
||||
]
|
||||
v1.cpulse_ext = [link.Link.make_link('self', pecan.request.host_url,
|
||||
'cpulse_ext', ''),
|
||||
link.Link.make_link('bookmark',
|
||||
pecan.request.host_url,
|
||||
'cpulse_ext', '',
|
||||
bookmark=True)
|
||||
]
|
||||
return v1
|
||||
|
||||
|
||||
class Controller(rest.RestController):
|
||||
"""Version 1 API controller root."""
|
||||
|
||||
cpulse = cpulse.cpulseController()
|
||||
|
||||
@wsme_pecan.wsexpose(V1)
|
||||
def get(self):
|
||||
# NOTE: The reason why convert() it's being called for every
|
||||
# request is because we need to get the host url from
|
||||
# the request object to make the links.
|
||||
return V1.convert()
|
||||
|
||||
__all__ = (Controller)
|
48
cloudpulse/api/controllers/v1/collection.py
Normal file
48
cloudpulse/api/controllers/v1/collection.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# 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.
|
||||
|
||||
import pecan
|
||||
from wsme import types as wtypes
|
||||
|
||||
from cloudpulse.api.controllers import base
|
||||
from cloudpulse.api.controllers import link
|
||||
|
||||
|
||||
class Collection(base.APIBase):
|
||||
|
||||
next = wtypes.text
|
||||
"""A link to retrieve the next subset of the collection"""
|
||||
|
||||
@property
|
||||
def collection(self):
|
||||
return getattr(self, self._type)
|
||||
|
||||
def has_next(self, limit):
|
||||
"""Return whether collection has more items."""
|
||||
return len(self.collection) and len(self.collection) == limit
|
||||
|
||||
def get_next(self, limit, url=None, **kwargs):
|
||||
"""Return a link to the next subset of the collection."""
|
||||
if not self.has_next(limit):
|
||||
return wtypes.Unset
|
||||
|
||||
resource_url = url or self._type
|
||||
q_args = ''.join(['%s=%s&' % (key, kwargs[key]) for key in kwargs])
|
||||
next_args = ('?%(args)slimit=%(limit)d&marker=%(marker)s'
|
||||
% {'args': q_args, 'limit': limit,
|
||||
'marker': self.collection[-1].uuid})
|
||||
|
||||
return link.Link.make_link('next', pecan.request.host_url,
|
||||
resource_url, next_args).href
|
231
cloudpulse/api/controllers/v1/cpulse.py
Normal file
231
cloudpulse/api/controllers/v1/cpulse.py
Normal file
@@ -0,0 +1,231 @@
|
||||
# Copyright 2013 UnitedStack 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.
|
||||
|
||||
import datetime
|
||||
|
||||
import pecan
|
||||
from pecan import rest
|
||||
import wsme
|
||||
from wsme import types as wtypes
|
||||
import wsmeext.pecan as wsme_pecan
|
||||
|
||||
from cloudpulse.api.controllers import base
|
||||
from cloudpulse.api.controllers import link
|
||||
from cloudpulse.api.controllers.v1 import collection
|
||||
from cloudpulse.api.controllers.v1 import types
|
||||
from cloudpulse.api.controllers.v1 import utils as api_utils
|
||||
from cloudpulse.common import exception
|
||||
from cloudpulse import objects
|
||||
|
||||
|
||||
class CpulsePatchType(types.JsonPatchType):
|
||||
|
||||
@staticmethod
|
||||
def mandatory_attrs():
|
||||
return ['/uuid']
|
||||
|
||||
|
||||
class Cpulse(base.APIBase):
|
||||
"""API representation of a test.
|
||||
|
||||
This class enforces type checking and value constraints, and converts
|
||||
between the internal object model and the API representation of a test.
|
||||
"""
|
||||
id = wtypes.IntegerType(minimum=1)
|
||||
|
||||
uuid = types.uuid
|
||||
"""Unique UUID for this test"""
|
||||
|
||||
name = wtypes.StringType(min_length=1, max_length=255)
|
||||
"""Name of this test"""
|
||||
|
||||
state = wtypes.StringType(min_length=1, max_length=255)
|
||||
"""State of this test"""
|
||||
|
||||
cpulse_create_timeout = wtypes.IntegerType(minimum=0)
|
||||
"""Timeout for creating the test in minutes. Set to 0 for no timeout."""
|
||||
links = wsme.wsattr([link.Link], readonly=True)
|
||||
"""A list containing a self link and associated test links"""
|
||||
|
||||
result = wtypes.StringType(min_length=1, max_length=1024)
|
||||
"""Result of this test"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(Cpulse, self).__init__()
|
||||
|
||||
self.fields = []
|
||||
for field in objects.Cpulse.fields:
|
||||
# Skip fields we do not expose.
|
||||
if not hasattr(self, field):
|
||||
continue
|
||||
self.fields.append(field)
|
||||
setattr(self, field, kwargs.get(field, wtypes.Unset))
|
||||
|
||||
@staticmethod
|
||||
def _convert_with_links(cpulse, url, expand=True):
|
||||
if not expand:
|
||||
cpulse.unset_fields_except(['uuid', 'name', 'state', 'id', 'result'
|
||||
])
|
||||
return cpulse
|
||||
|
||||
@classmethod
|
||||
def convert_with_links(cls, rpc_test, expand=True):
|
||||
test = Cpulse(**rpc_test.as_dict())
|
||||
return cls._convert_with_links(test, pecan.request.host_url, expand)
|
||||
|
||||
@classmethod
|
||||
def sample(cls, expand=True):
|
||||
sample = cls(uuid='27e3153e-d5bf-4b7e-b517-fb518e17f34c',
|
||||
name='example',
|
||||
state="CREATED",
|
||||
result="NotYetRun",
|
||||
created_at=datetime.datetime.utcnow(),
|
||||
updated_at=datetime.datetime.utcnow())
|
||||
return cls._convert_with_links(sample, 'http://localhost:9511', expand)
|
||||
|
||||
|
||||
class CpulseCollection(collection.Collection):
|
||||
"""API representation of a collection of tests."""
|
||||
|
||||
cpulses = [Cpulse]
|
||||
"""A list containing tests objects"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self._type = 'cpulses'
|
||||
|
||||
@staticmethod
|
||||
def convert_with_links(rpc_tests, limit, url=None, expand=False, **kwargs):
|
||||
collection = CpulseCollection()
|
||||
collection.cpulses = [Cpulse.convert_with_links(p, expand)
|
||||
for p in rpc_tests]
|
||||
collection.next = collection.get_next(limit, url=url, **kwargs)
|
||||
return collection
|
||||
|
||||
@classmethod
|
||||
def sample(cls):
|
||||
sample = cls()
|
||||
sample.cpulse = [Cpulse.sample(expand=False)]
|
||||
return sample
|
||||
|
||||
|
||||
class cpulseController(rest.RestController):
|
||||
"""REST controller for Cpulse.."""
|
||||
def __init__(self):
|
||||
super(cpulseController, self).__init__()
|
||||
|
||||
_custom_actions = {'detail': ['GET']}
|
||||
|
||||
def _get_tests_collection(self, marker, limit,
|
||||
sort_key, sort_dir, expand=False,
|
||||
resource_url=None):
|
||||
|
||||
limit = api_utils.validate_limit(limit)
|
||||
sort_dir = api_utils.validate_sort_dir(sort_dir)
|
||||
|
||||
marker_obj = None
|
||||
if marker:
|
||||
marker_obj = objects.Cpulse.get_by_uuid(pecan.request.context,
|
||||
marker)
|
||||
|
||||
tests = pecan.request.rpcapi.test_list(pecan.request.context, limit,
|
||||
marker_obj, sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
|
||||
return CpulseCollection.convert_with_links(tests, limit,
|
||||
url=resource_url,
|
||||
expand=expand,
|
||||
sort_key=sort_key,
|
||||
sort_dir=sort_dir)
|
||||
|
||||
@wsme_pecan.wsexpose(CpulseCollection, types.uuid,
|
||||
types.uuid, int, wtypes.text, wtypes.text)
|
||||
def get_all(self, test_uuid=None, marker=None, limit=None,
|
||||
sort_key='id', sort_dir='asc'):
|
||||
"""Retrieve a list of tests.
|
||||
|
||||
:param marker: pagination marker for large data sets.
|
||||
:param limit: maximum number of resources to return in a single result.
|
||||
:param sort_key: column to sort results by. Default: id.
|
||||
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
||||
"""
|
||||
return self._get_tests_collection(marker, limit, sort_key,
|
||||
sort_dir)
|
||||
|
||||
@wsme_pecan.wsexpose(CpulseCollection, types.uuid,
|
||||
types.uuid, int, wtypes.text, wtypes.text)
|
||||
def detail(self, test_uuid=None, marker=None, limit=None,
|
||||
sort_key='id', sort_dir='asc'):
|
||||
"""Retrieve a list of tests with detail.
|
||||
|
||||
:param test_uuid: UUID of a test, to get only tests for that test.
|
||||
:param marker: pagination marker for large data sets.
|
||||
:param limit: maximum number of resources to return in a single result.
|
||||
:param sort_key: column to sort results by. Default: id.
|
||||
:param sort_dir: direction to sort. "asc" or "desc". Default: asc.
|
||||
"""
|
||||
# NOTE(lucasagomes): /detail should only work agaist collections
|
||||
parent = pecan.request.path.split('/')[:-1][-1]
|
||||
if parent != "tests":
|
||||
raise exception.HTTPNotFound
|
||||
|
||||
expand = True
|
||||
resource_url = '/'.join(['tests', 'detail'])
|
||||
return self._get_tests_collection(marker, limit,
|
||||
sort_key, sort_dir, expand,
|
||||
resource_url)
|
||||
|
||||
@wsme_pecan.wsexpose(Cpulse, types.uuid_or_name)
|
||||
def get_one(self, test_ident):
|
||||
"""Retrieve information about the given test.
|
||||
|
||||
:param test_ident: UUID of a test or logical name of the test.
|
||||
"""
|
||||
|
||||
rpc_test = api_utils.get_rpc_resource('Cpulse', test_ident)
|
||||
|
||||
return Cpulse.convert_with_links(rpc_test)
|
||||
|
||||
@wsme_pecan.wsexpose(Cpulse, body=Cpulse, status_code=201)
|
||||
def post(self, test):
|
||||
"""Create a new test.
|
||||
|
||||
:param test: a test within the request body.
|
||||
"""
|
||||
|
||||
test_dict = test.as_dict()
|
||||
context = pecan.request.context
|
||||
auth_token = context.auth_token_info['token']
|
||||
test_dict['project_id'] = auth_token['project']['id']
|
||||
test_dict['user_id'] = auth_token['user']['id']
|
||||
ncp = objects.Cpulse(context, **test_dict)
|
||||
ncp.cpulse_create_timeout = 0
|
||||
ncp.result = "NotYetRun"
|
||||
res_test = pecan.request.rpcapi.test_create(ncp,
|
||||
ncp.cpulse_create_timeout)
|
||||
|
||||
return Cpulse.convert_with_links(res_test)
|
||||
|
||||
@wsme_pecan.wsexpose(None, types.uuid_or_name, status_code=204)
|
||||
def delete(self, test_ident):
|
||||
"""Delete a test.
|
||||
|
||||
:param test_ident: UUID of a test or logical name of the test.
|
||||
"""
|
||||
|
||||
context = pecan.request.context
|
||||
|
||||
rpc_test = api_utils.get_rpc_resource('Cpulse', test_ident)
|
||||
|
||||
pecan.request.rpcapi.test_delete(context, rpc_test.uuid)
|
181
cloudpulse/api/controllers/v1/types.py
Normal file
181
cloudpulse/api/controllers/v1/types.py
Normal file
@@ -0,0 +1,181 @@
|
||||
# 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.
|
||||
|
||||
from oslo_utils import strutils
|
||||
import wsme
|
||||
from wsme import types as wtypes
|
||||
|
||||
from cloudpulse.common import exception
|
||||
from cloudpulse.common import utils
|
||||
from cloudpulse.openstack.common._i18n import _
|
||||
|
||||
|
||||
class NameType(wtypes.UserType):
|
||||
"""A logical name type."""
|
||||
|
||||
basetype = wtypes.text
|
||||
name = 'name'
|
||||
# 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_name_safe(value):
|
||||
raise exception.InvalidName(name=value)
|
||||
return value
|
||||
|
||||
@staticmethod
|
||||
def frombasetype(value):
|
||||
if value is None:
|
||||
return None
|
||||
return NameType.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'
|
||||
# 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):
|
||||
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(e)
|
||||
|
||||
@staticmethod
|
||||
def frombasetype(value):
|
||||
if value is None:
|
||||
return None
|
||||
return BooleanType.validate(value)
|
||||
|
||||
|
||||
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.
|
||||
|
||||
"""
|
||||
basetype = wtypes.text
|
||||
|
||||
def __init__(self, *types):
|
||||
self.types = types
|
||||
|
||||
def __str__(self):
|
||||
return ' | '.join(map(str, self.types))
|
||||
|
||||
def validate(self, value):
|
||||
for t in self.types:
|
||||
try:
|
||||
return wtypes.validate_value(t, value)
|
||||
except (exception.InvalidUUID, ValueError):
|
||||
pass
|
||||
else:
|
||||
raise ValueError(_("Expected '%(type)s', got '%(value)s'")
|
||||
% {'type': self.types, 'value': type(value)})
|
||||
|
||||
|
||||
uuid = UuidType()
|
||||
name = NameType()
|
||||
uuid_or_name = MultiType(UuidType, NameType)
|
||||
boolean = BooleanType()
|
||||
|
||||
|
||||
class JsonPatchType(wtypes.Base):
|
||||
"""A complex type that represents a single json-patch operation."""
|
||||
|
||||
path = wtypes.wsattr(wtypes.StringType(pattern='^(/[\w-]+)+$'),
|
||||
mandatory=True)
|
||||
op = wtypes.wsattr(wtypes.Enum(str, 'add', 'replace', 'remove'),
|
||||
mandatory=True)
|
||||
value = MultiType(wtypes.text, int)
|
||||
|
||||
@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 != 'remove':
|
||||
if not patch.value:
|
||||
msg = _("'add' and 'replace' operations needs value")
|
||||
raise wsme.exc.ClientSideError(msg)
|
||||
|
||||
ret = {'path': patch.path, 'op': patch.op}
|
||||
if patch.value:
|
||||
ret['value'] = patch.value
|
||||
return ret
|
70
cloudpulse/api/controllers/v1/utils.py
Normal file
70
cloudpulse/api/controllers/v1/utils.py
Normal file
@@ -0,0 +1,70 @@
|
||||
# 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.
|
||||
|
||||
import jsonpatch
|
||||
from oslo_config import cfg
|
||||
import pecan
|
||||
import wsme
|
||||
|
||||
from cloudpulse.common import exception
|
||||
from cloudpulse.common import utils
|
||||
from cloudpulse import objects
|
||||
from cloudpulse.openstack.common._i18n import _
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
JSONPATCH_EXCEPTIONS = (jsonpatch.JsonPatchException,
|
||||
jsonpatch.JsonPointerException,
|
||||
KeyError)
|
||||
|
||||
|
||||
def validate_limit(limit):
|
||||
if limit is not None and limit <= 0:
|
||||
raise wsme.exc.ClientSideError(_("Limit must be positive"))
|
||||
|
||||
return min(CONF.api.max_limit, limit) or CONF.api.max_limit
|
||||
|
||||
|
||||
def validate_sort_dir(sort_dir):
|
||||
if sort_dir not in ['asc', 'desc']:
|
||||
raise wsme.exc.ClientSideError(_("Invalid sort direction: %s. "
|
||||
"Acceptable values are "
|
||||
"'asc' or 'desc'") % sort_dir)
|
||||
return sort_dir
|
||||
|
||||
|
||||
def apply_jsonpatch(doc, patch):
|
||||
return jsonpatch.apply_patch(doc, jsonpatch.JsonPatch(patch))
|
||||
|
||||
|
||||
def get_rpc_resource(resource, resource_ident):
|
||||
"""Get the RPC resource from the uuid or logical name.
|
||||
|
||||
:param resource: the resource type.
|
||||
:param resource_ident: the UUID or logical name of the resource.
|
||||
|
||||
:returns: The RPC resource.
|
||||
:raises: InvalidUuidOrName if the name or uuid provided is not valid.
|
||||
"""
|
||||
resource = getattr(objects, resource)
|
||||
|
||||
if utils.is_uuid_like(resource_ident):
|
||||
return resource.get_by_uuid(pecan.request.context, resource_ident)
|
||||
|
||||
if utils.allow_logical_names():
|
||||
return resource.get_by_name(pecan.request.context, resource_ident)
|
||||
|
||||
raise exception.InvalidUuidOrName(name=resource_ident)
|
Reference in New Issue
Block a user