Example #1
0
def list_for_resource_provider(req):
    """List allocations associated with a resource provider."""
    # TODO(cdent): On a shared resource provider (for example a
    # giant disk farm) this list could get very long. At the moment
    # we have no facility for limiting the output. Given that we are
    # using a dict of dicts for the output we are potentially limiting
    # ourselves in terms of sorting and filtering.
    context = req.environ['placement.context']
    uuid = util.wsgi_path_item(req.environ, 'uuid')

    # confirm existence of resource provider so we get a reasonable
    # 404 instead of empty list
    try:
        resource_provider = objects.ResourceProvider.get_by_uuid(context, uuid)
    except exception.NotFound as exc:
        raise webob.exc.HTTPNotFound(
            _("Resource provider '%(rp_uuid)s' not found: %(error)s") % {
                'rp_uuid': uuid,
                'error': exc
            })

    allocations = objects.AllocationList.get_all_by_resource_provider_uuid(
        context, uuid)

    allocations_json = jsonutils.dumps(
        _serialize_allocations_for_resource_provider(allocations,
                                                     resource_provider))

    req.response.status = 200
    req.response.body = encodeutils.to_utf8(allocations_json)
    req.response.content_type = 'application/json'
    return req.response
Example #2
0
def normalize_traits_qs_param(val, allow_forbidden=False):
    """Parse a traits query string parameter value.

    Note that this method doesn't know or care about the query parameter key,
    which may currently be of the form `required`, `required123`, etc., but
    which may someday also include `preferred`, etc.

    This method currently does no format validation of trait strings, other
    than to ensure they're not zero-length.

    :param val: A traits query parameter value: a comma-separated string of
                trait names.
    :param allow_forbidden: If True, accept forbidden traits (that is, traits
                            prefixed by '!') as a valid form when notifying
                            the caller that the provided value is not properly
                            formed.
    :return: A set of trait names.
    :raises `webob.exc.HTTPBadRequest` if the val parameter is not in the
            expected format.
    """
    ret = set(substr.strip() for substr in val.split(','))
    expected_form = 'HW_CPU_X86_VMX,CUSTOM_MAGIC'
    if allow_forbidden:
        expected_form = 'HW_CPU_X86_VMX,!CUSTOM_MAGIC'
    if not all(trait and valid_trait(trait, allow_forbidden) for trait in ret):
        msg = _("Invalid query string parameters: Expected 'required' "
                "parameter value of the form: %(form)s. "
                "Got: %(val)s") % {
                    'form': expected_form,
                    'val': val
                }
        raise webob.exc.HTTPBadRequest(msg)
    return ret
Example #3
0
def _extract_allocations(body, schema):
    """Extract allocation data from a JSON body."""
    try:
        data = jsonutils.loads(body)
    except ValueError as exc:
        raise webob.exc.HTTPBadRequest(
            _('Malformed JSON: %(error)s') % {'error': exc},
            json_formatter=util.json_error_formatter)
    try:
        jsonschema.validate(data,
                            schema,
                            format_checker=jsonschema.FormatChecker())
    except jsonschema.ValidationError as exc:
        raise webob.exc.HTTPBadRequest(
            _('JSON does not validate: %(error)s') % {'error': exc},
            json_formatter=util.json_error_formatter)
    return data
Example #4
0
def extract_json(body, schema):
    """Extract JSON from a body and validate with the provided schema."""
    try:
        data = jsonutils.loads(body)
    except ValueError as exc:
        raise webob.exc.HTTPBadRequest(_('Malformed JSON: %(error)s') %
                                       {'error': exc},
                                       json_formatter=json_error_formatter)
    try:
        jsonschema.validate(data,
                            schema,
                            format_checker=jsonschema.FormatChecker())
    except jsonschema.ValidationError as exc:
        raise webob.exc.HTTPBadRequest(_('JSON does not validate: %(error)s') %
                                       {'error': exc},
                                       json_formatter=json_error_formatter)
    return data
