# 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 pecan import expose from pecan import rest from oslo_log import log as logging from tricircle.common import constants import tricircle.common.context as t_context import tricircle.common.exceptions as t_exceptions from tricircle.common.i18n import _ from tricircle.common import policy from tricircle.common import utils from tricircle.db import api as db_api LOG = logging.getLogger(__name__) SUPPORTED_FILTERS = ['id', 'top_id', 'bottom_id', 'pod_id', 'project_id', 'resource_type', 'created_at', 'updated_at'] class RoutingController(rest.RestController): def __init__(self): pass @expose(generic=True, template='json') def post(self, **kw): context = t_context.extract_context_from_environ() if not policy.enforce(context, policy.ADMIN_API_ROUTINGS_CREATE): return utils.format_api_error( 403, _("Unauthorized to create resource routing")) if 'routing' not in kw: return utils.format_api_error( 400, _("Request body not found")) routing = kw['routing'] for field in ('top_id', 'bottom_id', 'pod_id', 'project_id', 'resource_type'): value = routing.get(field) if value is None or len(value.strip()) == 0: return utils.format_api_error( 400, _("Field %(field)s can not be empty") % { 'field': field}) # the resource type should be properly provisioned. resource_type = routing.get('resource_type').strip() if not constants.is_valid_resource_type(resource_type): return utils.format_api_error( 400, _('There is no such resource type')) try: top_id = routing.get('top_id').strip() bottom_id = routing.get('bottom_id').strip() pod_id = routing.get('pod_id').strip() project_id = routing.get('project_id').strip() routing = db_api.create_resource_mapping(context, top_id, bottom_id, pod_id, project_id, resource_type) if not routing: return utils.format_api_error( 409, _('Resource routing already exists')) except Exception as e: LOG.exception('Failed to create resource routing: ' '%(exception)s ', {'exception': e}) return utils.format_api_error( 500, _('Failed to create resource routing')) return {'routing': routing} def _get_filters(self, params): """Return a dictionary of query param filters from the request. :param params: the URI params coming from the wsgi layer :return: (flag, filters), flag indicates whether the filters are valid, and the filters denote a list of key-value pairs. """ filters = {} unsupported_filters = {} for filter_name in params: if filter_name in SUPPORTED_FILTERS: # map filter name filters[filter_name] = params.get(filter_name) else: unsupported_filters[filter_name] = params.get(filter_name) if unsupported_filters: return False, unsupported_filters return True, filters @expose(generic=True, template='json') def get_all(self, **kwargs): context = t_context.extract_context_from_environ() if not policy.enforce(context, policy.ADMIN_API_ROUTINGS_LIST): return utils.format_api_error( 403, _('Unauthorized to show all resource routings')) # default value -1 means no pagination, then maximum pagination # limit from configuration will be used. _limit = kwargs.pop('limit', -1) try: limit = int(_limit) limit = utils.get_pagination_limit(limit) except ValueError as e: LOG.exception('Failed to convert pagination limit to an integer: ' '%(exception)s ', {'exception': e}) msg = (_("Limit should be an integer or a valid literal " "for int() rather than '%s'") % _limit) return utils.format_api_error(400, msg) marker = kwargs.pop('marker', None) if marker is not None: try: marker = int(marker) try: # we throw an exception if a marker with # invalid ID is specified. db_api.get_resource_routing(context, marker) except t_exceptions.ResourceNotFound: return utils.format_api_error( 400, _('Marker %s is an invalid ID') % marker) except ValueError as e: LOG.exception('Failed to convert page marker to an integer: ' '%(exception)s ', {'exception': e}) msg = (_("Marker should be an integer or a valid literal " "for int() rather than '%s'") % marker) return utils.format_api_error(400, msg) is_valid_filter, filters = self._get_filters(kwargs) if not is_valid_filter: msg = (_('Unsupported filter type: %(filters)s') % { 'filters': ', '.join( [filter_name for filter_name in filters]) }) return utils.format_api_error(400, msg) if 'id' in filters: try: # resource routing id is an integer. filters['id'] = int(filters['id']) except ValueError as e: LOG.exception('Failed to convert routing id to an integer:' ' %(exception)s ', {'exception': e}) msg = (_("Id should be an integer or a valid literal " "for int() rather than '%s'") % filters['id']) return utils.format_api_error(400, msg) # project ID from client should be equal to the one from # context, since only the project ID in which the user # is authorized will be used as the filter. filters['project_id'] = context.project_id expand_filters = [{'key': filter_name, 'comparator': 'eq', 'value': filters[filter_name]} for filter_name in filters] try: routings = db_api.list_resource_routings(context, expand_filters, limit, marker, sorts=[('id', 'desc')]) links = [] if len(routings) >= limit: marker = routings[-1]['id'] # if we reach the first element, then no elements in next page, # so link to next page won't be provided. if marker != 1: base = constants.ROUTING_PATH link = "%s?limit=%s&marker=%s" % (base, limit, marker) links.append({"rel": "next", "href": link}) result = {} result["routings"] = routings if links: result["routings_links"] = links return result except Exception as e: LOG.exception('Failed to show all resource routings: ' '%(exception)s ', {'exception': e}) return utils.format_api_error( 500, _('Failed to show all resource routings')) @expose(generic=True, template='json') def get_one(self, _id): context = t_context.extract_context_from_environ() if not policy.enforce(context, policy.ADMIN_API_ROUTINGS_SHOW): return utils.format_api_error( 403, _('Unauthorized to show the resource routing')) try: return {'routing': db_api.get_resource_routing(context, _id)} except t_exceptions.ResourceNotFound: return utils.format_api_error( 404, _('Resource routing not found')) @expose(generic=True, template='json') def delete(self, _id): context = t_context.extract_context_from_environ() if not policy.enforce(context, policy.ADMIN_API_ROUTINGS_DELETE): return utils.format_api_error( 403, _('Unauthorized to delete the resource routing')) try: db_api.get_resource_routing(context, _id) except t_exceptions.ResourceNotFound: return utils.format_api_error(404, _('Resource routing not found')) try: db_api.delete_resource_routing(context, _id) pecan.response.status = 200 return pecan.response except Exception as e: LOG.exception('Failed to delete the resource routing: ' '%(exception)s ', {'exception': e}) return utils.format_api_error( 500, _('Failed to delete the resource routing')) @expose(generic=True, template='json') def put(self, _id, **kw): context = t_context.extract_context_from_environ() if not policy.enforce(context, policy.ADMIN_API_ROUTINGS_PUT): return utils.format_api_error( 403, _('Unauthorized to update resource routing')) try: db_api.get_resource_routing(context, _id) except t_exceptions.ResourceNotFound: return utils.format_api_error(404, _('Resource routing not found')) if 'routing' not in kw: return utils.format_api_error( 400, _('Request body not found')) update_dict = kw['routing'] # values to be updated should not be empty for field in update_dict: value = update_dict.get(field) if value is None or len(value.strip()) == 0: return utils.format_api_error( 400, _("Field %(field)s can not be empty") % { 'field': field}) # the resource type should be properly provisioned. if 'resource_type' in update_dict: if not constants.is_valid_resource_type( update_dict['resource_type']): return utils.format_api_error( 400, _('There is no such resource type')) # the pod with new pod_id should exist in pod table if 'pod_id' in update_dict: new_pod_id = update_dict.get('pod_id') try: # find the pod through the pod_id and verify whether it exists db_api.get_pod(context, new_pod_id) except t_exceptions.ResourceNotFound: return utils.format_api_error( 400, _("The pod %(new_pod_id)s doesn't" " exist") % {'new_pod_id': new_pod_id}) except Exception as e: LOG.exception('Failed to update resource routing: ' '%(exception)s ', {'exception': e}) return utils.format_api_error( 500, _('Failed to update resource routing')) try: routing_updated = db_api.update_resource_routing( context, _id, update_dict) return {'routing': routing_updated} except Exception as e: LOG.exception('Failed to update resource routing: ' '%(exception)s ', {'exception': e}) return utils.format_api_error( 500, _('Failed to update resource routing'))