def list_for_resource_provider(req): """List allocations associated with a resource provider.""" # TODO(cdent): On a shared resource provider (for example a # giant disk farm) this list could get very long. At the moment # we have no facility for limiting the output. Given that we are # using a dict of dicts for the output we are potentially limiting # ourselves in terms of sorting and filtering. context = req.environ['placement.context'] uuid = util.wsgi_path_item(req.environ, 'uuid') # confirm existence of resource provider so we get a reasonable # 404 instead of empty list try: resource_provider = objects.ResourceProvider.get_by_uuid(context, uuid) except exception.NotFound as exc: raise webob.exc.HTTPNotFound( _("Resource provider '%(rp_uuid)s' not found: %(error)s") % { 'rp_uuid': uuid, 'error': exc }) allocations = objects.AllocationList.get_all_by_resource_provider_uuid( context, uuid) allocations_json = jsonutils.dumps( _serialize_allocations_for_resource_provider(allocations, resource_provider)) req.response.status = 200 req.response.body = encodeutils.to_utf8(allocations_json) req.response.content_type = 'application/json' return req.response
def normalize_traits_qs_param(val, allow_forbidden=False): """Parse a traits query string parameter value. Note that this method doesn't know or care about the query parameter key, which may currently be of the form `required`, `required123`, etc., but which may someday also include `preferred`, etc. This method currently does no format validation of trait strings, other than to ensure they're not zero-length. :param val: A traits query parameter value: a comma-separated string of trait names. :param allow_forbidden: If True, accept forbidden traits (that is, traits prefixed by '!') as a valid form when notifying the caller that the provided value is not properly formed. :return: A set of trait names. :raises `webob.exc.HTTPBadRequest` if the val parameter is not in the expected format. """ ret = set(substr.strip() for substr in val.split(',')) expected_form = 'HW_CPU_X86_VMX,CUSTOM_MAGIC' if allow_forbidden: expected_form = 'HW_CPU_X86_VMX,!CUSTOM_MAGIC' if not all(trait and valid_trait(trait, allow_forbidden) for trait in ret): msg = _("Invalid query string parameters: Expected 'required' " "parameter value of the form: %(form)s. " "Got: %(val)s") % { 'form': expected_form, 'val': val } raise webob.exc.HTTPBadRequest(msg) return ret
def _extract_allocations(body, schema): """Extract allocation data from a JSON body.""" try: data = jsonutils.loads(body) except ValueError as exc: raise webob.exc.HTTPBadRequest( _('Malformed JSON: %(error)s') % {'error': exc}, json_formatter=util.json_error_formatter) try: jsonschema.validate(data, schema, format_checker=jsonschema.FormatChecker()) except jsonschema.ValidationError as exc: raise webob.exc.HTTPBadRequest( _('JSON does not validate: %(error)s') % {'error': exc}, json_formatter=util.json_error_formatter) return data
def extract_json(body, schema): """Extract JSON from a body and validate with the provided schema.""" try: data = jsonutils.loads(body) except ValueError as exc: raise webob.exc.HTTPBadRequest(_('Malformed JSON: %(error)s') % {'error': exc}, json_formatter=json_error_formatter) try: jsonschema.validate(data, schema, format_checker=jsonschema.FormatChecker()) except jsonschema.ValidationError as exc: raise webob.exc.HTTPBadRequest(_('JSON does not validate: %(error)s') % {'error': exc}, json_formatter=json_error_formatter) return data
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 decorated_function(req): if req.accept: best_matches = req.accept.acceptable_offers(types) if not best_matches: type_string = ', '.join(types) raise webob.exc.HTTPNotAcceptable( _('Only %(type)s is provided') % {'type': type_string}, json_formatter=json_error_formatter) return f(req)
def validate_query_params(req, schema): try: # NOTE(Kevin_Zheng): The webob package throws UnicodeError when # param cannot be decoded. Catch this and raise HTTP 400. jsonschema.validate(dict(req.GET), schema, format_checker=jsonschema.FormatChecker()) except (jsonschema.ValidationError, UnicodeDecodeError) as exc: raise webob.exc.HTTPBadRequest( _('Invalid query string parameters: %(exc)s') % {'exc': exc})
def __call__(self, environ, start_response): # All requests but '/' require admin. if environ['PATH_INFO'] != '/': context = environ['placement.context'] # TODO(cdent): Using is_admin everywhere (except /) is # insufficiently flexible for future use case but is # convenient for initial exploration. if not policy.placement_authorize(context, 'placement'): raise webob.exc.HTTPForbidden( _('admin required'), json_formatter=util.json_error_formatter) # Check that an incoming write-oriented request method has # the required content-type header. If not raise a 400. If # this doesn't happen here then webob.dec.wsgify (elsewhere # in the stack) will raise an uncaught KeyError. Since that # is such a generic exception we cannot merely catch it # here, we need to avoid it ever happening. # TODO(cdent): Move this and the auth checking above into # middleware. It shouldn't be here. This is for dispatch not # validation or authorization. request_method = environ['REQUEST_METHOD'].upper() if request_method in ('POST', 'PUT', 'PATCH'): if 'CONTENT_TYPE' not in environ: raise webob.exc.HTTPBadRequest( _('content-type header required'), json_formatter=util.json_error_formatter) try: return dispatch(environ, start_response, self._map) # Trap the NotFound exceptions raised by the objects used # with the API and transform them into webob.exc.HTTPNotFound. except exception.NotFound as exc: raise webob.exc.HTTPNotFound( exc, json_formatter=util.json_error_formatter) # Trap the HTTPNotFound that can be raised by dispatch() # when no route is found. The exception is passed through to # the FaultWrap middleware without causing an alarming log # message. except webob.exc.HTTPNotFound: raise except Exception as exc: LOG.exception(_LE("Uncaught exception")) raise
def setup_commands(config): # This is a separate method because it facilitates unit testing. # Use an additional SubCommandOpt and parser for each new sub command. add_db_cmd_parsers = functools.partial(add_db_command_parsers, config=config) command_opt = cfg.SubCommandOpt('db', dest='command', title='Command', help=_('Available DB commands'), handler=add_db_cmd_parsers) return [command_opt]
def delete_resource_class(req): """DELETE to destroy a single resource class. On success return a 204 and an empty body. """ name = util.wsgi_path_item(req.environ, 'name') context = req.environ['placement.context'] context.can(policies.DELETE) # The containing application will catch a not found here. rc = rp_obj.ResourceClass.get_by_name(context, name) try: rc.destroy() except exception.ResourceClassCannotDeleteStandard as exc: raise webob.exc.HTTPBadRequest( _('Error in delete resource class: %(error)s') % {'error': exc}) except exception.ResourceClassInUse as exc: raise webob.exc.HTTPConflict( _('Error in delete resource class: %(error)s') % {'error': exc}) req.response.status = 204 req.response.content_type = None return req.response
def create_inventory(req): """POST to create one inventory. On success return a 201 response, a location header pointing to the newly created inventory and an application/json representation of the inventory. """ context = req.environ['placement.context'] context.can(policies.CREATE) uuid = util.wsgi_path_item(req.environ, 'uuid') resource_provider = rp_obj.ResourceProvider.get_by_uuid( context, uuid) data = _extract_inventory(req.body, schema.POST_INVENTORY_SCHEMA) resource_class = data.pop('resource_class') inventory = make_inventory_object(resource_provider, resource_class, **data) try: _validate_inventory_capacity( req.environ[microversion.MICROVERSION_ENVIRON], inventory) resource_provider.add_inventory(inventory) except (exception.ConcurrentUpdateDetected, db_exc.DBDuplicateEntry) as exc: raise webob.exc.HTTPConflict( _('Update conflict: %(error)s') % {'error': exc}, comment=errors.CONCURRENT_UPDATE) except (exception.InvalidInventoryCapacity, exception.NotFound) as exc: raise webob.exc.HTTPBadRequest( _('Unable to create inventory for resource provider ' '%(rp_uuid)s: %(error)s') % {'rp_uuid': resource_provider.uuid, 'error': exc}) response = req.response response.location = util.inventory_url( req.environ, resource_provider, resource_class) return _send_inventory(req, resource_provider, inventory, status=201)
def decorated_function(req): if req.content_type != content_type: # webob's unset content_type is the empty string so # set it the error message content to 'None' to make # a useful message in that case. raise webob.exc.HTTPUnsupportedMediaType( _('The media type %(bad_type)s is not supported, ' 'use %(good_type)s') % {'bad_type': req.content_type or 'None', 'good_type': content_type}, json_formatter=json_error_formatter) else: return f(req)
def delete_resource_provider(req): """DELETE to destroy a single resource provider. On success return a 204 and an empty body. """ uuid = util.wsgi_path_item(req.environ, 'uuid') context = req.environ['placement.context'] # The containing application will catch a not found here. try: resource_provider = objects.ResourceProvider.get_by_uuid( context, uuid) resource_provider.destroy() except exception.ResourceProviderInUse as exc: raise webob.exc.HTTPConflict( _('Unable to delete resource provider %(rp_uuid)s: %(error)s') % {'rp_uuid': uuid, 'error': exc}) except exception.NotFound as exc: raise webob.exc.HTTPNotFound( _("No resource provider with uuid %s found for delete") % uuid) req.response.status = 204 req.response.content_type = None return req.response
def _set_aggregates(resource_provider, aggregate_uuids, increment_generation=False): """Set aggregates for the resource provider. If increment generation is true, the resource provider generation will be incremented if possible. If that fails (because something else incremented the generation in another thread), a ConcurrentUpdateDetected will be raised. """ # NOTE(cdent): It's not clear what the DBDuplicateEntry handling # is doing here, set_aggregates already handles that, but I'm leaving # it here because it was already there. try: resource_provider.set_aggregates( aggregate_uuids, increment_generation=increment_generation) except exception.ConcurrentUpdateDetected as exc: raise webob.exc.HTTPConflict( _('Update conflict: %(error)s') % {'error': exc}, comment=errors.CONCURRENT_UPDATE) except db_exc.DBDuplicateEntry as exc: raise webob.exc.HTTPConflict( _('Update conflict: %(error)s') % {'error': exc})
def create_inventory(req): """POST to create one inventory. On success return a 201 response, a location header pointing to the newly created inventory and an application/json representation of the inventory. """ context = req.environ['placement.context'] uuid = util.wsgi_path_item(req.environ, 'uuid') resource_provider = objects.ResourceProvider.get_by_uuid( context, uuid) data = _extract_inventory(req.body, POST_INVENTORY_SCHEMA) resource_class = data.pop('resource_class') inventory = _make_inventory_object(resource_provider, resource_class, **data) try: resource_provider.add_inventory(inventory) except (exception.ConcurrentUpdateDetected, db_exc.DBDuplicateEntry) as exc: raise webob.exc.HTTPConflict( _('Update conflict: %(error)s') % {'error': exc}, json_formatter=util.json_error_formatter) except (exception.InvalidInventoryCapacity, exception.NotFound) as exc: raise webob.exc.HTTPBadRequest( _('Unable to create inventory for resource provider ' '%(rp_uuid)s: %(error)s') % {'rp_uuid': resource_provider.uuid, 'error': exc}, json_formatter=util.json_error_formatter) response = req.response response.location = util.inventory_url( req.environ, resource_provider, resource_class) return _send_inventory(response, resource_provider, inventory, status=201)
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 upgrade(migrate_engine): meta = MetaData() meta.bind = migrate_engine flavors = Table('flavors', meta, autoload=True) count = select([func.count()]).select_from(flavors).scalar() if count == 0: # NOTE(danms): We need to be careful here if this is a new # installation, which can't possibly have any mappings. Check # to see if any flavors are defined to determine if we are # upgrading an existing system. If not, then don't obsess over # the lack of mappings return cell_mappings = Table('cell_mappings', meta, autoload=True) count = select([func.count()]).select_from(cell_mappings).scalar() # Two mappings are required at a minimum, cell0 and your first cell if count < 2: msg = _('Cell mappings are not created, but required for Ocata. ' 'Please run nova-manage cell_v2 simple_cell_setup before ' 'continuing.') raise exception.ValidationError(detail=msg) count = select([func.count()]).select_from(cell_mappings).where( cell_mappings.c.uuid == objects.CellMapping.CELL0_UUID).scalar() if count != 1: msg = _('A mapping for Cell0 was not found, but is required for ' 'Ocata. Please run nova-manage cell_v2 simple_cell_setup ' 'before continuing.') raise exception.ValidationError(detail=msg) host_mappings = Table('host_mappings', meta, autoload=True) count = select([func.count()]).select_from(host_mappings).scalar() if count == 0: LOG.warning('No host mappings were found, but are required for Ocata. ' 'Please run nova-manage cell_v2 simple_cell_setup before ' 'continuing.')
def add_db_command_parsers(subparsers, config): command_object = DbCommands(config) # If we set False here, we avoid having an exit during the parse # args part of CONF processing and we can thus print out meaningful # help text. subparsers.required = False parser = subparsers.add_parser('db') # Avoid https://bugs.python.org/issue9351 with cpython < 2.7.9 if not six.PY2: parser.set_defaults(func=parser.print_help) db_parser = parser.add_subparsers(description='database commands') help = _('Sync the datatabse to the current version.') sync_parser = db_parser.add_parser('sync', help=help, description=help) sync_parser.set_defaults(func=command_object.db_sync) help = _('Report the current database version.') version_parser = db_parser.add_parser('version', help=help, description=help) version_parser.set_defaults(func=command_object.db_version) help = _('Stamp the revision table with the given version.') stamp_parser = db_parser.add_parser('stamp', help=help, description=help) stamp_parser.add_argument('version', help=_('the version to stamp')) stamp_parser.set_defaults(func=command_object.db_stamp) help = _('Run the online data migrations.') online_dm_parser = db_parser.add_parser('online_data_migrations', help=help, description=help) online_dm_parser.add_argument('--max-count', metavar='<number>', help='Maximum number of objects to consider') online_dm_parser.set_defaults( func=command_object.db_online_data_migrations)
def __call__(self, environ, start_response): # Check that an incoming request with a content-length header # that is an integer > 0 and not empty, also has a content-type # header that is not empty. If not raise a 400. clen = environ.get('CONTENT_LENGTH') try: if clen and (int(clen) > 0) and not environ.get('CONTENT_TYPE'): raise webob.exc.HTTPBadRequest( _('content-type header required when content-length > 0'), json_formatter=util.json_error_formatter) except ValueError as exc: raise webob.exc.HTTPBadRequest( _('content-length header must be an integer'), json_formatter=util.json_error_formatter) try: return dispatch(environ, start_response, self._map) # Trap the NotFound exceptions raised by the objects used # with the API and transform them into webob.exc.HTTPNotFound. except exception.NotFound as exc: raise webob.exc.HTTPNotFound( exc, json_formatter=util.json_error_formatter) except exception.PolicyNotAuthorized as exc: raise webob.exc.HTTPForbidden( exc.format_message(), json_formatter=util.json_error_formatter)
def create(self): if self.id is not None: raise exception.ObjectActionError(action='create', reason='already created') if not self.name: raise exception.ObjectActionError(action='create', reason='name is required') if self.name in orc.STANDARDS: raise exception.ResourceClassExists(resource_class=self.name) if not self.name.startswith(orc.CUSTOM_NAMESPACE): raise exception.ObjectActionError( action='create', reason='name must start with ' + orc.CUSTOM_NAMESPACE) updates = {} for field in ['name', 'updated_at', 'created_at']: value = getattr(self, field, None) if value: updates[field] = value # There is the possibility of a race when adding resource classes, as # the ID is generated locally. This loop catches that exception, and # retries until either it succeeds, or a different exception is # encountered. retries = self.RESOURCE_CREATE_RETRY_COUNT while retries: retries -= 1 try: rc = self._create_in_db(self._context, updates) self._from_db_object(self._context, self, rc) break except db_exc.DBDuplicateEntry as e: if 'id' in e.columns: # Race condition for ID creation; try again continue # The duplication is on the other unique column, 'name'. So do # not retry; raise the exception immediately. raise exception.ResourceClassExists(resource_class=self.name) else: # We have no idea how common it will be in practice for the retry # limit to be exceeded. We set it high in the hope that we never # hit this point, but added this log message so we know that this # specific situation occurred. LOG.warning("Exceeded retry limit on ID generation while " "creating ResourceClass %(name)s", {'name': self.name}) msg = _("creating resource class %s") % self.name raise exception.MaxDBRetriesExceeded(action=msg)
def normalize_in_tree_qs_params(value): """Parse a in_tree query string parameter value. :param value: in_tree query parameter: A UUID of a resource provider. :return: A UUID of a resource provider. :raises `webob.exc.HTTPBadRequest` if the val parameter is not in the expected format. """ ret = value.strip() if not uuidutils.is_uuid_like(ret): msg = _("Invalid query string parameters: Expected 'in_tree' " "parameter to be a format of uuid. " "Got: %(val)s") % { 'val': value } raise webob.exc.HTTPBadRequest(msg) return ret
def _normalize_traits_qs_param(qs): try: op, value = qs.split(':', 1) except ValueError: msg = _('Badly formatted name parameter. Expected name query string ' 'parameter in form: ' '?name=[in|startswith]:[name1,name2|prefix]. Got: "%s"') msg = msg % qs raise webob.exc.HTTPBadRequest(msg) filters = {} if op == 'in': filters['name_in'] = value.split(',') elif op == 'startswith': filters['prefix'] = value return filters
def make_inventory_object(resource_provider, resource_class, **data): """Single place to catch malformed Inventories.""" # TODO(cdent): Some of the validation checks that are done here # could be done via JSONschema (using, for example, "minimum": # 0) for non-negative integers. It's not clear if that is # duplication or decoupling so leaving it as this for now. try: inventory = inv_obj.Inventory( resource_provider=resource_provider, resource_class=resource_class, **data) except (ValueError, TypeError) as exc: raise webob.exc.HTTPBadRequest( _('Bad inventory %(class)s for resource provider ' '%(rp_uuid)s: %(error)s') % {'class': resource_class, 'rp_uuid': resource_provider.uuid, 'error': exc}) return inventory
def decorated_function(req): if req.content_type != content_type: # webob's unset content_type is the empty string so # set it the error message content to 'None' to make # a useful message in that case. This also avoids a # KeyError raised when webob.exc eagerly fills in a # Template for output we will never use. if not req.content_type: req.content_type = 'None' raise webob.exc.HTTPUnsupportedMediaType( _('The media type %(bad_type)s is not supported, ' 'use %(good_type)s') % { 'bad_type': req.content_type, 'good_type': content_type }, json_formatter=json_error_formatter) else: return f(req)
def handle_405(environ, start_response): """Return a 405 response when method is not allowed. If _methods are in routing_args, send an allow header listing the methods that are possible on the provided URL. """ _methods = util.wsgi_path_item(environ, '_methods') headers = {} if _methods: # Ensure allow header is a python 2 or 3 native string (thus # not unicode in python 2 but stay a string in python 3) # In the process done by Routes to save the allowed methods # to its routing table they become unicode in py2. headers['allow'] = str(_methods) raise webob.exc.HTTPMethodNotAllowed( _('The method specified is not allowed for this resource.'), headers=headers, json_formatter=util.json_error_formatter)
def _fix_forbidden(by_suffix): conflicting_traits = [] for suff, group in by_suffix.items(): forbidden = [ trait for trait in group.required_traits if trait.startswith('!') ] group.required_traits = group.required_traits - set(forbidden) group.forbidden_traits = set( [trait.lstrip('!') for trait in forbidden]) conflicts = group.forbidden_traits & group.required_traits if conflicts: conflicting_traits.append('required%s: (%s)' % (suff, ', '.join(conflicts))) if conflicting_traits: msg = _('Conflicting required and forbidden traits found in the ' 'following traits keys: %s') raise webob.exc.HTTPBadRequest(msg % ', '.join(conflicting_traits))
def get_inventories(req): """GET a list of inventories. On success return a 200 with an application/json body representing a collection of inventories. """ context = req.environ['placement.context'] context.can(policies.LIST) uuid = util.wsgi_path_item(req.environ, 'uuid') try: rp = rp_obj.ResourceProvider.get_by_uuid(context, uuid) except exception.NotFound as exc: raise webob.exc.HTTPNotFound( _("No resource provider with uuid %(uuid)s found : %(error)s") % {'uuid': uuid, 'error': exc}) inv_list = inv_obj.get_all_by_resource_provider(context, rp) return _send_inventories(req, rp, inv_list)
def list_usages(req): """GET a dictionary of resource provider usage by resource class. If the resource provider does not exist return a 404. On success return a 200 with an application/json representation of the usage dictionary. """ context = req.environ['placement.context'] context.can(policies.PROVIDER_USAGES) uuid = util.wsgi_path_item(req.environ, 'uuid') want_version = req.environ[microversion.MICROVERSION_ENVIRON] # Resource provider object needed for two things: If it is # NotFound we'll get a 404 here, which needs to happen because # get_all_by_resource_provider_uuid can return an empty list. # It is also needed for the generation, used in the outgoing # representation. try: resource_provider = rp_obj.ResourceProvider.get_by_uuid(context, uuid) except exception.NotFound as exc: raise webob.exc.HTTPNotFound( _("No resource provider with uuid %(uuid)s found: %(error)s") % { 'uuid': uuid, 'error': exc }) usage = rp_obj.UsageList.get_all_by_resource_provider_uuid(context, uuid) response = req.response response.body = encodeutils.to_utf8( jsonutils.dumps(_serialize_usages(resource_provider, usage))) req.response.content_type = 'application/json' if want_version.matches((1, 15)): req.response.cache_control = 'no-cache' # While it would be possible to generate a last-modified time # based on the collection of allocations that result in a usage # value (with some spelunking in the SQL) that doesn't align with # the question that is being asked in a request for usages: What # is the usage, now? So the last-modified time is set to utcnow. req.response.last_modified = timeutils.utcnow(with_timezone=True) return req.response
def normalize_member_of_qs_params(req, suffix=''): """Given a webob.Request object, validate that the member_of querystring parameters are correct. We begin supporting multiple member_of params in microversion 1.24. :param req: webob.Request object :return: A list containing sets of UUIDs of aggregates to filter on :raises `webob.exc.HTTPBadRequest` if the microversion requested is <1.24 and the request contains multiple member_of querystring params :raises `webob.exc.HTTPBadRequest` if the val parameter is not in the expected format. """ want_version = req.environ[placement.microversion.MICROVERSION_ENVIRON] multi_member_of = want_version.matches((1, 24)) if not multi_member_of and len(req.GET.getall('member_of' + suffix)) > 1: raise webob.exc.HTTPBadRequest( _('Multiple member_of%s parameters are not supported') % suffix) values = [] for value in req.GET.getall('member_of' + suffix): values.append(normalize_member_of_qs_param(value)) return values