Example #5
0
def update_resource_provider(req):
    """PUT to update a single resource provider.

    On success return a 200 response with a representation of the updated
    resource provider.
    """
    uuid = util.wsgi_path_item(req.environ, 'uuid')
    context = req.environ['placement.context']
    context.can(policies.UPDATE)
    want_version = req.environ[microversion.MICROVERSION_ENVIRON]

    # The containing application will catch a not found here.
    resource_provider = rp_obj.ResourceProvider.get_by_uuid(
        context, uuid)

    schema = rp_schema.PUT_RESOURCE_PROVIDER_SCHEMA
    if want_version.matches((1, 14)):
        schema = rp_schema.PUT_RP_SCHEMA_V1_14

    data = util.extract_json(req.body, schema)

    for field in rp_obj.ResourceProvider.SETTABLE_FIELDS:
        if field in data:
            setattr(resource_provider, field, data[field])

    try:
        resource_provider.save()
    except db_exc.DBDuplicateEntry as exc:
        raise webob.exc.HTTPConflict(
            _('Conflicting resource provider %(name)s already exists.') %
            {'name': data['name']},
            comment=errors.DUPLICATE_NAME)
    except exception.ObjectActionError as exc:
        raise webob.exc.HTTPBadRequest(
            _('Unable to save resource provider %(rp_uuid)s: %(error)s') %
            {'rp_uuid': uuid, 'error': exc})

    response = req.response
    response.status = 200
    response.body = encodeutils.to_utf8(jsonutils.dumps(
        _serialize_provider(req.environ, resource_provider, want_version)))
    response.content_type = 'application/json'
    if want_version.matches((1, 15)):
        response.last_modified = resource_provider.updated_at
        response.cache_control = 'no-cache'
    return response
Example #6
0
def set_allocations(req):
    context = req.environ['placement.context']
    context.can(policies.ALLOC_MANAGE)
    want_version = req.environ[microversion.MICROVERSION_ENVIRON]
    want_schema = schema.POST_ALLOCATIONS_V1_13
    if want_version.matches((1, 28)):
        want_schema = schema.POST_ALLOCATIONS_V1_28
    data = util.extract_json(req.body, want_schema)

    consumers, new_consumers_created = inspect_consumers(
        context, data, want_version)
    # Create a sequence of allocation objects to be used in one
    # AllocationList.replace_all() call, which will mean all the changes
    # happen within a single transaction and with resource provider
    # and consumer generations (if applicable) check all in one go.
    allocations = create_allocation_list(context, data, consumers)

    def _create_allocations(alloc_list):
        try:
            alloc_list.replace_all()
            LOG.debug("Successfully wrote allocations %s", alloc_list)
        except Exception:
            delete_consumers(new_consumers_created)
            raise

    try:
        _create_allocations(allocations)
    except exception.NotFound as exc:
        raise webob.exc.HTTPBadRequest(
            _("Unable to allocate inventory %(error)s") % {'error': exc})
    except exception.InvalidInventory as exc:
        # InvalidInventory is a parent for several exceptions that
        # indicate either that Inventory is not present, or that
        # capacity limits have been exceeded.
        raise webob.exc.HTTPConflict(
            _('Unable to allocate inventory: %(error)s') % {'error': exc})
    except exception.ConcurrentUpdateDetected as exc:
        raise webob.exc.HTTPConflict(
            _('Inventory and/or allocations changed while attempting to '
              'allocate: %(error)s') % {'error': exc},
              comment=errors.CONCURRENT_UPDATE)

    req.response.status = 204
    req.response.content_type = None
    return req.response
Example #7
0
 def decorated_function(req):
     if req.accept:
         best_matches = req.accept.acceptable_offers(types)
         if not best_matches:
             type_string = ', '.join(types)
             raise webob.exc.HTTPNotAcceptable(
                 _('Only %(type)s is provided') % {'type': type_string},
                 json_formatter=json_error_formatter)
     return f(req)
