Ejemplo n.º 1
0
def update_resource_class(req):
    """PUT to create or validate the existence of single resource class.

    On a successful create return 201. Return 204 if the class already
    exists. If the resource class is not a custom resource class, return
    a 400. 409 might be a better choice, but 400 aligns with previous code.
    """
    name = util.wsgi_path_item(req.environ, 'name')
    context = req.environ['placement.context']
    context.can(policies.UPDATE)

    # Use JSON validation to validation resource class name.
    util.extract_json('{"name": "%s"}' % name, schema.PUT_RC_SCHEMA_V1_2)

    status = 204
    try:
        rc = rc_obj.ResourceClass.get_by_name(context, name)
    except exception.NotFound:
        try:
            rc = rc_obj.ResourceClass(context, name=name)
            rc.create()
            status = 201
        # We will not see ResourceClassCannotUpdateStandard because
        # that was already caught when validating the {name}.
        except exception.ResourceClassExists:
            # Someone just now created the class, so stick with 204
            pass

    req.response.status = status
    req.response.content_type = None
    req.response.location = util.resource_class_url(req.environ, rc)
    return req.response
Ejemplo n.º 2
0
def set_aggregates(req):
    context = req.environ['placement.context']
    context.can(policies.UPDATE)
    want_version = req.environ[microversion.MICROVERSION_ENVIRON]
    consider_generation = want_version.matches(
        min_version=_INCLUDE_GENERATION_VERSION)
    put_schema = schema.PUT_AGGREGATES_SCHEMA_V1_1
    if consider_generation:
        put_schema = schema.PUT_AGGREGATES_SCHEMA_V1_19
    uuid = util.wsgi_path_item(req.environ, 'uuid')
    resource_provider = rp_obj.ResourceProvider.get_by_uuid(
        context, uuid)
    data = util.extract_json(req.body, put_schema)
    if consider_generation:
        # Check for generation conflict
        rp_gen = data['resource_provider_generation']
        if resource_provider.generation != rp_gen:
            raise webob.exc.HTTPConflict(
                _("Resource provider's generation already changed. Please "
                  "update the generation and try again."),
                comment=errors.CONCURRENT_UPDATE)
        aggregate_uuids = data['aggregates']
    else:
        aggregate_uuids = data
    _set_aggregates(resource_provider, aggregate_uuids,
                    increment_generation=consider_generation)

    return _send_aggregates(req, resource_provider, aggregate_uuids)
Ejemplo n.º 3
0
def set_aggregates(req):
    context = req.environ['placement.context']
    want_version = req.environ[microversion.MICROVERSION_ENVIRON]
    consider_generation = want_version.matches(
        min_version=_INCLUDE_GENERATION_VERSION)
    put_schema = schema.PUT_AGGREGATES_SCHEMA_V1_1
    if consider_generation:
        put_schema = schema.PUT_AGGREGATES_SCHEMA_V1_19
    uuid = util.wsgi_path_item(req.environ, 'uuid')
    resource_provider = rp_obj.ResourceProvider.get_by_uuid(context, uuid)
    data = util.extract_json(req.body, put_schema)
    if consider_generation:
        # Check for generation conflict
        rp_gen = data['resource_provider_generation']
        if resource_provider.generation != rp_gen:
            raise webob.exc.HTTPConflict(
                _("Resource provider's generation already changed. Please "
                  "update the generation and try again."))
        aggregate_uuids = data['aggregates']
    else:
        aggregate_uuids = data
    try:
        resource_provider.set_aggregates(
            aggregate_uuids, increment_generation=consider_generation)
    except (exception.ConcurrentUpdateDetected,
            db_exc.DBDuplicateEntry) as exc:
        raise webob.exc.HTTPConflict(
            _('Update conflict: %(error)s') % {'error': exc})

    return _send_aggregates(req, resource_provider, aggregate_uuids)
Ejemplo n.º 4
0
def update_traits_for_resource_provider(req):
    context = req.environ['placement.context']
    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)

    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
