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'] # Use JSON validation to validation resource class name. util.extract_json('{"name": "%s"}' % name, PUT_RC_SCHEMA_V1_2) status = 204 try: rc = objects.ResourceClass.get_by_name(context, name) except exception.NotFound: try: rc = objects.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
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 = rp_obj.ResourceClass.get_by_name(context, name) except exception.NotFound: try: rc = rp_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
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'] data = util.extract_json(req.body, POST_RESOURCE_PROVIDER_SCHEMA) try: uuid = data.get('uuid', uuidutils.generate_uuid()) resource_provider = rp_obj.ResourceProvider( context, name=data['name'], uuid=uuid) 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 %(rp_uuid)s: %(error)s') % {'rp_uuid': uuid, 'error': exc}) req.response.location = util.resource_provider_url( req.environ, resource_provider) req.response.status = 201 req.response.content_type = None return req.response
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'] data = util.extract_json(req.body, POST_RESOURCE_PROVIDER_SCHEMA) try: uuid = data.get('uuid', uuidutils.generate_uuid()) resource_provider = objects.ResourceProvider( context, name=data['name'], uuid=uuid) 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 %(rp_uuid)s: %(error)s') % {'rp_uuid': uuid, 'error': exc}) req.response.location = util.resource_provider_url( req.environ, resource_provider) req.response.status = 201 req.response.content_type = None return req.response
def update_traits_for_resource_provider(req): context = req.environ['placement.context'] uuid = util.wsgi_path_item(req.environ, 'uuid') data = util.extract_json(req.body, SET_TRAITS_FOR_RP_SCHEMA) rp_gen = data['resource_provider_generation'] traits = data['traits'] resource_provider = objects.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 = objects.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 = _serialize_traits(trait_objs) response_body[ 'resource_provider_generation'] = resource_provider.generation req.response.status = 200 req.response.body = encodeutils.to_utf8(jsonutils.dumps(response_body)) req.response.content_type = 'application/json' return req.response
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'] data = util.extract_json(req.body, POST_RC_SCHEMA_V1_2) try: rc = objects.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
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'] data = util.extract_json(req.body, POST_RESOURCE_PROVIDER_SCHEMA) try: uuid = data.get('uuid', uuidutils.generate_uuid()) resource_provider = objects.ResourceProvider(context, name=data['name'], uuid=uuid) resource_provider.create() except db_exc.DBDuplicateEntry as exc: raise webob.exc.HTTPConflict( _('Conflicting resource provider already exists: %(error)s') % {'error': exc}, json_formatter=util.json_error_formatter) except exception.ObjectActionError as exc: raise webob.exc.HTTPBadRequest( _('Unable to create resource provider %(rp_uuid)s: %(error)s') % { 'rp_uuid': uuid, 'error': exc }, json_formatter=util.json_error_formatter) req.response.location = util.resource_provider_url(req.environ, resource_provider) req.response.status = 201 req.response.content_type = None return req.response
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'])
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'] data = util.extract_json(req.body, POST_RESOURCE_PROVIDER_SCHEMA) try: uuid = data.get('uuid', uuidutils.generate_uuid()) resource_provider = objects.ResourceProvider( context, name=data['name'], uuid=uuid) resource_provider.create() except db_exc.DBDuplicateEntry as exc: raise webob.exc.HTTPConflict( _('Conflicting resource provider already exists: %(error)s') % {'error': exc}, json_formatter=util.json_error_formatter) except exception.ObjectActionError as exc: raise webob.exc.HTTPBadRequest( _('Unable to create resource provider %(rp_uuid)s: %(error)s') % {'rp_uuid': uuid, 'error': exc}, json_formatter=util.json_error_formatter) req.response.location = util.resource_provider_url( req.environ, resource_provider) req.response.status = 201 req.response.content_type = None return req.response
def _set_allocations(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'] # If the body includes an allocation for a resource provider # that does not exist, raise a 400. allocation_objects = [] for allocation in allocation_data: resource_provider_uuid = allocation['resource_provider']['uuid'] try: resource_provider = rp_obj.ResourceProvider.get_by_uuid( context, resource_provider_uuid) except exception.NotFound: raise webob.exc.HTTPBadRequest( _("Allocation for resource provider '%(rp_uuid)s' " "that does not exist.") % {'rp_uuid': resource_provider_uuid}) resources = allocation['resources'] for resource_class in resources: allocation = rp_obj.Allocation(resource_provider=resource_provider, consumer_id=consumer_uuid, resource_class=resource_class, used=resources[resource_class]) allocation_objects.append(allocation) allocations = rp_obj.AllocationList( context, objects=allocation_objects, project_id=data.get('project_id'), user_id=data.get('user_id'), ) 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 resource provider " "%(rp_uuid)s: %(error)s") % { 'rp_uuid': resource_provider_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
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'] # The containing application will catch a not found here. resource_provider = objects.ResourceProvider.get_by_uuid( context, uuid) data = util.extract_json(req.body, PUT_RESOURCE_PROVIDER_SCHEMA) resource_provider.name = data['name'] try: resource_provider.save() except db_exc.DBDuplicateEntry as exc: raise webob.exc.HTTPConflict( _('Conflicting resource provider already exists: %(error)s') % {'error': exc}, json_formatter=util.json_error_formatter) except exception.ObjectActionError as exc: raise webob.exc.HTTPBadRequest( _('Unable to save resource provider %(rp_uuid)s: %(error)s') % {'rp_uuid': uuid, 'error': exc}, json_formatter=util.json_error_formatter) req.response.body = jsonutils.dumps( _serialize_provider(req.environ, resource_provider)) req.response.status = 200 req.response.content_type = 'application/json' return req.response
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)
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'] # The containing application will catch a not found here. resource_provider = rp_obj.ResourceProvider.get_by_uuid(context, uuid) data = util.extract_json(req.body, PUT_RESOURCE_PROVIDER_SCHEMA) resource_provider.name = data['name'] try: resource_provider.save() except db_exc.DBDuplicateEntry as exc: raise webob.exc.HTTPConflict( _('Conflicting resource provider %(name)s already exists.') % {'name': data['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 }) req.response.body = encodeutils.to_utf8( jsonutils.dumps(_serialize_provider(req.environ, resource_provider))) req.response.status = 200 req.response.content_type = 'application/json' return req.response
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'] data = util.extract_json(req.body, PUT_RC_SCHEMA_V1_2) # The containing application will catch a not found here. rc = objects.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': name}, json_formatter=util.json_error_formatter) except exception.ResourceClassCannotUpdateStandard: raise webob.exc.HTTPBadRequest( _('Cannot update standard resource class %(rp_name)s') % {'rp_name': name}, json_formatter=util.json_error_formatter) req.response.body = jsonutils.dumps( _serialize_resource_class(req.environ, rc) ) req.response.status = 200 req.response.content_type = 'application/json' return req.response
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)
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 = rp_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
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'] data = util.extract_json(req.body, PUT_RC_SCHEMA_V1_2) # The containing application will catch a not found here. rc = objects.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}, json_formatter=util.json_error_formatter) except exception.ResourceClassCannotUpdateStandard: raise webob.exc.HTTPBadRequest( _('Cannot update standard resource class %(rp_name)s') % {'rp_name': name}, json_formatter=util.json_error_formatter) 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
def update_traits_for_resource_provider(req): context = req.environ['placement.context'] uuid = util.wsgi_path_item(req.environ, 'uuid') data = util.extract_json(req.body, 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 = _serialize_traits(trait_objs) response_body[ 'resource_provider_generation'] = resource_provider.generation req.response.status = 200 req.response.body = encodeutils.to_utf8(jsonutils.dumps(response_body)) req.response.content_type = 'application/json' return req.response
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)
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) # 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'] consumer_generation = data[consumer_uuid].get('consumer_generation') 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, consumer_generation, want_version) 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 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
def _set_allocations(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'] # If the body includes an allocation for a resource provider # that does not exist, raise a 400. allocation_objects = [] for allocation in allocation_data: resource_provider_uuid = allocation['resource_provider']['uuid'] try: resource_provider = objects.ResourceProvider.get_by_uuid( context, resource_provider_uuid) except exception.NotFound: raise webob.exc.HTTPBadRequest( _("Allocation for resource provider '%(rp_uuid)s' " "that does not exist.") % {'rp_uuid': resource_provider_uuid}) resources = allocation['resources'] for resource_class in resources: allocation = objects.Allocation( resource_provider=resource_provider, consumer_id=consumer_uuid, resource_class=resource_class, used=resources[resource_class]) allocation_objects.append(allocation) allocations = objects.AllocationList( context, objects=allocation_objects, project_id=data.get('project_id'), user_id=data.get('user_id'), ) 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 resource provider " "%(rp_uuid)s: %(error)s") % {'rp_uuid': resource_provider_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
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
def set_aggregates(req): context = req.environ['placement.context'] uuid = util.wsgi_path_item(req.environ, 'uuid') resource_provider = rp_obj.ResourceProvider.get_by_uuid(context, uuid) aggregate_uuids = util.extract_json(req.body, schema.PUT_AGGREGATES_SCHEMA) resource_provider.set_aggregates(aggregate_uuids) return _send_aggregates(req, aggregate_uuids)
def set_aggregates(req): microversion.raise_404_if_not_version(req, (1, 1)) context = req.environ['placement.context'] uuid = util.wsgi_path_item(req.environ, 'uuid') resource_provider = objects.ResourceProvider.get_by_uuid(context, uuid) aggregate_uuids = util.extract_json(req.body, PUT_AGGREGATES_SCHEMA) resource_provider.set_aggregates(aggregate_uuids) return _send_aggregates(req.response, aggregate_uuids)
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
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') 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
def set_aggregates(req): microversion.raise_http_status_code_if_not_version(req, 404, (1, 1)) context = req.environ['placement.context'] uuid = util.wsgi_path_item(req.environ, 'uuid') resource_provider = objects.ResourceProvider.get_by_uuid( context, uuid) aggregate_uuids = util.extract_json(req.body, PUT_AGGREGATES_SCHEMA) resource_provider.set_aggregates(aggregate_uuids) return _send_aggregates(req.response, aggregate_uuids)
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
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']}) 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
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
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
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'] data = util.extract_json(req.body, POST_RC_SCHEMA_V1_2) try: rc = objects.ResourceClass(context, name=data['name']) rc.create() except exception.ResourceClassExists: raise webob.exc.HTTPConflict( _('Conflicting resource class already exists: %(name)s') % {'name': data['name']}, json_formatter=util.json_error_formatter) req.response.location = util.resource_class_url(req.environ, rc) req.response.status = 201 req.response.content_type = None return req.response
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
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) 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
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 InventoryList, 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: ' 'actual: %(actual)s, given: %(given)s') % { '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_obj = inventory.make_inventory_object(resource_provider, res_class, **inv_data) inv_list.append(inv_obj) inventory_by_rp[resource_provider] = rp_obj.InventoryList( objects=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) rp_obj.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
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
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
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 InventoryList, 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: ' 'actual: %(actual)s, given: %(given)s') % {'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_obj = inventory.make_inventory_object( resource_provider, res_class, **inv_data) inv_list.append(inv_obj) inventory_by_rp[resource_provider] = rp_obj.InventoryList( objects=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) rp_obj.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
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') 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 = [] 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: 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'), data.get('consumer_generation'), want_version) 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 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
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 create_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.create_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.create_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