Example #8
0
def validate_query_params(req, schema):
    try:
        # NOTE(Kevin_Zheng): The webob package throws UnicodeError when
        # param cannot be decoded. Catch this and raise HTTP 400.
        jsonschema.validate(dict(req.GET),
                            schema,
                            format_checker=jsonschema.FormatChecker())
    except (jsonschema.ValidationError, UnicodeDecodeError) as exc:
        raise webob.exc.HTTPBadRequest(
            _('Invalid query string parameters: %(exc)s') % {'exc': exc})
Example #9
0
 def __call__(self, environ, start_response):
     # All requests but '/' require admin.
     if environ['PATH_INFO'] != '/':
         context = environ['placement.context']
         # TODO(cdent): Using is_admin everywhere (except /) is
         # insufficiently flexible for future use case but is
         # convenient for initial exploration.
         if not policy.placement_authorize(context, 'placement'):
             raise webob.exc.HTTPForbidden(
                 _('admin required'),
                 json_formatter=util.json_error_formatter)
     # Check that an incoming write-oriented request method has
     # the required content-type header. If not raise a 400. If
     # this doesn't happen here then webob.dec.wsgify (elsewhere
     # in the stack) will raise an uncaught KeyError. Since that
     # is such a generic exception we cannot merely catch it
     # here, we need to avoid it ever happening.
     # TODO(cdent): Move this and the auth checking above into
     # middleware. It shouldn't be here. This is for dispatch not
     # validation or authorization.
     request_method = environ['REQUEST_METHOD'].upper()
     if request_method in ('POST', 'PUT', 'PATCH'):
         if 'CONTENT_TYPE' not in environ:
             raise webob.exc.HTTPBadRequest(
                 _('content-type header required'),
                 json_formatter=util.json_error_formatter)
     try:
         return dispatch(environ, start_response, self._map)
     # Trap the NotFound exceptions raised by the objects used
     # with the API and transform them into webob.exc.HTTPNotFound.
     except exception.NotFound as exc:
         raise webob.exc.HTTPNotFound(
             exc, json_formatter=util.json_error_formatter)
     # Trap the HTTPNotFound that can be raised by dispatch()
     # when no route is found. The exception is passed through to
     # the FaultWrap middleware without causing an alarming log
     # message.
     except webob.exc.HTTPNotFound:
         raise
     except Exception as exc:
         LOG.exception(_LE("Uncaught exception"))
         raise
Example #10
0
def setup_commands(config):
    # This is a separate method because it facilitates unit testing.
    # Use an additional SubCommandOpt and parser for each new sub command.
    add_db_cmd_parsers = functools.partial(add_db_command_parsers,
                                           config=config)
    command_opt = cfg.SubCommandOpt('db',
                                    dest='command',
                                    title='Command',
                                    help=_('Available DB commands'),
                                    handler=add_db_cmd_parsers)
    return [command_opt]
Example #11
0
def delete_resource_class(req):
    """DELETE to destroy a single resource class.

    On success return a 204 and an empty body.
    """
    name = util.wsgi_path_item(req.environ, 'name')
    context = req.environ['placement.context']
    context.can(policies.DELETE)
    # The containing application will catch a not found here.
    rc = rp_obj.ResourceClass.get_by_name(context, name)
    try:
        rc.destroy()
    except exception.ResourceClassCannotDeleteStandard as exc:
        raise webob.exc.HTTPBadRequest(
            _('Error in delete resource class: %(error)s') % {'error': exc})
    except exception.ResourceClassInUse as exc:
        raise webob.exc.HTTPConflict(
            _('Error in delete resource class: %(error)s') % {'error': exc})
    req.response.status = 204
    req.response.content_type = None
    return req.response