Ejemplo n.º 5
0
def update_resource_class(req):
    """PUT to update a single resource class.

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

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

    # The containing application will catch a not found here.
    rc = rc_obj.ResourceClass.get_by_name(context, name)

    rc.name = data['name']

    try:
        rc.save()
    except exception.ResourceClassExists:
        raise webob.exc.HTTPConflict(
            'Resource class already exists: %(name)s' % {'name': rc.name})
    except exception.ResourceClassCannotUpdateStandard:
        raise webob.exc.HTTPBadRequest(
            'Cannot update standard resource class %(rp_name)s' %
            {'rp_name': name})

    req.response.body = encodeutils.to_utf8(
        jsonutils.dumps(_serialize_resource_class(req.environ, rc)))
    req.response.status = 200
    req.response.content_type = 'application/json'
    return req.response
Ejemplo n.º 6
0
def create_resource_class(req):
    """POST to create a resource class.

    On success return a 201 response with an empty body and a location
    header pointing to the newly created resource class.
    """
    context = req.environ['placement.context']
    context.can(policies.CREATE)
    data = util.extract_json(req.body, schema.POST_RC_SCHEMA_V1_2)

    try:
        rc = rc_obj.ResourceClass(context, name=data['name'])
        rc.create()
    except exception.ResourceClassExists:
        raise webob.exc.HTTPConflict(
            'Conflicting resource class already exists: %(name)s' %
            {'name': data['name']})
    except exception.MaxDBRetriesExceeded:
        raise webob.exc.HTTPConflict(
            'Max retries of DB transaction exceeded attempting '
            'to create resource class: %(name)s, please '
            'try again.' % {'name': data['name']})

    req.response.location = util.resource_class_url(req.environ, rc)
    req.response.status = 201
    req.response.content_type = None
    return req.response
Ejemplo n.º 7
0
 def test_valid(self):
     data = util.extract_json(
         '{"name": "cow", '
         '"uuid": "%s"}' % uuidsentinel.rp_uuid,
         self.schema)
     self.assertEqual('cow', data['name'])
     self.assertEqual(uuidsentinel.rp_uuid, data['uuid'])
Ejemplo n.º 8
0
def create_resource_tree(req):
    """This method accepts a nested dict that describes a tree-like
    relationship among resource providers. Each node on the tree should contain
    the following keys:
        name: the name given to the resource. If not supplied, no name is set.
        uuid: the resource's UUID. If not supplied, one will be generated.
        resources: a dict of resources that this node provides directly. Each
            member should be of the form `resource_class: amount`
        traits: a list of traits to apply to this node.
        children: a list of nodes representing the children of this node. Each
            child node should be the same format dict as described here.

        returns: the UUID of the root of the newly-created tree
    There is no limit to the level of nesting for child resource providers.
    """
    context = req.environ["placement.context"]
    context.can(policies.UPDATE)
    schema = rp_schema.POST_RP_TREE
    want_version = req.environ[microversion.MICROVERSION_ENVIRON]
    data = util.extract_json(req.body, schema)
    root_rp = rp_obj.ResourceProvider.create_tree(context, data)
    
    response = req.response
    response.status = 200
    req.response.location = util.resource_provider_url(req.environ, root_rp)
    req.response.body = encodeutils.to_utf8(
            jsonutils.dumps(
            _serialize_provider(req.environ, root_rp, want_version)))
    req.response.content_type = "application/json"
    req.response.cache_control = "no-cache"
Ejemplo n.º 9
0
def create_resource_provider(req):
    """POST to create a resource provider.

    On success return a 201 response with an empty body
    (microversions 1.0 - 1.19) or a 200 response with a
    payload representing the newly created resource provider
    (microversions 1.20 - latest), and a location header
    pointing to the resource provider.
    """
    context = req.environ['placement.context']
    context.can(policies.CREATE)
    schema = rp_schema.POST_RESOURCE_PROVIDER_SCHEMA
    want_version = req.environ[microversion.MICROVERSION_ENVIRON]
    if want_version.matches((1, 14)):
        schema = rp_schema.POST_RP_SCHEMA_V1_14
    data = util.extract_json(req.body, schema)

    if data.get('uuid'):
        # Normalize UUID with no proper dashes into dashed one
        # with format {8}-{4}-{4}-{4}-{12}
        data['uuid'] = str(uuidlib.UUID(data['uuid']))
    else:
        data['uuid'] = uuidutils.generate_uuid()
    data["generation"] = 0

    try:
        resource_provider = rp_obj.ResourceProvider(context, **data)
        resource_provider.create()
    except db.ClientError as e:
        if "ConstraintValidationFailed" in str(e):
            if "property `uuid`" in str(e):
                duplicate = "uuid: %s" % data.get("uuid")
            else:
                duplicate = "name: %s" % data.get("name")
            raise webob.exc.HTTPConflict("Conflicting resource provider "
                    "%(duplicate)s already exists." % {'duplicate': duplicate},
                    comment=errors.DUPLICATE_NAME)
        else:
            raise webob.exc.HTTPBadRequest("Unable to create resource "
                    "provider '%(rp_uuid)s': %(error)s" %
                    {"rp_uuid": data.get("uuid"), "error": e})
    except exception.ObjectActionError as exc:
        raise webob.exc.HTTPBadRequest(
            'Unable to create resource provider "%(name)s", %(rp_uuid)s: '
            '%(error)s' %
            {'name': data['name'], 'rp_uuid': data['uuid'], 'error': exc})

    req.response.location = util.resource_provider_url(
        req.environ, resource_provider)
    if want_version.matches(min_version=(1, 20)):
        req.response.body = encodeutils.to_utf8(jsonutils.dumps(
            _serialize_provider(req.environ, resource_provider, want_version)))
        req.response.content_type = 'application/json'
        modified = util.pick_last_modified(None, resource_provider)
        req.response.last_modified = modified
        req.response.cache_control = 'no-cache'
    else:
        req.response.status = 201
        req.response.content_type = None
    return req.response
Ejemplo n.º 10
0
def _extract_inventory(body, schema):
    """Extract and validate inventory from JSON body."""
    data = util.extract_json(body, schema)

    inventory_data = copy.copy(INVENTORY_DEFAULTS)
    inventory_data.update(data)

    return inventory_data
Ejemplo n.º 11
0
def create_resource_provider(req):
    """POST to create a resource provider.

    On success return a 201 response with an empty body
    (microversions 1.0 - 1.19) or a 200 response with a
    payload representing the newly created resource provider
    (microversions 1.20 - latest), and a location header
    pointing to the resource provider.
    """
    context = req.environ['placement.context']
    context.can(policies.CREATE)
    schema = rp_schema.POST_RESOURCE_PROVIDER_SCHEMA
    want_version = req.environ[microversion.MICROVERSION_ENVIRON]
    if want_version.matches((1, 14)):
        schema = rp_schema.POST_RP_SCHEMA_V1_14
    data = util.extract_json(req.body, schema)

    try:
        if data.get('uuid'):
            # Normalize UUID with no proper dashes into dashed one
            # with format {8}-{4}-{4}-{4}-{12}
            data['uuid'] = str(uuidlib.UUID(data['uuid']))
        else:
            data['uuid'] = uuidutils.generate_uuid()

        resource_provider = rp_obj.ResourceProvider(context, **data)
        resource_provider.create()
    except db_exc.DBDuplicateEntry as exc:
        # Whether exc.columns has one or two entries (in the event
        # of both fields being duplicates) appears to be database
        # dependent, so going with the complete solution here.
        duplicate = ', '.join(
            ['%s: %s' % (column, data[column]) for column in exc.columns])
        raise webob.exc.HTTPConflict(
            'Conflicting resource provider %(duplicate)s already exists.' %
            {'duplicate': duplicate},
            comment=errors.DUPLICATE_NAME)
    except exception.ObjectActionError as exc:
        raise webob.exc.HTTPBadRequest(
            'Unable to create resource provider "%(name)s", %(rp_uuid)s: '
            '%(error)s' %
            {'name': data['name'], 'rp_uuid': data['uuid'], 'error': exc})

    req.response.location = util.resource_provider_url(
        req.environ, resource_provider)
    if want_version.matches(min_version=(1, 20)):
        req.response.body = encodeutils.to_utf8(jsonutils.dumps(
            _serialize_provider(req.environ, resource_provider, want_version)))
        req.response.content_type = 'application/json'
        modified = util.pick_last_modified(None, resource_provider)
        req.response.last_modified = modified
        req.response.cache_control = 'no-cache'
    else:
        req.response.status = 201
        req.response.content_type = None
    return req.response
Ejemplo n.º 12
0
def _set_allocations_for_consumer(req, schema):
    context = req.environ['placement.context']
    consumer_uuid = util.wsgi_path_item(req.environ, 'consumer_uuid')
    data = util.extract_json(req.body, schema)
    allocation_data = data['allocations']

    # Normalize allocation data to dict.
    want_version = req.environ[microversion.MICROVERSION_ENVIRON]
    if not want_version.matches((1, 12)):
        allocations_dict = {}
        # Allocation are list-ish, transform to dict-ish
        for allocation in allocation_data:
            resource_provider_uuid = allocation['resource_provider']['uuid']
            allocations_dict[resource_provider_uuid] = {
                'resources': allocation['resources']
            }
        allocation_data = allocations_dict

    # If the body includes an allocation for a resource provider
    # that does not exist, raise a 400.
    allocation_objects = []
    for resource_provider_uuid, allocation in allocation_data.items():
        new_allocations = _new_allocations(context, resource_provider_uuid,
                                           consumer_uuid,
                                           allocation['resources'],
                                           data.get('project_id'),
                                           data.get('user_id'))
        allocation_objects.extend(new_allocations)

    allocations = rp_obj.AllocationList(context, objects=allocation_objects)

    try:
        allocations.create_all()
        LOG.debug("Successfully wrote allocations %s", allocations)
    # InvalidInventory is a parent for several exceptions that
    # indicate either that Inventory is not present, or that
    # capacity limits have been exceeded.
    except exception.NotFound as exc:
        raise webob.exc.HTTPBadRequest(
            _("Unable to allocate inventory for consumer "
              "%(consumer_uuid)s: %(error)s") % {
                  'consumer_uuid': consumer_uuid,
                  'error': exc
              })
    except exception.InvalidInventory as exc:
        raise webob.exc.HTTPConflict(
            _('Unable to allocate inventory: %(error)s') % {'error': exc})
    except exception.ConcurrentUpdateDetected as exc:
        raise webob.exc.HTTPConflict(
            _('Inventory changed while attempting to allocate: %(error)s') %
            {'error': exc})

    req.response.status = 204
    req.response.content_type = None
    return req.response
Ejemplo n.º 13
0
def set_allocations(req):
    context = req.environ['placement.context']
    data = util.extract_json(req.body, schema.POST_ALLOCATIONS_V1_13)

    # Create a sequence of allocation objects to be used in an
    # AllocationList.create_all() call, which will mean all the changes
    # happen within a single transaction and with resource provider
    # generations check all in one go.
    allocation_objects = []

    for consumer_uuid in data:
        project_id = data[consumer_uuid]['project_id']
        user_id = data[consumer_uuid]['user_id']
        allocations = data[consumer_uuid]['allocations']
        if allocations:
            for resource_provider_uuid in allocations:
                resources = allocations[resource_provider_uuid]['resources']
                new_allocations = _new_allocations(context,
                                                   resource_provider_uuid,
                                                   consumer_uuid, resources,
                                                   project_id, user_id)
                allocation_objects.extend(new_allocations)
        else:
            # The allocations are empty, which means wipe them out.
            # Internal to the allocation object this is signalled by a
            # used value of 0.
            allocations = rp_obj.AllocationList.get_all_by_consumer_id(
                context, consumer_uuid)
            for allocation in allocations:
                allocation.used = 0
                allocation_objects.append(allocation)

    allocations = rp_obj.AllocationList(context, objects=allocation_objects)

    try:
        allocations.create_all()
        LOG.debug("Successfully wrote allocations %s", 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 changed while attempting to allocate: %(error)s') %
            {'error': exc})

    req.response.status = 204
    req.response.content_type = None
    return req.response
Ejemplo n.º 14
0
def _extract_inventories(body, schema):
    """Extract and validate multiple inventories from JSON body."""
    data = util.extract_json(body, schema)

    inventories = {}
    for res_class, raw_inventory in data['inventories'].items():
        inventory_data = copy.copy(INVENTORY_DEFAULTS)
        inventory_data.update(raw_inventory)
        inventories[res_class] = inventory_data

    data['inventories'] = inventories
    return data
Ejemplo n.º 15
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.ClientError as e:
        if "ConstraintValidationFailed" in e.message:
            raise webob.exc.HTTPConflict(
                'Conflicting resource provider %(name)s already exists.' %
                {'name': data['name']},
                comment=errors.DUPLICATE_NAME)
    except db_exc.DBDuplicateEntry:
        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
Ejemplo n.º 16
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
    if want_version.matches((1, 34)):
        want_schema = schema.POST_ALLOCATIONS_V1_34
    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
    # alloc_obj.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_obj.replace_all(context, alloc_list)
            LOG.debug("Successfully wrote allocations %s", alloc_list)
        except Exception:
            with excutils.save_and_reraise_exception():
                delete_consumers(new_consumers_created)

    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
Ejemplo n.º 17
0
def create_resource_provider(req):
    """POST to create a resource provider.

    On success return a 201 response with an empty body and a location
    header pointing to the newly created resource provider.
    """
    context = req.environ['placement.context']
    schema = rp_schema.POST_RESOURCE_PROVIDER_SCHEMA
    want_version = req.environ[microversion.MICROVERSION_ENVIRON]
    if want_version.matches((1, 14)):
        schema = rp_schema.POST_RP_SCHEMA_V1_14
    data = util.extract_json(req.body, schema)

    try:
        uuid = data.setdefault('uuid', uuidutils.generate_uuid())
        resource_provider = rp_obj.ResourceProvider(context, **data)
        resource_provider.create()
    except db_exc.DBDuplicateEntry as exc:
        # Whether exc.columns has one or two entries (in the event
        # of both fields being duplicates) appears to be database
        # dependent, so going with the complete solution here.
        duplicate = ', '.join(['%s: %s' % (column, data[column])
                          for column in exc.columns])
        raise webob.exc.HTTPConflict(
            _('Conflicting resource provider %(duplicate)s already exists.') %
            {'duplicate': duplicate})
    except exception.ObjectActionError as exc:
        raise webob.exc.HTTPBadRequest(
            _('Unable to create resource provider "%(name)s", %(rp_uuid)s: '
              '%(error)s') %
            {'name': data['name'], 'rp_uuid': uuid, 'error': exc})

    req.response.location = util.resource_provider_url(
        req.environ, resource_provider)
    if want_version.matches(min_version=(1, 20)):
        req.response.body = encodeutils.to_utf8(jsonutils.dumps(
            _serialize_provider(req.environ, resource_provider, want_version)))
        req.response.content_type = 'application/json'
        modified = util.pick_last_modified(None, resource_provider)
        req.response.last_modified = modified
        req.response.cache_control = 'no-cache'
    else:
        req.response.status = 201
        req.response.content_type = None
    return req.response
Ejemplo n.º 18
0
def associate(req):
    """Associate a resource provider with one or more other resource providers.
    This is commonly used to create a sharing relationship among resource
    providers.

    On success return a 204 and an empty body.
    """
    uuid = util.wsgi_path_item(req.environ, "uuid")
    context = req.environ["placement.context"]
    context.can(policies.UPDATE)
    schema = rp_schema.POST_RPS_ASSOCIATE
    want_version = req.environ[microversion.MICROVERSION_ENVIRON]
    data = util.extract_json(req.body, schema)
    target_uuids = data.get("targets", [])

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

    response = req.response
    response.status = 204
Ejemplo n.º 19
0
def _set_allocations_for_consumer(req, schema):
    context = req.environ['placement.context']
    context.can(policies.ALLOC_UPDATE)
    consumer_uuid = util.wsgi_path_item(req.environ, 'consumer_uuid')
    if not uuidutils.is_uuid_like(consumer_uuid):
        raise webob.exc.HTTPBadRequest(
            _('Malformed consumer_uuid: %(consumer_uuid)s') %
            {'consumer_uuid': consumer_uuid})
    consumer_uuid = str(uuid.UUID(consumer_uuid))
    data = util.extract_json(req.body, schema)
    allocation_data = data['allocations']

    # Normalize allocation data to dict.
    want_version = req.environ[microversion.MICROVERSION_ENVIRON]
    if not want_version.matches((1, 12)):
        allocations_dict = {}
        # Allocation are list-ish, transform to dict-ish
        for allocation in allocation_data:
            resource_provider_uuid = allocation['resource_provider']['uuid']
            allocations_dict[resource_provider_uuid] = {
                'resources': allocation['resources']
            }
        allocation_data = allocations_dict

    allocation_objects = []
    # Consumer object saved in case we need to delete the auto-created consumer
    # record
    consumer = None
    # Whether we created a new consumer record
    created_new_consumer = False
    if not allocation_data:
        # The allocations are empty, which means wipe them out. Internal
        # to the allocation object this is signalled by a used value of 0.
        # We still need to verify the consumer's generation, though, which
        # we do in _ensure_consumer()
        # NOTE(jaypipes): This will only occur 1.28+. The JSONSchema will
        # prevent an empty allocations object from being passed when there is
        # no consumer generation, so this is safe to do.
        util.ensure_consumer(context, consumer_uuid, data.get('project_id'),
             data.get('user_id'), data.get('consumer_generation'),
             want_version)
        allocations = rp_obj.AllocationList.get_all_by_consumer_id(
            context, consumer_uuid)
        for allocation in allocations:
            allocation.used = 0
            allocation_objects.append(allocation)
    else:
        # If the body includes an allocation for a resource provider
        # that does not exist, raise a 400.
        rp_objs = _resource_providers_by_uuid(context, allocation_data.keys())
        consumer, created_new_consumer = util.ensure_consumer(
            context, consumer_uuid, data.get('project_id'),
            data.get('user_id'), data.get('consumer_generation'),
            want_version)
        for resource_provider_uuid, allocation in allocation_data.items():
            resource_provider = rp_objs[resource_provider_uuid]
            new_allocations = _new_allocations(context,
                                               resource_provider,
                                               consumer,
                                               allocation['resources'])
            allocation_objects.extend(new_allocations)

    allocations = rp_obj.AllocationList(
        context, objects=allocation_objects)

    def _create_allocations(alloc_list):
        try:
            alloc_list.replace_all()
            LOG.debug("Successfully wrote allocations %s", alloc_list)
        except Exception:
            if created_new_consumer:
                delete_consumers([consumer])
            raise

    try:
        _create_allocations(allocations)
    # InvalidInventory is a parent for several exceptions that
    # indicate either that Inventory is not present, or that
    # capacity limits have been exceeded.
    except exception.NotFound as exc:
        raise webob.exc.HTTPBadRequest(
                _("Unable to allocate inventory for consumer "
                  "%(consumer_uuid)s: %(error)s") %
            {'consumer_uuid': consumer_uuid, 'error': exc})
    except exception.InvalidInventory as exc:
        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
Ejemplo n.º 20
0
def reshape(req):
    context = req.environ['placement.context']
    want_version = req.environ[microversion.MICROVERSION_ENVIRON]
    context.can(policies.RESHAPE)
    data = util.extract_json(req.body, schema.POST_RESHAPER_SCHEMA)
    inventories = data['inventories']
    allocations = data['allocations']
    # We're going to create several lists of Inventory objects, keyed by rp
    # uuid.
    inventory_by_rp = {}

    # TODO(cdent): this has overlaps with inventory:set_inventories
    # and is a mess of bad names and lack of method extraction.
    for rp_uuid, inventory_data in inventories.items():
        try:
            resource_provider = rp_obj.ResourceProvider.get_by_uuid(
                context, rp_uuid)
        except exception.NotFound as exc:
            raise webob.exc.HTTPBadRequest(
                'Resource provider %(rp_uuid)s in inventories not found: '
                '%(error)s' % {
                    'rp_uuid': rp_uuid,
                    'error': exc
                },
                comment=errors.RESOURCE_PROVIDER_NOT_FOUND)

        # Do an early generation check.
        generation = inventory_data['resource_provider_generation']
        if generation != resource_provider.generation:
            raise webob.exc.HTTPConflict(
                'resource provider generation conflict for provider %(rp)s: '
                'actual: %(actual)s, given: %(given)s' % {
                    'rp': rp_uuid,
                    'actual': resource_provider.generation,
                    'given': generation
                },
                comment=errors.CONCURRENT_UPDATE)

        inv_list = []
        for res_class, raw_inventory in inventory_data['inventories'].items():
            inv_data = copy.copy(inventory.INVENTORY_DEFAULTS)
            inv_data.update(raw_inventory)
            inv_object = inventory.make_inventory_object(
                resource_provider, res_class, **inv_data)
            inv_list.append(inv_object)
        inventory_by_rp[resource_provider] = inv_list

    # Make the consumer objects associated with the allocations.
    consumers, new_consumers_created = allocation.inspect_consumers(
        context, allocations, want_version)

    # Nest exception handling so that any exception results in new consumer
    # objects being deleted, then reraise for translating to HTTP exceptions.
    try:
        try:
            # When these allocations are created they get resource provider
            # objects which are different instances (usually with the same
            # data) from those loaded above when creating inventory objects.
            # The reshape method below is responsible for ensuring that the
            # resource providers and their generations do not conflict.
            allocation_objects = allocation.create_allocation_list(
                context, allocations, consumers)

            reshaper.reshape(context, inventory_by_rp, allocation_objects)
        except Exception:
            with excutils.save_and_reraise_exception():
                allocation.delete_consumers(new_consumers_created)
    # Generation conflict is a (rare) possibility in a few different
    # places in reshape().
    except exception.ConcurrentUpdateDetected as exc:
        raise webob.exc.HTTPConflict('update conflict: %(error)s' %
                                     {'error': exc},
                                     comment=errors.CONCURRENT_UPDATE)
    # A NotFound here means a resource class that does not exist was named
    except exception.NotFound as exc:
        raise webob.exc.HTTPBadRequest('malformed reshaper data: %(error)s' %
                                       {'error': exc})
    # Distinguish inventory in use (has allocations on it)...
    except exception.InventoryInUse as exc:
        raise webob.exc.HTTPConflict('update conflict: %(error)s' %
                                     {'error': exc},
                                     comment=errors.INVENTORY_INUSE)
    # ...from allocations which won't fit for a variety of reasons.
    except exception.InvalidInventory as exc:
        raise webob.exc.HTTPConflict(
            'Unable to allocate inventory: %(error)s' % {'error': exc})

    req.response.status = 204
    req.response.content_type = None
    return req.response
Ejemplo n.º 21
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)

    # First, ensure that all consumers referenced in the payload actually
    # exist. And if not, create them. Keep a record of auto-created consumers
    # so we can clean them up if the end allocation replace_all() fails.
    consumers = {}  # dict of Consumer objects, keyed by consumer UUID
    new_consumers_created = []
    for consumer_uuid in data:
        project_id = data[consumer_uuid]['project_id']
        user_id = data[consumer_uuid]['user_id']
        consumer_generation = data[consumer_uuid].get('consumer_generation')
        try:
            consumer, new_consumer_created = util.ensure_consumer(
                context, consumer_uuid, project_id, user_id,
                consumer_generation, want_version)
            if new_consumer_created:
                new_consumers_created.append(consumer)
            consumers[consumer_uuid] = consumer
        except Exception:
            # If any errors (for instance, a consumer generation conflict)
            # occur when ensuring consumer records above, make sure we delete
            # any auto-created consumers.
            _delete_consumers(new_consumers_created)
            raise

    # 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
Ejemplo n.º 22
0
def create_resource_provider(req):
    """POST to create a resource provider.

    On success return a 201 response with an empty body
    (microversions 1.0 - 1.19) or a 200 response with a
    payload representing the newly created resource provider
    (microversions 1.20 - latest), and a location header
    pointing to the resource provider.
    """
    context = req.environ['placement.context']
    context.can(policies.CREATE)
    schema = rp_schema.POST_RESOURCE_PROVIDER_SCHEMA
    want_version = req.environ[microversion.MICROVERSION_ENVIRON]
    if want_version.matches((1, 14)):
        schema = rp_schema.POST_RP_SCHEMA_V1_14
    data = util.extract_json(req.body, schema)

    try:
        if data.get('uuid'):
            # Normalize UUID with no proper dashes into dashed one
            # with format {8}-{4}-{4}-{4}-{12}
            data['uuid'] = str(uuidlib.UUID(data['uuid']))
        else:
            data['uuid'] = uuidutils.generate_uuid()

        resource_provider = rp_obj.ResourceProvider(context, **data)
        resource_provider.create()
    except db_exc.DBDuplicateEntry as exc:
        # Whether exc.columns has one or two entries (in the event
        # of both fields being duplicates) appears to be database
        # dependent, so going with the complete solution here.
        duplicates = []
        for column in exc.columns:
            # For MySQL, this is error 1062:
            #
            #   Duplicate entry '%s' for key %d
            #
            # The 'key' value is captured in 'DBDuplicateEntry.columns'.
            # Despite the name, this isn't always a column name. While MySQL
            # 5.x does indeed use the name of the column, 8.x uses the name of
            # the constraint. oslo.db should probably fix this, but until that
            # happens we need to handle both cases
            if column == 'uniq_resource_providers0uuid':
                duplicates.append(f'uuid: {data["uuid"]}')
            elif column == 'uniq_resource_providers0name':
                duplicates.append(f'name: {data["name"]}')
            else:
                duplicates.append(f'{column}: {data[column]}')
        raise webob.exc.HTTPConflict(
            'Conflicting resource provider %(duplicate)s already exists.' %
            {'duplicate': ', '.join(duplicates)},
            comment=errors.DUPLICATE_NAME)
    except exception.ObjectActionError as exc:
        raise webob.exc.HTTPBadRequest(
            'Unable to create resource provider "%(name)s", %(rp_uuid)s: '
            '%(error)s' % {
                'name': data['name'],
                'rp_uuid': data['uuid'],
                'error': exc
            })

    req.response.location = util.resource_provider_url(req.environ,
                                                       resource_provider)
    if want_version.matches(min_version=(1, 20)):
        req.response.body = encodeutils.to_utf8(
            jsonutils.dumps(
                _serialize_provider(req.environ, resource_provider,
                                    want_version)))
        req.response.content_type = 'application/json'
        modified = util.pick_last_modified(None, resource_provider)
        req.response.last_modified = modified
        req.response.cache_control = 'no-cache'
    else:
        req.response.status = 201
        req.response.content_type = None
    return req.response
Ejemplo n.º 23
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
    if want_version.matches((1, 34)):
        want_schema = schema.POST_ALLOCATIONS_V1_34
    if want_version.matches((1, 38)):
        want_schema = schema.POST_ALLOCATIONS_V1_38
    data = util.extract_json(req.body, want_schema)

    consumers, new_consumers_created, requested_attrs = inspect_consumers(
        context, data, want_version)
    # Create a sequence of allocation objects to be used in one
    # alloc_obj.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)

    @db_api.placement_context_manager.writer
    def _update_consumers_and_create_allocations(ctx):
        # Update consumer attributes if requested attributes are different.
        # NOTE(melwitt): This will not raise ConcurrentUpdateDetected, that
        # happens later in AllocationList.replace_all()
        data_util.update_consumers(consumers.values(), requested_attrs)

        alloc_obj.replace_all(ctx, allocations)
        LOG.debug("Successfully wrote allocations %s", allocations)

    def _create_allocations():
        try:
            # NOTE(melwitt): Group the consumer and allocation database updates
            # in a single transaction so that updates get rolled back
            # automatically in the event of a consumer generation conflict.
            _update_consumers_and_create_allocations(context)
        except Exception:
            with excutils.save_and_reraise_exception():
                delete_consumers(new_consumers_created)

    try:
        _create_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
Ejemplo n.º 24
0
def reshape(req):
    context = req.environ['placement.context']
    want_version = req.environ[microversion.MICROVERSION_ENVIRON]
    context.can(policies.RESHAPE)
    data = util.extract_json(req.body, schema.POST_RESHAPER_SCHEMA)
    inventories = data['inventories']
    allocations = data['allocations']

    # Get the current inventory for all the RPs included. Compare it to the
    # passed inventories; if they don't have the same resources, then something
    # is off, and reject the reshape.
    curr_resources = []
    proposed_resources = []
    for rp_uuid, inv_data in inventories.items():
        curr_resources += list(
            rp_obj.get_current_inventory_resources(context,
                                                   rp_uuid,
                                                   include_total=True))
        for rc_name, rc_vals in inv_data["inventories"].items():
            proposed_resources.append((rc_name, rc_vals["total"]))
    curr_resources.sort()
    proposed_resources.sort()
    if not curr_resources == proposed_resources:
        raise webob.exc.HTTPBadRequest("Proposed reshaped inventory does not "
                                       "match existing inventory.")

    # Now check the allocations to make sure that they match
    curr_allocs = defaultdict(int)
    proposed_allocs = defaultdict(int)

    alloc_data = [itm["allocations"] for itm in allocations.values()]
    # First, get all the unique RPs
    rp_dict_keys = [itm.keys() for itm in alloc_data]
    # Convert dict_keys data to lists
    rp_lists = [list(itm) for itm in rp_dict_keys]
    # Collapse the lists of lists to a single list, and get the unique values
    rp_set = set(list(itertools.chain(*rp_lists)))
    # Get the current allocations for each RP
    for rp in rp_set:
        alloc_dict = rp_obj.get_allocated_inventory(context, rp)
        for key, val in alloc_dict.items():
            curr_allocs[key] += val
    # Now sum up the proposed allocations. The RC name and amount are in a
    # deeply-nested dict.
    for rdict in alloc_data:
        for _, rsrc in rdict.items():
            for _, rc in rsrc.items():
                for rc_name, amt in rc.items():
                    proposed_allocs[rc_name] += amt

    if not curr_allocs == proposed_allocs:
        raise webob.HTTPBadRequest("Proposed reshaped allocations do not "
                                   "match existing allocations.")

    # The inventories and allocations match up, so now just switch the
    # connections among the entities.
    rp_obj.reshape(context, inventories, allocations)
    # ^^^ This needs to be written

    # We're going to create several lists of Inventory objects, keyed by rp
    # uuid.
    inventory_by_rp = {}

    # TODO(cdent): this has overlaps with inventory:set_inventories
    # and is a mess of bad names and lack of method extraction.
    for rp_uuid, inventory_data in inventories.items():
        try:
            resource_provider = rp_obj.ResourceProvider.get_by_uuid(
                context, rp_uuid)
        except exception.NotFound as exc:
            raise webob.exc.HTTPBadRequest(
                'Resource provider %(rp_uuid)s in inventories not found: '
                '%(error)s' % {
                    'rp_uuid': rp_uuid,
                    'error': exc
                },
                comment=errors.RESOURCE_PROVIDER_NOT_FOUND)

        # Do an early generation check.
        generation = inventory_data['resource_provider_generation']
        if generation != resource_provider.generation:
            raise webob.exc.HTTPConflict(
                'resource provider generation conflict for provider %(rp)s: '
                'actual: %(actual)s, given: %(given)s' % {
                    'rp': rp_uuid,
                    'actual': resource_provider.generation,
                    'given': generation
                },
                comment=errors.CONCURRENT_UPDATE)

        inv_list = []
        for res_class, raw_inventory in inventory_data['inventories'].items():
            inv_data = copy.copy(inventory.INVENTORY_DEFAULTS)
            inv_data.update(raw_inventory)
            inv_object = inventory.make_inventory_object(
                resource_provider, res_class, **inv_data)
            inv_list.append(inv_object)
        inventory_by_rp[resource_provider] = inv_list

    # Make the consumer objects associated with the allocations.
    consumers, new_consumers_created = allocation.inspect_consumers(
        context, allocations, want_version)

    # Nest exception handling so that any exception results in new consumer
    # objects being deleted, then reraise for translating to HTTP exceptions.
    try:
        try:
            # When these allocations are created they get resource provider
            # objects which are different instances (usually with the same
            # data) from those loaded above when creating inventory objects.
            # The reshape method below is responsible for ensuring that the
            # resource providers and their generations do not conflict.
            allocation_objects = allocation.create_allocation_list(
                context, allocations, consumers)

            reshaper.reshape(context, inventory_by_rp, allocation_objects)
        except Exception:
            with excutils.save_and_reraise_exception():
                allocation.delete_consumers(new_consumers_created)
    # Generation conflict is a (rare) possibility in a few different
    # places in reshape().
    except exception.ConcurrentUpdateDetected as exc:
        raise webob.exc.HTTPConflict('update conflict: %(error)s' %
                                     {'error': exc},
                                     comment=errors.CONCURRENT_UPDATE)
    # A NotFound here means a resource class that does not exist was named
    except exception.NotFound as exc:
        raise webob.exc.HTTPBadRequest('malformed reshaper data: %(error)s' %
                                       {'error': exc})
    # Distinguish inventory in use (has allocations on it)...
    except exception.InventoryInUse as exc:
        raise webob.exc.HTTPConflict('update conflict: %(error)s' %
                                     {'error': exc},
                                     comment=errors.INVENTORY_INUSE)
    # ...from allocations which won't fit for a variety of reasons.
    except exception.InvalidInventory as exc:
        raise webob.exc.HTTPConflict(
            'Unable to allocate inventory: %(error)s' % {'error': exc})

    req.response.status = 204
    req.response.content_type = None
    return req.response
Ejemplo n.º 25
0
def _set_allocations_for_consumer(req, schema):
    context = req.environ['placement.context']
    context.can(policies.ALLOC_UPDATE)
    consumer_uuid = util.wsgi_path_item(req.environ, 'consumer_uuid')
    if not uuidutils.is_uuid_like(consumer_uuid):
        raise webob.exc.HTTPBadRequest(
            'Malformed consumer_uuid: %(consumer_uuid)s' %
            {'consumer_uuid': consumer_uuid})
    consumer_uuid = str(uuid.UUID(consumer_uuid))
    data = util.extract_json(req.body, schema)
    allocation_data = data['allocations']

    # Normalize allocation data to dict.
    want_version = req.environ[microversion.MICROVERSION_ENVIRON]
    if not want_version.matches((1, 12)):
        allocations_dict = {}
        # Allocation are list-ish, transform to dict-ish
        for allocation in allocation_data:
            resource_provider_uuid = allocation['resource_provider']['uuid']
            allocations_dict[resource_provider_uuid] = {
                'resources': allocation['resources']
            }
        allocation_data = allocations_dict

    allocation_objects = []
    # Consumer object saved in case we need to delete the auto-created consumer
    # record
    consumer = None
    # Whether we created a new consumer record
    created_new_consumer = False
    # Get or create the project, user, consumer, and consumer type.
    # This needs to be done in separate database transactions so that the
    # records can be read after a create collision due to a racing request.
    consumer, created_new_consumer, request_attr = (data_util.ensure_consumer(
        context, consumer_uuid, data.get('project_id'), data.get('user_id'),
        data.get('consumer_generation'), data.get('consumer_type'),
        want_version))

    if not allocation_data:
        # The allocations are empty, which means wipe them out. Internal
        # to the allocation object this is signalled by a used value of 0.
        # We verified the consumer's generation in util.ensure_consumer()
        # NOTE(jaypipes): This will only occur 1.28+. The JSONSchema will
        # prevent an empty allocations object from being passed when there is
        # no consumer generation, so this is safe to do.
        allocations = alloc_obj.get_all_by_consumer_id(context, consumer_uuid)
        for allocation in allocations:
            allocation.used = 0
            allocation_objects.append(allocation)
    else:
        # If the body includes an allocation for a resource provider
        # that does not exist, raise a 400.
        rp_objs = _resource_providers_by_uuid(context, allocation_data.keys())

        for resource_provider_uuid, allocation in allocation_data.items():
            resource_provider = rp_objs[resource_provider_uuid]
            new_allocations = _new_allocations(context, resource_provider,
                                               consumer,
                                               allocation['resources'])
            allocation_objects.extend(new_allocations)

    @db_api.placement_context_manager.writer
    def _update_consumers_and_create_allocations(ctx):
        # Update consumer attributes if requested attributes are different.
        # NOTE(melwitt): This will not raise ConcurrentUpdateDetected, that
        # happens later in AllocationList.replace_all()
        data_util.update_consumers([consumer], {consumer_uuid: request_attr})

        alloc_obj.replace_all(ctx, allocation_objects)
        LOG.debug("Successfully wrote allocations %s", allocation_objects)

    def _create_allocations():
        try:
            # NOTE(melwitt): Group the consumer and allocation database updates
            # in a single transaction so that updates get rolled back
            # automatically in the event of a consumer generation conflict.
            _update_consumers_and_create_allocations(context)
        except Exception:
            with excutils.save_and_reraise_exception():
                if created_new_consumer:
                    delete_consumers([consumer])

    try:
        _create_allocations()
    # InvalidInventory is a parent for several exceptions that
    # indicate either that Inventory is not present, or that
    # capacity limits have been exceeded.
    except exception.NotFound as exc:
        raise webob.exc.HTTPBadRequest(
            "Unable to allocate inventory for consumer %(consumer_uuid)s: "
            "%(error)s" % {
                'consumer_uuid': consumer_uuid,
                'error': exc
            })
    except exception.InvalidInventory as exc:
        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