deb-mistral/mistral/utils/rest_utils.py
2017-01-11 11:34:52 +00:00

227 lines
7.4 KiB
Python

# Copyright 2014 - Mirantis, Inc.
# Copyright 2016 - Brocade Communications Systems, Inc.
#
# 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 functools
import json
from oslo_log import log as logging
import pecan
import six
import webob
from wsme import exc as wsme_exc
from mistral import context as auth_ctx
from mistral.db.v2.sqlalchemy import api as db_api
from mistral import exceptions as exc
LOG = logging.getLogger(__name__)
def wrap_wsme_controller_exception(func):
"""Decorator for controllers method.
This decorator wraps controllers method to manage wsme exceptions:
In case of expected error it aborts the request with specific status code.
"""
@functools.wraps(func)
def wrapped(*args, **kwargs):
try:
return func(*args, **kwargs)
except (exc.MistralException, exc.MistralError) as e:
pecan.response.translatable_error = e
LOG.error('Error during API call: %s' % str(e))
raise wsme_exc.ClientSideError(
msg=six.text_type(e),
status_code=e.http_code
)
return wrapped
def wrap_pecan_controller_exception(func):
"""Decorator for controllers method.
This decorator wraps controllers method to manage pecan exceptions:
In case of expected error it aborts the request with specific status code.
"""
@functools.wraps(func)
def wrapped(*args, **kwargs):
try:
return func(*args, **kwargs)
except (exc.MistralException, exc.MistralError) as e:
LOG.error('Error during API call: %s' % str(e))
return webob.Response(
status=e.http_code,
content_type='application/json',
body=json.dumps(dict(faultstring=six.text_type(e)))
)
return wrapped
def validate_query_params(limit, sort_keys, sort_dirs):
if limit is not None and limit <= 0:
raise wsme_exc.ClientSideError("Limit must be positive.")
if len(sort_keys) < len(sort_dirs):
raise wsme_exc.ClientSideError(
"Length of sort_keys must be equal or greater than sort_dirs."
)
if len(sort_keys) > len(sort_dirs):
sort_dirs.extend(['asc'] * (len(sort_keys) - len(sort_dirs)))
for sort_dir in sort_dirs:
if sort_dir not in ['asc', 'desc']:
raise wsme_exc.ClientSideError(
"Unknown sort direction, must be 'desc' or 'asc'."
)
def validate_fields(fields, object_fields):
"""Check for requested non-existent fields.
Check if the user requested non-existent fields.
:param fields: A list of fields requested by the user.
:param object_fields: A list of fields supported by the object.
"""
if not fields:
return
invalid_fields = set(fields) - set(object_fields)
if invalid_fields:
raise wsme_exc.ClientSideError(
'Field(s) %s are invalid.' % ', '.join(invalid_fields)
)
def filters_to_dict(**kwargs):
"""Return only non-null values
:param kwargs: All possible filters
:type kwargs: dict
:return: Actual filters
:rtype: dict
"""
return {k: v for k, v in kwargs.items() if v is not None}
def get_all(list_cls, cls, get_all_function, get_function,
resource_function=None, marker=None, limit=None,
sort_keys='created_at', sort_dirs='asc', fields='',
all_projects=False, **filters):
"""Return a list of cls.
:param list_cls: Collection class (e.g.: Actions, Workflows, ...).
:param cls: Class (e.g.: Action, Workflow, ...).
:param get_all_function: Request function to get all elements with
filtering (limit, marker, sort_keys, sort_dirs,
fields)
:param get_function: Function used to fetch the marker
:param resource_function: Optional, function used to fetch additional data
:param marker: Optional. Pagination marker for large data sets.
:param limit: Optional. Maximum number of resources to return in a
single result. Default value is None for backward
compatibility.
:param sort_keys: Optional. Columns to sort results by.
Default: created_at.
:param sort_dirs: Optional. Directions to sort corresponding to
sort_keys, "asc" or "desc" can be chosen.
Default: asc.
:param fields: Optional. A specified list of fields of the resource to
be returned. 'id' will be included automatically in
fields if it's provided, since it will be used when
constructing 'next' link.
:param filters: Optional. A specified dictionary of filters to match.
:param all_projects: Optional. Get resources of all projects.
"""
if fields and 'id' not in fields:
fields.insert(0, 'id')
validate_query_params(limit, sort_keys, sort_dirs)
validate_fields(fields, cls.get_fields())
# Admin user can get all tenants resources, no matter they are private or
# public.
insecure = False
if (all_projects or
(auth_ctx.ctx().is_admin and filters.get('project_id', ''))):
insecure = True
marker_obj = None
if marker:
marker_obj = get_function(marker)
list_to_return = []
if resource_function:
with db_api.transaction():
# do not filter fields yet, resource_function needs the ORM object
db_list = get_all_function(
limit=limit,
marker=marker_obj,
sort_keys=sort_keys,
sort_dirs=sort_dirs,
insecure=insecure,
**filters
)
for data in db_list:
obj = resource_function(data)
# filter fields using a loop instead of the ORM
if fields:
data = []
for f in fields:
if hasattr(obj, f):
data.append(getattr(obj, f))
dict_data = dict(zip(fields, data))
else:
dict_data = obj.to_dict()
list_to_return.append(cls.from_dict(dict_data))
else:
db_list = get_all_function(
limit=limit,
marker=marker_obj,
sort_keys=sort_keys,
sort_dirs=sort_dirs,
fields=fields,
insecure=insecure,
**filters
)
for data in db_list:
dict_data = (dict(zip(fields, data)) if fields else
data.to_dict())
list_to_return.append(cls.from_dict(dict_data))
return list_cls.convert_with_links(
list_to_return,
limit,
pecan.request.host_url,
sort_keys=','.join(sort_keys),
sort_dirs=','.join(sort_dirs),
fields=','.join(fields) if fields else '',
**filters
)