Example #12
0
def create_inventory(req):
    """POST to create one inventory.

    On success return a 201 response, a location header pointing
    to the newly created inventory and an application/json representation
    of the inventory.
    """
    context = req.environ['placement.context']
    context.can(policies.CREATE)
    uuid = util.wsgi_path_item(req.environ, 'uuid')
    resource_provider = rp_obj.ResourceProvider.get_by_uuid(
        context, uuid)
    data = _extract_inventory(req.body, schema.POST_INVENTORY_SCHEMA)
    resource_class = data.pop('resource_class')

    inventory = make_inventory_object(resource_provider,
                                      resource_class,
                                      **data)

    try:
        _validate_inventory_capacity(
            req.environ[microversion.MICROVERSION_ENVIRON], inventory)
        resource_provider.add_inventory(inventory)
    except (exception.ConcurrentUpdateDetected,
            db_exc.DBDuplicateEntry) as exc:
        raise webob.exc.HTTPConflict(
            _('Update conflict: %(error)s') % {'error': exc},
            comment=errors.CONCURRENT_UPDATE)
    except (exception.InvalidInventoryCapacity,
            exception.NotFound) as exc:
        raise webob.exc.HTTPBadRequest(
            _('Unable to create inventory for resource provider '
              '%(rp_uuid)s: %(error)s') % {'rp_uuid': resource_provider.uuid,
                                           'error': exc})

    response = req.response
    response.location = util.inventory_url(
        req.environ, resource_provider, resource_class)
    return _send_inventory(req, resource_provider, inventory,
                           status=201)
Example #13
0
 def decorated_function(req):
     if req.content_type != content_type:
         # webob's unset content_type is the empty string so
         # set it the error message content to 'None' to make
         # a useful message in that case.
         raise webob.exc.HTTPUnsupportedMediaType(
             _('The media type %(bad_type)s is not supported, '
               'use %(good_type)s') %
             {'bad_type': req.content_type or 'None',
              'good_type': content_type},
             json_formatter=json_error_formatter)
     else:
         return f(req)
Example #14
0
def delete_resource_provider(req):
    """DELETE to destroy a single resource provider.

    On success return a 204 and an empty body.
    """
    uuid = util.wsgi_path_item(req.environ, 'uuid')
    context = req.environ['placement.context']
    # The containing application will catch a not found here.
    try:
        resource_provider = objects.ResourceProvider.get_by_uuid(
            context, uuid)
        resource_provider.destroy()
    except exception.ResourceProviderInUse as exc:
        raise webob.exc.HTTPConflict(
            _('Unable to delete resource provider %(rp_uuid)s: %(error)s') %
            {'rp_uuid': uuid, 'error': exc})
    except exception.NotFound as exc:
        raise webob.exc.HTTPNotFound(
            _("No resource provider with uuid %s found for delete") % uuid)
    req.response.status = 204
    req.response.content_type = None
    return req.response
Example #15
0
def _set_aggregates(resource_provider, aggregate_uuids,
                    increment_generation=False):
    """Set aggregates for the resource provider.

    If increment generation is true, the resource provider generation
    will be incremented if possible. If that fails (because something
    else incremented the generation in another thread), a
    ConcurrentUpdateDetected will be raised.
    """
    # NOTE(cdent): It's not clear what the DBDuplicateEntry handling
    # is doing here, set_aggregates already handles that, but I'm leaving
    # it here because it was already there.
    try:
        resource_provider.set_aggregates(
            aggregate_uuids, increment_generation=increment_generation)
    except exception.ConcurrentUpdateDetected as exc:
        raise webob.exc.HTTPConflict(
            _('Update conflict: %(error)s') % {'error': exc},
            comment=errors.CONCURRENT_UPDATE)
    except db_exc.DBDuplicateEntry as exc:
        raise webob.exc.HTTPConflict(
            _('Update conflict: %(error)s') % {'error': exc})
Example #16
0
def create_inventory(req):
    """POST to create one inventory.

    On success return a 201 response, a location header pointing
    to the newly created inventory and an application/json representation
    of the inventory.
    """
    context = req.environ['placement.context']
    uuid = util.wsgi_path_item(req.environ, 'uuid')
    resource_provider = objects.ResourceProvider.get_by_uuid(
        context, uuid)
    data = _extract_inventory(req.body, POST_INVENTORY_SCHEMA)
    resource_class = data.pop('resource_class')

    inventory = _make_inventory_object(resource_provider,
                                       resource_class,
                                       **data)

    try:
        resource_provider.add_inventory(inventory)
    except (exception.ConcurrentUpdateDetected,
            db_exc.DBDuplicateEntry) as exc:
        raise webob.exc.HTTPConflict(
            _('Update conflict: %(error)s') % {'error': exc},
            json_formatter=util.json_error_formatter)
    except (exception.InvalidInventoryCapacity,
            exception.NotFound) as exc:
        raise webob.exc.HTTPBadRequest(
            _('Unable to create inventory for resource provider '
              '%(rp_uuid)s: %(error)s') % {'rp_uuid': resource_provider.uuid,
                                           'error': exc},
            json_formatter=util.json_error_formatter)

    response = req.response
    response.location = util.inventory_url(
        req.environ, resource_provider, resource_class)
    return _send_inventory(response, resource_provider, inventory,
                           status=201)
Example #17
0
def update_traits_for_resource_provider(req):
    context = req.environ['placement.context']
    context.can(policies.RP_TRAIT_UPDATE)
    want_version = req.environ[microversion.MICROVERSION_ENVIRON]
    uuid = util.wsgi_path_item(req.environ, 'uuid')
    data = util.extract_json(req.body, schema.SET_TRAITS_FOR_RP_SCHEMA)
    rp_gen = data['resource_provider_generation']
    traits = data['traits']
    resource_provider = rp_obj.ResourceProvider.get_by_uuid(
        context, uuid)

    if resource_provider.generation != rp_gen:
        raise webob.exc.HTTPConflict(
            _("Resource provider's generation already changed. Please update "
              "the generation and try again."),
            json_formatter=util.json_error_formatter,
            comment=errors.CONCURRENT_UPDATE)

    trait_objs = rp_obj.TraitList.get_all(
        context, filters={'name_in': traits})
    traits_name = set([obj.name for obj in trait_objs])
    non_existed_trait = set(traits) - set(traits_name)
    if non_existed_trait:
        raise webob.exc.HTTPBadRequest(
            _("No such trait %s") % ', '.join(non_existed_trait))

    resource_provider.set_traits(trait_objs)

    response_body, last_modified = _serialize_traits(trait_objs, want_version)
    response_body[
        'resource_provider_generation'] = resource_provider.generation
    if want_version.matches((1, 15)):
        req.response.last_modified = last_modified
        req.response.cache_control = 'no-cache'
    req.response.status = 200
    req.response.body = encodeutils.to_utf8(jsonutils.dumps(response_body))
    req.response.content_type = 'application/json'
    return req.response
Example #18
0
def upgrade(migrate_engine):
    meta = MetaData()
    meta.bind = migrate_engine

    flavors = Table('flavors', meta, autoload=True)
    count = select([func.count()]).select_from(flavors).scalar()
    if count == 0:
        # NOTE(danms): We need to be careful here if this is a new
        # installation, which can't possibly have any mappings. Check
        # to see if any flavors are defined to determine if we are
        # upgrading an existing system. If not, then don't obsess over
        # the lack of mappings
        return

    cell_mappings = Table('cell_mappings', meta, autoload=True)
    count = select([func.count()]).select_from(cell_mappings).scalar()
    # Two mappings are required at a minimum, cell0 and your first cell
    if count < 2:
        msg = _('Cell mappings are not created, but required for Ocata. '
                'Please run nova-manage cell_v2 simple_cell_setup before '
                'continuing.')
        raise exception.ValidationError(detail=msg)

    count = select([func.count()]).select_from(cell_mappings).where(
        cell_mappings.c.uuid == objects.CellMapping.CELL0_UUID).scalar()
    if count != 1:
        msg = _('A mapping for Cell0 was not found, but is required for '
                'Ocata. Please run nova-manage cell_v2 simple_cell_setup '
                'before continuing.')
        raise exception.ValidationError(detail=msg)

    host_mappings = Table('host_mappings', meta, autoload=True)
    count = select([func.count()]).select_from(host_mappings).scalar()
    if count == 0:
        LOG.warning('No host mappings were found, but are required for Ocata. '
                    'Please run nova-manage cell_v2 simple_cell_setup before '
                    'continuing.')
Example #19
0
def add_db_command_parsers(subparsers, config):
    command_object = DbCommands(config)

    # If we set False here, we avoid having an exit during the parse
    # args part of CONF processing and we can thus print out meaningful
    # help text.
    subparsers.required = False
    parser = subparsers.add_parser('db')
    # Avoid https://bugs.python.org/issue9351 with cpython < 2.7.9
    if not six.PY2:
        parser.set_defaults(func=parser.print_help)
    db_parser = parser.add_subparsers(description='database commands')

    help = _('Sync the datatabse to the current version.')
    sync_parser = db_parser.add_parser('sync', help=help, description=help)
    sync_parser.set_defaults(func=command_object.db_sync)

    help = _('Report the current database version.')
    version_parser = db_parser.add_parser('version',
                                          help=help,
                                          description=help)
    version_parser.set_defaults(func=command_object.db_version)

    help = _('Stamp the revision table with the given version.')
    stamp_parser = db_parser.add_parser('stamp', help=help, description=help)
    stamp_parser.add_argument('version', help=_('the version to stamp'))
    stamp_parser.set_defaults(func=command_object.db_stamp)

    help = _('Run the online data migrations.')
    online_dm_parser = db_parser.add_parser('online_data_migrations',
                                            help=help,
                                            description=help)
    online_dm_parser.add_argument('--max-count',
                                  metavar='<number>',
                                  help='Maximum number of objects to consider')
    online_dm_parser.set_defaults(
        func=command_object.db_online_data_migrations)
Example #20
0
 def __call__(self, environ, start_response):
     # Check that an incoming request with a content-length header
     # that is an integer > 0 and not empty, also has a content-type
     # header that is not empty. If not raise a 400.
     clen = environ.get('CONTENT_LENGTH')
     try:
         if clen and (int(clen) > 0) and not environ.get('CONTENT_TYPE'):
             raise webob.exc.HTTPBadRequest(
                 _('content-type header required when content-length > 0'),
                 json_formatter=util.json_error_formatter)
     except ValueError as exc:
         raise webob.exc.HTTPBadRequest(
             _('content-length header must be an integer'),
             json_formatter=util.json_error_formatter)
     try:
         return dispatch(environ, start_response, self._map)
     # Trap the NotFound exceptions raised by the objects used
     # with the API and transform them into webob.exc.HTTPNotFound.
     except exception.NotFound as exc:
         raise webob.exc.HTTPNotFound(
             exc, json_formatter=util.json_error_formatter)
     except exception.PolicyNotAuthorized as exc:
         raise webob.exc.HTTPForbidden(
             exc.format_message(), json_formatter=util.json_error_formatter)
Example #21
0
    def create(self):
        if self.id is not None:
            raise exception.ObjectActionError(action='create',
                                              reason='already created')
        if not self.name:
            raise exception.ObjectActionError(action='create',
                                              reason='name is required')
        if self.name in orc.STANDARDS:
            raise exception.ResourceClassExists(resource_class=self.name)

        if not self.name.startswith(orc.CUSTOM_NAMESPACE):
            raise exception.ObjectActionError(
                action='create',
                reason='name must start with ' + orc.CUSTOM_NAMESPACE)
        updates = {}
        for field in ['name', 'updated_at', 'created_at']:
            value = getattr(self, field, None)
            if value:
                updates[field] = value

        # There is the possibility of a race when adding resource classes, as
        # the ID is generated locally. This loop catches that exception, and
        # retries until either it succeeds, or a different exception is
        # encountered.
        retries = self.RESOURCE_CREATE_RETRY_COUNT
        while retries:
            retries -= 1
            try:
                rc = self._create_in_db(self._context, updates)
                self._from_db_object(self._context, self, rc)
                break
            except db_exc.DBDuplicateEntry as e:
                if 'id' in e.columns:
                    # Race condition for ID creation; try again
                    continue
                # The duplication is on the other unique column, 'name'. So do
                # not retry; raise the exception immediately.
                raise exception.ResourceClassExists(resource_class=self.name)
        else:
            # We have no idea how common it will be in practice for the retry
            # limit to be exceeded. We set it high in the hope that we never
            # hit this point, but added this log message so we know that this
            # specific situation occurred.
            LOG.warning("Exceeded retry limit on ID generation while "
                        "creating ResourceClass %(name)s",
                        {'name': self.name})
            msg = _("creating resource class %s") % self.name
            raise exception.MaxDBRetriesExceeded(action=msg)
Example #22
0
def normalize_in_tree_qs_params(value):
    """Parse a in_tree query string parameter value.

    :param value: in_tree query parameter: A UUID of a resource provider.
    :return: A UUID of a resource provider.
    :raises `webob.exc.HTTPBadRequest` if the val parameter is not in the
            expected format.
    """
    ret = value.strip()
    if not uuidutils.is_uuid_like(ret):
        msg = _("Invalid query string parameters: Expected 'in_tree' "
                "parameter to be a format of uuid. "
                "Got: %(val)s") % {
                    'val': value
                }
        raise webob.exc.HTTPBadRequest(msg)
    return ret
Example #23
0
def _normalize_traits_qs_param(qs):
    try:
        op, value = qs.split(':', 1)
    except ValueError:
        msg = _('Badly formatted name parameter. Expected name query string '
                'parameter in form: '
                '?name=[in|startswith]:[name1,name2|prefix]. Got: "%s"')
        msg = msg % qs
        raise webob.exc.HTTPBadRequest(msg)

    filters = {}
    if op == 'in':
        filters['name_in'] = value.split(',')
    elif op == 'startswith':
        filters['prefix'] = value

    return filters
Example #24
0
def make_inventory_object(resource_provider, resource_class, **data):
    """Single place to catch malformed Inventories."""
    # TODO(cdent): Some of the validation checks that are done here
    # could be done via JSONschema (using, for example, "minimum":
    # 0) for non-negative integers. It's not clear if that is
    # duplication or decoupling so leaving it as this for now.
    try:
        inventory = inv_obj.Inventory(
            resource_provider=resource_provider,
            resource_class=resource_class, **data)
    except (ValueError, TypeError) as exc:
        raise webob.exc.HTTPBadRequest(
            _('Bad inventory %(class)s for resource provider '
              '%(rp_uuid)s: %(error)s') % {'class': resource_class,
                                           'rp_uuid': resource_provider.uuid,
                                           'error': exc})
    return inventory
Example #25
0
 def decorated_function(req):
     if req.content_type != content_type:
         # webob's unset content_type is the empty string so
         # set it the error message content to 'None' to make
         # a useful message in that case. This also avoids a
         # KeyError raised when webob.exc eagerly fills in a
         # Template for output we will never use.
         if not req.content_type:
             req.content_type = 'None'
         raise webob.exc.HTTPUnsupportedMediaType(
             _('The media type %(bad_type)s is not supported, '
               'use %(good_type)s') % {
                   'bad_type': req.content_type,
                   'good_type': content_type
               },
             json_formatter=json_error_formatter)
     else:
         return f(req)
Example #26
0
def handle_405(environ, start_response):
    """Return a 405 response when method is not allowed.

    If _methods are in routing_args, send an allow header listing
    the methods that are possible on the provided URL.
    """
    _methods = util.wsgi_path_item(environ, '_methods')
    headers = {}
    if _methods:
        # Ensure allow header is a python 2 or 3 native string (thus
        # not unicode in python 2 but stay a string in python 3)
        # In the process done by Routes to save the allowed methods
        # to its routing table they become unicode in py2.
        headers['allow'] = str(_methods)
    raise webob.exc.HTTPMethodNotAllowed(
        _('The method specified is not allowed for this resource.'),
        headers=headers,
        json_formatter=util.json_error_formatter)
Example #27
0
 def _fix_forbidden(by_suffix):
     conflicting_traits = []
     for suff, group in by_suffix.items():
         forbidden = [
             trait for trait in group.required_traits
             if trait.startswith('!')
         ]
         group.required_traits = group.required_traits - set(forbidden)
         group.forbidden_traits = set(
             [trait.lstrip('!') for trait in forbidden])
         conflicts = group.forbidden_traits & group.required_traits
         if conflicts:
             conflicting_traits.append('required%s: (%s)' %
                                       (suff, ', '.join(conflicts)))
     if conflicting_traits:
         msg = _('Conflicting required and forbidden traits found in the '
                 'following traits keys: %s')
         raise webob.exc.HTTPBadRequest(msg % ', '.join(conflicting_traits))
Example #28
0
def get_inventories(req):
    """GET a list of inventories.

    On success return a 200 with an application/json body representing
    a collection of inventories.
    """
    context = req.environ['placement.context']
    context.can(policies.LIST)
    uuid = util.wsgi_path_item(req.environ, 'uuid')
    try:
        rp = rp_obj.ResourceProvider.get_by_uuid(context, uuid)
    except exception.NotFound as exc:
        raise webob.exc.HTTPNotFound(
            _("No resource provider with uuid %(uuid)s found : %(error)s") %
            {'uuid': uuid, 'error': exc})

    inv_list = inv_obj.get_all_by_resource_provider(context, rp)

    return _send_inventories(req, rp, inv_list)
Example #29
0
def list_usages(req):
    """GET a dictionary of resource provider usage by resource class.

    If the resource provider does not exist return a 404.

    On success return a 200 with an application/json representation of
    the usage dictionary.
    """
    context = req.environ['placement.context']
    context.can(policies.PROVIDER_USAGES)
    uuid = util.wsgi_path_item(req.environ, 'uuid')
    want_version = req.environ[microversion.MICROVERSION_ENVIRON]

    # Resource provider object needed for two things: If it is
    # NotFound we'll get a 404 here, which needs to happen because
    # get_all_by_resource_provider_uuid can return an empty list.
    # It is also needed for the generation, used in the outgoing
    # representation.
    try:
        resource_provider = rp_obj.ResourceProvider.get_by_uuid(context, uuid)
    except exception.NotFound as exc:
        raise webob.exc.HTTPNotFound(
            _("No resource provider with uuid %(uuid)s found: %(error)s") % {
                'uuid': uuid,
                'error': exc
            })

    usage = rp_obj.UsageList.get_all_by_resource_provider_uuid(context, uuid)

    response = req.response
    response.body = encodeutils.to_utf8(
        jsonutils.dumps(_serialize_usages(resource_provider, usage)))
    req.response.content_type = 'application/json'
    if want_version.matches((1, 15)):
        req.response.cache_control = 'no-cache'
        # While it would be possible to generate a last-modified time
        # based on the collection of allocations that result in a usage
        # value (with some spelunking in the SQL) that doesn't align with
        # the question that is being asked in a request for usages: What
        # is the usage, now? So the last-modified time is set to utcnow.
        req.response.last_modified = timeutils.utcnow(with_timezone=True)
    return req.response
Example #30
0
def normalize_member_of_qs_params(req, suffix=''):
    """Given a webob.Request object, validate that the member_of querystring
    parameters are correct. We begin supporting multiple member_of params in
    microversion 1.24.

    :param req: webob.Request object
    :return: A list containing sets of UUIDs of aggregates to filter on
    :raises `webob.exc.HTTPBadRequest` if the microversion requested is <1.24
            and the request contains multiple member_of querystring params
    :raises `webob.exc.HTTPBadRequest` if the val parameter is not in the
            expected format.
    """
    want_version = req.environ[placement.microversion.MICROVERSION_ENVIRON]
    multi_member_of = want_version.matches((1, 24))
    if not multi_member_of and len(req.GET.getall('member_of' + suffix)) > 1:
        raise webob.exc.HTTPBadRequest(
            _('Multiple member_of%s parameters are not supported') % suffix)
    values = []
    for value in req.GET.getall('member_of' + suffix):
        values.append(normalize_member_of_qs_param(value))
    return values