diff --git a/nova/api/openstack/compute/console_auth_tokens.py b/nova/api/openstack/compute/console_auth_tokens.py index 387983f53f3b..b2df76464381 100644 --- a/nova/api/openstack/compute/console_auth_tokens.py +++ b/nova/api/openstack/compute/console_auth_tokens.py @@ -15,6 +15,7 @@ import webob +from nova.api.openstack import api_version_request from nova.api.openstack.compute.schemas import console_auth_tokens as schema from nova.api.openstack import wsgi from nova.api import validation @@ -29,6 +30,34 @@ CONF = nova.conf.CONF class ConsoleAuthTokensController(wsgi.Controller): + @wsgi.expected_errors((400, 401, 404), '2.1', '2.30') + @wsgi.expected_errors((400, 404), '2.31', '2.98') + @wsgi.expected_errors((400, 404), '2.99') + @validation.query_schema(schema.show_query, '2.1', '2.98') + @validation.query_schema(schema.show_query_v299, '2.99') + # NOTE(stephenfin): Technically this will never return a response now for + # microversion <= 2.30, (as an exception will be raised instead) but we use + # the same schema for documentation purposes + @validation.response_body_schema(schema.show_response, '2.1', '2.98') + @validation.response_body_schema(schema.show_response_v299, '2.99') + def show(self, req, id): + """Show console auth token. + + Until microversion 2.30, this API was available only for the rdp-html5 + console type which has been removed along with the HyperV driver in the + Nova 29.0.0 (Caracal) release. As a result, we now return a HTTP 400 + error for microversion <= 2.30. Starting from 2.31 microversion, this + API works for all the other supported console types. + """ + if not api_version_request.is_supported(req, '2.31'): + raise webob.exc.HTTPBadRequest() + + include_tls_port = False + if api_version_request.is_supported(req, '2.99'): + include_tls_port = True + + return self._show(req, id, include_tls_port=include_tls_port) + def _show(self, req, id, include_tls_port=False): """Checks a console auth token and returns the related connect info.""" context = req.environ['nova.context'] @@ -70,36 +99,3 @@ class ConsoleAuthTokensController(wsgi.Controller): retval['console']['tls_port'] = connect_info.tls_port return retval - - @wsgi.Controller.api_version("2.1", "2.30") - @wsgi.expected_errors((400, 401, 404)) - @validation.query_schema(schema.show_query) - # NOTE(stephenfin): Technically this will never return a response now (as - # an exception will be raised instead) but we use the same schema for - # documentation purposes - @validation.response_body_schema(schema.show_response) - def show(self, req, id): - """Until microversion 2.30, this API was available only for the - rdp-html5 console type which has been removed along with the HyperV - driver in the Nova 29.0.0 (Caracal) release. As this method is for - microversion <= 2.30, it will return an http 400 error. Starting - from 2.31 microversion, this API works for all the supported - console types that are handled by the separate show method - defined below. - """ - raise webob.exc.HTTPBadRequest() - - @wsgi.Controller.api_version("2.31") # noqa - @wsgi.Controller.api_version("2.31", "2.98") # noqa - @wsgi.expected_errors((400, 404)) - @validation.query_schema(schema.show_query) - @validation.response_body_schema(schema.show_response) - def show(self, req, id): # noqa - return self._show(req, id, include_tls_port=False) - - @wsgi.Controller.api_version("2.99") # noqa - @wsgi.expected_errors((400, 404)) - @validation.query_schema(schema.show_query_v299) - @validation.response_body_schema(schema.show_response_v299) - def show(self, req, id): # noqa - return self._show(req, id, include_tls_port=True) diff --git a/nova/api/openstack/compute/hypervisors.py b/nova/api/openstack/compute/hypervisors.py index 0cd7fee1b534..9ae93cd6a2df 100644 --- a/nova/api/openstack/compute/hypervisors.py +++ b/nova/api/openstack/compute/hypervisors.py @@ -23,7 +23,7 @@ import webob.exc from nova.api.openstack import api_version_request from nova.api.openstack import common -from nova.api.openstack.compute.schemas import hypervisors as hyper_schema +from nova.api.openstack.compute.schemas import hypervisors as schema from nova.api.openstack.compute.views import hypervisors as hyper_view from nova.api.openstack import wsgi from nova.api import validation @@ -223,31 +223,29 @@ class HypervisorsController(wsgi.Controller): hypervisors_dict['hypervisors_links'] = hypervisors_links return hypervisors_dict - @wsgi.Controller.api_version("2.53") - @wsgi.expected_errors((400, 404)) - @validation.query_schema(hyper_schema.index_query_v253, "2.53") + @wsgi.expected_errors((), '2.1', '2.32') + @wsgi.expected_errors(400, '2.33', '2.52') + @wsgi.expected_errors((400, 404), '2.53') + @validation.query_schema(schema.index_query, '2.1', '2.32') + @validation.query_schema(schema.index_query_v233, '2.33', '2.52') + @validation.query_schema(schema.index_query_v253, '2.53') def index(self, req): - """Starting with the 2.53 microversion, the id field in the response + """List hypervisors. + + Starting with the 2.53 microversion, the id field in the response is the compute_nodes.uuid value. Also, the search and servers routes are superseded and replaced with query parameters for listing hypervisors by a hostname pattern and whether or not to include hosted servers in the response. """ - limit, marker = common.get_limit_and_marker(req) - return self._index(req, limit=limit, marker=marker, links=True) + limit = None + marker = None + links = False + if api_version_request.is_supported(req, '2.33'): + limit, marker = common.get_limit_and_marker(req) + links = True - @wsgi.Controller.api_version("2.33", "2.52") # noqa - @wsgi.expected_errors(400) - @validation.query_schema(hyper_schema.index_query_v233) - def index(self, req): # noqa - limit, marker = common.get_limit_and_marker(req) - return self._index(req, limit=limit, marker=marker, links=True) - - @wsgi.Controller.api_version("2.1", "2.32") # noqa - @wsgi.expected_errors(()) - @validation.query_schema(hyper_schema.index_query) - def index(self, req): # noqa - return self._index(req) + return self._index(req, limit=limit, marker=marker, links=links) def _index(self, req, limit=None, marker=None, links=False): context = req.environ['nova.context'] @@ -255,31 +253,29 @@ class HypervisorsController(wsgi.Controller): return self._get_hypervisors(req, detail=False, limit=limit, marker=marker, links=links) - @wsgi.Controller.api_version("2.53") - @wsgi.expected_errors((400, 404)) - @validation.query_schema(hyper_schema.index_query_v253, "2.53") + @wsgi.expected_errors((), '2.1', '2.32') + @wsgi.expected_errors((400), '2.33', '2.52') + @wsgi.expected_errors((400, 404), '2.53') + @validation.query_schema(schema.index_query, '2.1', '2.32') + @validation.query_schema(schema.index_query_v233, '2.33', '2.52') + @validation.query_schema(schema.index_query_v253, '2.53') def detail(self, req): - """Starting with the 2.53 microversion, the id field in the response + """List hypervisors with extra details. + + Starting with the 2.53 microversion, the id field in the response is the compute_nodes.uuid value. Also, the search and servers routes are superseded and replaced with query parameters for listing hypervisors by a hostname pattern and whether or not to include hosted servers in the response. """ - limit, marker = common.get_limit_and_marker(req) - return self._detail(req, limit=limit, marker=marker, links=True) + limit = None + marker = None + links = False + if api_version_request.is_supported(req, '2.33'): + limit, marker = common.get_limit_and_marker(req) + links = True - @wsgi.Controller.api_version("2.33", "2.52") # noqa - @wsgi.expected_errors((400)) - @validation.query_schema(hyper_schema.index_query_v233) - def detail(self, req): # noqa - limit, marker = common.get_limit_and_marker(req) - return self._detail(req, limit=limit, marker=marker, links=True) - - @wsgi.Controller.api_version("2.1", "2.32") # noqa - @wsgi.expected_errors(()) - @validation.query_schema(hyper_schema.index_query) - def detail(self, req): # noqa - return self._detail(req) + return self._detail(req, limit=limit, marker=marker, links=links) def _detail(self, req, limit=None, marker=None, links=False): context = req.environ['nova.context'] @@ -311,25 +307,25 @@ class HypervisorsController(wsgi.Controller): hypervisor_id) raise webob.exc.HTTPNotFound(explanation=msg) - @wsgi.Controller.api_version("2.53") - @wsgi.expected_errors((400, 404)) - @validation.query_schema(hyper_schema.show_query_v253, "2.53") + @wsgi.expected_errors(404, '2.1', '2.52') + @wsgi.expected_errors((400, 404), '2.53') + @validation.query_schema(schema.show_query, '2.1', '2.52') + @validation.query_schema(schema.show_query_v253, '2.53') def show(self, req, id): - """The 2.53 microversion requires that the id is a uuid and as a result + """Show a hypervisor. + + The 2.53 microversion requires that the id is a uuid and as a result it can also return a 400 response if an invalid uuid is passed. The 2.53 microversion also supports the with_servers query parameter to include a list of servers on the given hypervisor if requested. """ - with_servers = strutils.bool_from_string( - req.GET.get('with_servers', False), strict=True) - return self._show(req, id, with_servers) + with_servers = False + if api_version_request.is_supported(req, '2.53'): + with_servers = strutils.bool_from_string( + req.GET.get('with_servers', False), strict=True) - @wsgi.Controller.api_version("2.1", "2.52") # noqa F811 - @wsgi.expected_errors(404) - @validation.query_schema(hyper_schema.show_query) - def show(self, req, id): # noqa - return self._show(req, id) + return self._show(req, id, with_servers=with_servers) def _show(self, req, id, with_servers=False): context = req.environ['nova.context'] @@ -372,7 +368,7 @@ class HypervisorsController(wsgi.Controller): @wsgi.Controller.api_version('2.1', '2.87') @wsgi.expected_errors((400, 404, 501)) - @validation.query_schema(hyper_schema.uptime_query) + @validation.query_schema(schema.uptime_query) def uptime(self, req, id): """Prior to microversion 2.88, you could retrieve a special version of the hypervisor detail view that included uptime. Starting in 2.88, this @@ -425,7 +421,7 @@ class HypervisorsController(wsgi.Controller): @wsgi.Controller.api_version('2.1', '2.52') @wsgi.expected_errors(404) - @validation.query_schema(hyper_schema.search_query) + @validation.query_schema(schema.search_query) def search(self, req, id): """Prior to microversion 2.53 you could search for hypervisors by a hostname pattern on a dedicated route. Starting with 2.53, searching @@ -464,7 +460,7 @@ class HypervisorsController(wsgi.Controller): @wsgi.Controller.api_version('2.1', '2.52') @wsgi.expected_errors(404) - @validation.query_schema(hyper_schema.servers_query) + @validation.query_schema(schema.servers_query) def servers(self, req, id): """Prior to microversion 2.53 you could search for hypervisors by a hostname pattern and include servers on those hosts in the response on @@ -510,7 +506,7 @@ class HypervisorsController(wsgi.Controller): @wsgi.Controller.api_version('2.1', '2.87') @wsgi.expected_errors(()) - @validation.query_schema(hyper_schema.statistics_query) + @validation.query_schema(schema.statistics_query) def statistics(self, req): """Prior to microversion 2.88, you could get statistics for the hypervisor. Most of these are now accessible from placement and the few diff --git a/nova/api/openstack/compute/instance_actions.py b/nova/api/openstack/compute/instance_actions.py index d25024102fbf..2d5e05215cc0 100644 --- a/nova/api/openstack/compute/instance_actions.py +++ b/nova/api/openstack/compute/instance_actions.py @@ -19,8 +19,7 @@ from oslo_utils import timeutils from nova.api.openstack import api_version_request from nova.api.openstack import common -from nova.api.openstack.compute.schemas \ - import instance_actions as schema_instance_actions +from nova.api.openstack.compute.schemas import instance_actions as schema from nova.api.openstack.compute.views \ import instance_actions as instance_actions_view from nova.api.openstack import wsgi @@ -72,58 +71,46 @@ class InstanceActionsController(wsgi.Controller): event['details'] = event_raw['details'] return event - @wsgi.Controller.api_version("2.1", "2.20") def _get_instance(self, req, context, server_id): - return common.get_instance(self.compute_api, context, server_id) + if not api_version_request.is_supported(req, min_version="2.21"): + return common.get_instance(self.compute_api, context, server_id) - @wsgi.Controller.api_version("2.21") # noqa - def _get_instance(self, req, context, server_id): # noqa with utils.temporary_mutation(context, read_deleted='yes'): return common.get_instance(self.compute_api, context, server_id) - @wsgi.Controller.api_version("2.1", "2.57") - @wsgi.expected_errors(404) - @validation.query_schema(schema_instance_actions.list_query) + @wsgi.expected_errors(404, "2.1", "2.57") + @wsgi.expected_errors((400, 404), "2.58") + @validation.query_schema(schema.list_query, "2.1", "2.57") + @validation.query_schema(schema.list_query_v258, "2.58", "2.65") + @validation.query_schema(schema.list_query_v266, "2.66") def index(self, req, server_id): """Returns the list of actions recorded for a given instance.""" context = req.environ["nova.context"] instance = self._get_instance(req, context, server_id) context.can(ia_policies.BASE_POLICY_NAME % 'list', target={'project_id': instance.project_id}) - actions_raw = self.action_api.actions_get(context, instance) - actions = [self._format_action(action, ACTION_KEYS) - for action in actions_raw] - return {'instanceActions': actions} - @wsgi.Controller.api_version("2.58") # noqa - @wsgi.expected_errors((400, 404)) - @validation.query_schema(schema_instance_actions.list_query_v266, - "2.66") - @validation.query_schema(schema_instance_actions.list_query_v258, - "2.58", "2.65") - def index(self, req, server_id): # noqa - """Returns the list of actions recorded for a given instance.""" - context = req.environ["nova.context"] - instance = self._get_instance(req, context, server_id) - context.can(ia_policies.BASE_POLICY_NAME % 'list', - target={'project_id': instance.project_id}) - search_opts = {} - search_opts.update(req.GET) - if 'changes-since' in search_opts: - search_opts['changes-since'] = timeutils.parse_isotime( - search_opts['changes-since']) + if api_version_request.is_supported(req, '2.58'): + search_opts = {} + search_opts.update(req.GET) + if 'changes-since' in search_opts: + search_opts['changes-since'] = timeutils.parse_isotime( + search_opts['changes-since']) - if 'changes-before' in search_opts: - search_opts['changes-before'] = timeutils.parse_isotime( - search_opts['changes-before']) - changes_since = search_opts.get('changes-since') - if (changes_since and search_opts['changes-before'] < - search_opts['changes-since']): - msg = _('The value of changes-since must be less than ' - 'or equal to changes-before.') - raise exc.HTTPBadRequest(explanation=msg) + if 'changes-before' in search_opts: + search_opts['changes-before'] = timeutils.parse_isotime( + search_opts['changes-before']) + changes_since = search_opts.get('changes-since') + if (changes_since and search_opts['changes-before'] < + search_opts['changes-since']): + msg = _('The value of changes-since must be less than ' + 'or equal to changes-before.') + raise exc.HTTPBadRequest(explanation=msg) + + limit, marker = common.get_limit_and_marker(req) + else: + limit, marker, search_opts = None, None, None - limit, marker = common.get_limit_and_marker(req) try: actions_raw = self.action_api.actions_get(context, instance, limit=limit, @@ -131,16 +118,25 @@ class InstanceActionsController(wsgi.Controller): filters=search_opts) except exception.MarkerNotFound as e: raise exc.HTTPBadRequest(explanation=e.format_message()) - actions = [self._format_action(action, ACTION_KEYS_V258) - for action in actions_raw] + + if api_version_request.is_supported(req, min_version="2.58"): + actions = [self._format_action(action, ACTION_KEYS_V258) + for action in actions_raw] + else: + actions = [self._format_action(action, ACTION_KEYS) + for action in actions_raw] actions_dict = {'instanceActions': actions} - actions_links = self._view_builder.get_links(req, server_id, actions) - if actions_links: - actions_dict['links'] = actions_links + + if api_version_request.is_supported(req, '2.58'): + if actions_links := self._view_builder.get_links( + req, server_id, actions + ): + actions_dict['links'] = actions_links + return actions_dict @wsgi.expected_errors(404) - @validation.query_schema(schema_instance_actions.show_query) + @validation.query_schema(schema.show_query) def show(self, req, server_id, id): """Return data about the given instance action.""" context = req.environ['nova.context'] @@ -186,8 +182,7 @@ class InstanceActionsController(wsgi.Controller): # NOTE(brinzhang): Event details are shown since microversion # 2.84. show_details = False - support_v284 = api_version_request.is_supported(req, '2.84') - if support_v284: + if api_version_request.is_supported(req, '2.84'): show_details = context.can( ia_policies.BASE_POLICY_NAME % 'events:details', target={'project_id': instance.project_id}, fatal=False) diff --git a/nova/api/openstack/compute/keypairs.py b/nova/api/openstack/compute/keypairs.py index 4d121b8fd74f..3fed90083da6 100644 --- a/nova/api/openstack/compute/keypairs.py +++ b/nova/api/openstack/compute/keypairs.py @@ -40,67 +40,39 @@ class KeypairController(wsgi.Controller): super(KeypairController, self).__init__() self.api = compute_api.KeypairAPI() - @wsgi.Controller.api_version("2.10") - @wsgi.response(201) + @wsgi.response(200, "2.0", "2.1") + @wsgi.response(201, "2.2") @wsgi.expected_errors((400, 403, 409)) + @validation.schema(keypairs.create_v20, "2.0", "2.0") + @validation.schema(keypairs.create, "2.1", "2.1") + @validation.schema(keypairs.create_v22, "2.2", "2.9") @validation.schema(keypairs.create_v210, "2.10", "2.91") @validation.schema(keypairs.create_v292, "2.92") def create(self, req, body): """Create or import keypair. - Keypair generations are allowed until version 2.91. - Afterwards, only imports are allowed. + Sending name will generate a key and return private_key and + fingerprint. You can send a public_key to add an existing ssh key. - A policy check restricts users from creating keys for other users + Starting in API microversion 2.2, keypairs will have the type ssh or + x509, specified by type. - params: keypair object with: - name (required) - string - public_key (optional or required if >=2.92) - string - type (optional) - string - user_id (optional) - string + Starting in API microversion 2.10, you can request a user if you are an + admin. + + Starting in API microversion 2.91, keypair generation is no longer + permitted. """ - # handle optional user-id for admin only - user_id = body['keypair'].get('user_id') - return self._create(req, body, key_type=True, user_id=user_id) + key_type = False + if api_version_request.is_supported(req, '2.2'): + key_type = True - @wsgi.Controller.api_version("2.2", "2.9") # noqa - @wsgi.response(201) - @wsgi.expected_errors((400, 403, 409)) - @validation.schema(keypairs.create_v22) - def create(self, req, body): # noqa - """Create or import keypair. + user_id = None + if api_version_request.is_supported(req, '2.10'): + # handle optional user-id for admin only + user_id = body['keypair'].get('user_id') - Sending name will generate a key and return private_key - and fingerprint. - - Keypair will have the type ssh or x509, specified by type. - - You can send a public_key to add an existing ssh/x509 key. - - params: keypair object with: - name (required) - string - public_key (optional) - string - type (optional) - string - """ - return self._create(req, body, key_type=True) - - @wsgi.Controller.api_version("2.1", "2.1") # noqa - @wsgi.expected_errors((400, 403, 409)) - @validation.schema(keypairs.create_v20, "2.0", "2.0") - @validation.schema(keypairs.create, "2.1", "2.1") - def create(self, req, body): # noqa - """Create or import keypair. - - Sending name will generate a key and return private_key - and fingerprint. - - You can send a public_key to add an existing ssh key. - - params: keypair object with: - name (required) - string - public_key (optional) - string - """ - return self._create(req, body) + return self._create(req, body, key_type=key_type, user_id=user_id) def _create(self, req, body, user_id=None, key_type=False): context = req.environ['nova.context'] @@ -135,28 +107,23 @@ class KeypairController(wsgi.Controller): private_key=return_priv_key, key_type=key_type) - @wsgi.Controller.api_version("2.1", "2.1") - @validation.query_schema(keypairs.delete_query_schema_v20) - @wsgi.response(202) + def _get_user_id(self, req): + if 'user_id' in req.GET.keys(): + user_id = req.GET.getall('user_id')[0] + return user_id + + @wsgi.response(202, '2.0', '2.1') + @wsgi.response(204, '2.2') + @validation.query_schema(keypairs.delete_query_schema_v20, '2.1', '2.9') + @validation.query_schema(keypairs.delete_query_schema_v210, '2.10', '2.74') + @validation.query_schema(keypairs.delete_query_schema_v275, '2.75') @wsgi.expected_errors(404) def delete(self, req, id): - self._delete(req, id) + user_id = None + if api_version_request.is_supported(req, '2.10'): + # handle optional user-id for admin only + user_id = self._get_user_id(req) - @wsgi.Controller.api_version("2.2", "2.9") # noqa - @validation.query_schema(keypairs.delete_query_schema_v20) - @wsgi.response(204) - @wsgi.expected_errors(404) - def delete(self, req, id): # noqa - self._delete(req, id) - - @wsgi.Controller.api_version("2.10") # noqa - @validation.query_schema(keypairs.delete_query_schema_v275, '2.75') - @validation.query_schema(keypairs.delete_query_schema_v210, '2.10', '2.74') - @wsgi.response(204) - @wsgi.expected_errors(404) - def delete(self, req, id): # noqa - # handle optional user-id for admin only - user_id = self._get_user_id(req) self._delete(req, id, user_id=user_id) def _delete(self, req, id, user_id=None): @@ -171,31 +138,21 @@ class KeypairController(wsgi.Controller): except exception.KeypairNotFound as exc: raise webob.exc.HTTPNotFound(explanation=exc.format_message()) - def _get_user_id(self, req): - if 'user_id' in req.GET.keys(): - user_id = req.GET.getall('user_id')[0] - return user_id - - @wsgi.Controller.api_version("2.10") + @validation.query_schema(keypairs.show_query_schema_v20, '2.0', '2.9') @validation.query_schema(keypairs.show_query_schema_v210, '2.10', '2.74') @validation.query_schema(keypairs.show_query_schema_v275, '2.75') @wsgi.expected_errors(404) def show(self, req, id): - # handle optional user-id for admin only - user_id = self._get_user_id(req) - return self._show(req, id, key_type=True, user_id=user_id) + key_type = False + if api_version_request.is_supported(req, '2.2'): + key_type = True - @wsgi.Controller.api_version("2.2", "2.9") # noqa - @validation.query_schema(keypairs.show_query_schema_v20) - @wsgi.expected_errors(404) - def show(self, req, id): # noqa - return self._show(req, id, key_type=True) + user_id = None + if api_version_request.is_supported(req, '2.10'): + # handle optional user-id for admin only + user_id = self._get_user_id(req) - @wsgi.Controller.api_version("2.1", "2.1") # noqa - @validation.query_schema(keypairs.show_query_schema_v20) - @wsgi.expected_errors(404) - def show(self, req, id): # noqa - return self._show(req, id) + return self._show(req, id, key_type=key_type, user_id=user_id) def _show(self, req, id, key_type=False, user_id=None): """Return data for the given key name.""" @@ -210,33 +167,29 @@ class KeypairController(wsgi.Controller): raise webob.exc.HTTPNotFound(explanation=exc.format_message()) return self._view_builder.show(keypair, key_type=key_type) - @wsgi.Controller.api_version("2.35") - @validation.query_schema(keypairs.index_query_schema_v275, '2.75') + @validation.query_schema(keypairs.index_query_schema_v20, '2.0', '2.9') + @validation.query_schema(keypairs.index_query_schema_v210, '2.10', '2.34') @validation.query_schema(keypairs.index_query_schema_v235, '2.35', '2.74') - @wsgi.expected_errors(400) + @validation.query_schema(keypairs.index_query_schema_v275, '2.75') + @wsgi.expected_errors((), '2.0', '2.9') + @wsgi.expected_errors(400, '2.10') def index(self, req): - user_id = self._get_user_id(req) - return self._index(req, key_type=True, user_id=user_id, links=True) + key_type = False + if api_version_request.is_supported(req, '2.2'): + key_type = True - @wsgi.Controller.api_version("2.10", "2.34") # noqa - @validation.query_schema(keypairs.index_query_schema_v210) - @wsgi.expected_errors(()) - def index(self, req): # noqa - # handle optional user-id for admin only - user_id = self._get_user_id(req) - return self._index(req, key_type=True, user_id=user_id) + user_id = None + if api_version_request.is_supported(req, '2.10'): + # handle optional user-id for admin only + user_id = self._get_user_id(req) - @wsgi.Controller.api_version("2.2", "2.9") # noqa - @validation.query_schema(keypairs.index_query_schema_v20) - @wsgi.expected_errors(()) - def index(self, req): # noqa - return self._index(req, key_type=True) + links = False + if api_version_request.is_supported(req, '2.35'): + links = True - @wsgi.Controller.api_version("2.1", "2.1") # noqa - @validation.query_schema(keypairs.index_query_schema_v20) - @wsgi.expected_errors(()) - def index(self, req): # noqa - return self._index(req) + return self._index( + req, key_type=key_type, user_id=user_id, links=links + ) def _index(self, req, key_type=False, user_id=None, links=False): """List of keypairs for a user.""" @@ -245,7 +198,7 @@ class KeypairController(wsgi.Controller): context.can(kp_policies.POLICY_ROOT % 'index', target={'user_id': user_id}) - if api_version_request.is_supported(req, min_version='2.35'): + if api_version_request.is_supported(req, '2.35'): limit, marker = common.get_limit_and_marker(req) else: limit = marker = None diff --git a/nova/api/openstack/compute/limits.py b/nova/api/openstack/compute/limits.py index 755a6c365654..58e46cf2feed 100644 --- a/nova/api/openstack/compute/limits.py +++ b/nova/api/openstack/compute/limits.py @@ -13,14 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. -from nova.api.openstack.api_version_request \ - import MAX_IMAGE_META_PROXY_API_VERSION -from nova.api.openstack.api_version_request \ - import MAX_PROXY_API_SUPPORT_VERSION -from nova.api.openstack.api_version_request \ - import MIN_WITHOUT_IMAGE_META_PROXY_API_VERSION -from nova.api.openstack.api_version_request \ - import MIN_WITHOUT_PROXY_API_SUPPORT_VERSION +from nova.api.openstack import api_version_request from nova.api.openstack.compute.schemas import limits from nova.api.openstack.compute.views import limits as limits_views from nova.api.openstack import wsgi @@ -44,32 +37,23 @@ FILTERED_LIMITS_2_57.extend(['injected_files', 'injected_file_content_bytes']) class LimitsController(wsgi.Controller): """Controller for accessing limits in the OpenStack API.""" - @wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.expected_errors(()) - @validation.query_schema(limits.limits_query_schema) - def index(self, req): - return self._index(req) - - @wsgi.Controller.api_version(MIN_WITHOUT_PROXY_API_SUPPORT_VERSION, # noqa - MAX_IMAGE_META_PROXY_API_VERSION) - @wsgi.expected_errors(()) - @validation.query_schema(limits.limits_query_schema) - def index(self, req): # noqa - return self._index(req, FILTERED_LIMITS_2_36) - - @wsgi.Controller.api_version( # noqa - MIN_WITHOUT_IMAGE_META_PROXY_API_VERSION, '2.56') - @wsgi.expected_errors(()) - @validation.query_schema(limits.limits_query_schema) - def index(self, req): # noqa - return self._index(req, FILTERED_LIMITS_2_36, max_image_meta=False) - - @wsgi.Controller.api_version('2.57') # noqa - @wsgi.expected_errors(()) - @validation.query_schema(limits.limits_query_schema_275, '2.75') + @validation.query_schema(limits.limits_query_schema, '2.1', '2.56') @validation.query_schema(limits.limits_query_schema, '2.57', '2.74') - def index(self, req): # noqa - return self._index(req, FILTERED_LIMITS_2_57, max_image_meta=False) + @validation.query_schema(limits.limits_query_schema_275, '2.75') + def index(self, req): + filtered_limits = [] + if api_version_request.is_supported(req, '2.57'): + filtered_limits = FILTERED_LIMITS_2_57 + elif api_version_request.is_supported(req, '2.36'): + filtered_limits = FILTERED_LIMITS_2_36 + + max_image_meta = True + if api_version_request.is_supported(req, '2.39'): + max_image_meta = False + + return self._index(req, filtered_limits=filtered_limits, + max_image_meta=max_image_meta) def _index(self, req, filtered_limits=None, max_image_meta=True): """Return all global limit information.""" @@ -80,8 +64,7 @@ class LimitsController(wsgi.Controller): project_id = req.GET.get('tenant_id') context.can(limits_policies.OTHER_PROJECT_LIMIT_POLICY_NAME) - quotas = QUOTAS.get_project_quotas(context, project_id, - usages=True) + quotas = QUOTAS.get_project_quotas(context, project_id, usages=True) builder = limits_views.ViewBuilder() return builder.build(req, quotas, filtered_limits=filtered_limits, max_image_meta=max_image_meta) diff --git a/nova/api/openstack/compute/migrations.py b/nova/api/openstack/compute/migrations.py index a64d6160aba4..2cbf2849c70c 100644 --- a/nova/api/openstack/compute/migrations.py +++ b/nova/api/openstack/compute/migrations.py @@ -15,7 +15,7 @@ from webob import exc from nova.api.openstack import api_version_request from nova.api.openstack import common -from nova.api.openstack.compute.schemas import migrations as schema_migrations +from nova.api.openstack.compute.schemas import migrations as schema from nova.api.openstack.compute.views import migrations as migrations_view from nova.api.openstack import wsgi from nova.api import validation @@ -145,47 +145,41 @@ class MigrationsController(wsgi.Controller): migrations_dict['migrations_links'] = migrations_links return migrations_dict - @wsgi.Controller.api_version("2.1", "2.22") # noqa - @wsgi.expected_errors(()) - @validation.query_schema(schema_migrations.list_query_schema_v20, - "2.0", "2.22") + @wsgi.expected_errors((), "2.1", "2.58") + @wsgi.expected_errors(400, "2.59") + @validation.query_schema(schema.list_query_schema_v20, "2.0", "2.22") + @validation.query_schema(schema.list_query_schema_v20, "2.23", "2.58") + @validation.query_schema(schema.list_query_params_v259, "2.59", "2.65") + @validation.query_schema(schema.list_query_params_v266, "2.66", "2.79") + @validation.query_schema(schema.list_query_params_v280, "2.80") def index(self, req): """Return all migrations using the query parameters as filters.""" - return self._index(req) + add_link = False + if api_version_request.is_supported(req, '2.23'): + add_link = True - @wsgi.Controller.api_version("2.23", "2.58") # noqa - @wsgi.expected_errors(()) - @validation.query_schema(schema_migrations.list_query_schema_v20, - "2.23", "2.58") - def index(self, req): # noqa - """Return all migrations using the query parameters as filters.""" - return self._index(req, add_link=True) + next_link = False + add_uuid = False + sort_keys = None + sort_dirs = None + limit = None + marker = None + allow_changes_since = False + if api_version_request.is_supported(req, '2.59'): + next_link = True + add_uuid = True + sort_keys = ['created_at', 'id'] + # FIXME(stephenfin): This looks like a typo? + sort_dirs = ['desc', 'desc'] + limit, marker = common.get_limit_and_marker(req) + allow_changes_since = True - @wsgi.Controller.api_version("2.59", "2.65") # noqa - @wsgi.expected_errors(400) - @validation.query_schema(schema_migrations.list_query_params_v259, - "2.59", "2.65") - def index(self, req): # noqa - """Return all migrations using the query parameters as filters.""" - limit, marker = common.get_limit_and_marker(req) - return self._index(req, add_link=True, next_link=True, add_uuid=True, - sort_keys=['created_at', 'id'], - sort_dirs=['desc', 'desc'], - limit=limit, marker=marker, - allow_changes_since=True) + allow_changes_before = False + if api_version_request.is_supported(req, '2.66'): + allow_changes_before = True - @wsgi.Controller.api_version("2.66") # noqa - @wsgi.expected_errors(400) - @validation.query_schema(schema_migrations.list_query_params_v266, - "2.66", "2.79") - @validation.query_schema(schema_migrations.list_query_params_v280, - "2.80") - def index(self, req): # noqa - """Return all migrations using the query parameters as filters.""" - limit, marker = common.get_limit_and_marker(req) - return self._index(req, add_link=True, next_link=True, add_uuid=True, - sort_keys=['created_at', 'id'], - sort_dirs=['desc', 'desc'], - limit=limit, marker=marker, - allow_changes_since=True, - allow_changes_before=True) + return self._index( + req, add_link=add_link, next_link=next_link, add_uuid=add_uuid, + sort_keys=sort_keys, sort_dirs=sort_dirs, limit=limit, + marker=marker, allow_changes_since=allow_changes_since, + allow_changes_before=allow_changes_before) diff --git a/nova/api/openstack/compute/quota_classes.py b/nova/api/openstack/compute/quota_classes.py index d3acfab1219d..612b91e32774 100644 --- a/nova/api/openstack/compute/quota_classes.py +++ b/nova/api/openstack/compute/quota_classes.py @@ -16,6 +16,7 @@ import copy import webob +from nova.api.openstack import api_version_request from nova.api.openstack.compute.schemas import quota_classes from nova.api.openstack import wsgi from nova.api import validation @@ -76,23 +77,27 @@ class QuotaClassSetsController(wsgi.Controller): return dict(quota_class_set=result) - @wsgi.Controller.api_version('2.1', '2.49') + def _get_filtered_quotas(self, req): + if api_version_request.is_supported(req, '2.57'): + return FILTERED_QUOTAS_2_57 + elif api_version_request.is_supported(req, '2.50'): + return FILTERED_QUOTAS_2_50 + else: + return [] + @wsgi.expected_errors(()) @validation.query_schema(quota_classes.show_query) def show(self, req, id): - return self._show(req, id, exclude_server_groups=True) + filtered_quotas = self._get_filtered_quotas(req) - @wsgi.Controller.api_version('2.50', '2.56') # noqa - @wsgi.expected_errors(()) - @validation.query_schema(quota_classes.show_query) - def show(self, req, id): # noqa - return self._show(req, id, FILTERED_QUOTAS_2_50) + exclude_server_groups = True + if api_version_request.is_supported(req, '2.50'): + exclude_server_groups = False - @wsgi.Controller.api_version('2.57') # noqa - @wsgi.expected_errors(()) - @validation.query_schema(quota_classes.show_query) - def show(self, req, id): # noqa - return self._show(req, id, FILTERED_QUOTAS_2_57) + return self._show( + req, id, filtered_quotas=filtered_quotas, + exclude_server_groups=exclude_server_groups, + ) def _show(self, req, id, filtered_quotas=None, exclude_server_groups=False): @@ -102,23 +107,21 @@ class QuotaClassSetsController(wsgi.Controller): return self._format_quota_set(id, values, filtered_quotas, exclude_server_groups) - @wsgi.Controller.api_version("2.1", "2.49") # noqa @wsgi.expected_errors(400) - @validation.schema(quota_classes.update) + @validation.schema(quota_classes.update, '2.1', '2.49') + @validation.schema(quota_classes.update_v250, '2.50', '2.56') + @validation.schema(quota_classes.update_v257, '2.57') def update(self, req, id, body): - return self._update(req, id, body, exclude_server_groups=True) + filtered_quotas = self._get_filtered_quotas(req) - @wsgi.Controller.api_version("2.50", "2.56") # noqa - @wsgi.expected_errors(400) - @validation.schema(quota_classes.update_v250) - def update(self, req, id, body): # noqa - return self._update(req, id, body, FILTERED_QUOTAS_2_50) + exclude_server_groups = True + if api_version_request.is_supported(req, '2.50'): + exclude_server_groups = False - @wsgi.Controller.api_version("2.57") # noqa - @wsgi.expected_errors(400) - @validation.schema(quota_classes.update_v257) - def update(self, req, id, body): # noqa - return self._update(req, id, body, FILTERED_QUOTAS_2_57) + return self._update( + req, id, body, filtered_quotas=filtered_quotas, + exclude_server_groups=exclude_server_groups, + ) def _update(self, req, id, body, filtered_quotas=None, exclude_server_groups=False): diff --git a/nova/api/openstack/compute/quota_sets.py b/nova/api/openstack/compute/quota_sets.py index 25bad85dc2b6..14bb8c49e0f5 100644 --- a/nova/api/openstack/compute/quota_sets.py +++ b/nova/api/openstack/compute/quota_sets.py @@ -18,10 +18,7 @@ from urllib import parse as urlparse from oslo_utils import strutils import webob -from nova.api.openstack.api_version_request \ - import MAX_PROXY_API_SUPPORT_VERSION -from nova.api.openstack.api_version_request \ - import MIN_WITHOUT_PROXY_API_SUPPORT_VERSION +from nova.api.openstack import api_version_request from nova.api.openstack.compute.schemas import quota_sets from nova.api.openstack import identity from nova.api.openstack import wsgi @@ -105,24 +102,21 @@ class QuotaSetsController(wsgi.Controller): else: return {k: v['limit'] for k, v in values.items()} - @wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) - @wsgi.expected_errors(400) - @validation.query_schema(quota_sets.show_query, '2.0', '2.74') - def show(self, req, id): - return self._show(req, id, []) + def _get_filtered_quotas(self, req): + if api_version_request.is_supported(req, '2.57'): + return FILTERED_QUOTAS_2_57 + elif api_version_request.is_supported(req, '2.36'): + return FILTERED_QUOTAS_2_36 + else: + return [] - @wsgi.Controller.api_version(MIN_WITHOUT_PROXY_API_SUPPORT_VERSION, '2.56') - @wsgi.expected_errors(400) - @validation.query_schema(quota_sets.show_query, '2.0', '2.74') - def show(self, req, id): # noqa - return self._show(req, id, FILTERED_QUOTAS_2_36) - - @wsgi.Controller.api_version('2.57') # noqa + @wsgi.Controller.api_version('2.1') @wsgi.expected_errors(400) @validation.query_schema(quota_sets.show_query, '2.0', '2.74') @validation.query_schema(quota_sets.show_query_v275, '2.75') - def show(self, req, id): # noqa - return self._show(req, id, FILTERED_QUOTAS_2_57) + def show(self, req, id): + filtered_quotas = self._get_filtered_quotas(req) + return self._show(req, id, filtered_quotas) def _show(self, req, id, filtered_quotas): context = req.environ['nova.context'] @@ -131,29 +125,17 @@ class QuotaSetsController(wsgi.Controller): params = urlparse.parse_qs(req.environ.get('QUERY_STRING', '')) user_id = params.get('user_id', [None])[0] - return self._format_quota_set(id, + return self._format_quota_set( + id, self._get_quotas(context, id, user_id=user_id), filtered_quotas=filtered_quotas) - @wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.expected_errors(400) @validation.query_schema(quota_sets.show_query, '2.0', '2.74') + @validation.query_schema(quota_sets.show_query_v275, '2.75') def detail(self, req, id): - return self._detail(req, id, []) - - @wsgi.Controller.api_version(MIN_WITHOUT_PROXY_API_SUPPORT_VERSION, '2.56') - @wsgi.expected_errors(400) - @validation.query_schema(quota_sets.show_query, '2.0', '2.74') - @validation.query_schema(quota_sets.show_query_v275, '2.75') - def detail(self, req, id): # noqa - return self._detail(req, id, FILTERED_QUOTAS_2_36) - - @wsgi.Controller.api_version('2.57') # noqa - @wsgi.expected_errors(400) - @validation.query_schema(quota_sets.show_query, '2.0', '2.74') - @validation.query_schema(quota_sets.show_query_v275, '2.75') - def detail(self, req, id): # noqa - return self._detail(req, id, FILTERED_QUOTAS_2_57) + filtered_quotas = self._get_filtered_quotas(req) + return self._detail(req, id, filtered_quotas) def _detail(self, req, id, filtered_quotas): context = req.environ['nova.context'] @@ -166,26 +148,16 @@ class QuotaSetsController(wsgi.Controller): self._get_quotas(context, id, user_id=user_id, usages=True), filtered_quotas=filtered_quotas) - @wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) + @wsgi.Controller.api_version('2.1') @wsgi.expected_errors(400) - @validation.schema(quota_sets.update) - def update(self, req, id, body): - return self._update(req, id, body, []) - - @wsgi.Controller.api_version(MIN_WITHOUT_PROXY_API_SUPPORT_VERSION, '2.56') - @wsgi.expected_errors(400) - @validation.schema(quota_sets.update_v236) - @validation.query_schema(quota_sets.show_query, '2.0', '2.74') - def update(self, req, id, body): # noqa - return self._update(req, id, body, FILTERED_QUOTAS_2_36) - - @wsgi.Controller.api_version('2.57') # noqa - @wsgi.expected_errors(400) - @validation.schema(quota_sets.update_v257) + @validation.schema(quota_sets.update, '2.0', '2.35') + @validation.schema(quota_sets.update_v236, '2.36', '2.56') + @validation.schema(quota_sets.update_v257, '2.57') @validation.query_schema(quota_sets.show_query, '2.0', '2.74') @validation.query_schema(quota_sets.show_query_v275, '2.75') - def update(self, req, id, body): # noqa - return self._update(req, id, body, FILTERED_QUOTAS_2_57) + def update(self, req, id, body): + filtered_quotas = self._get_filtered_quotas(req) + return self._update(req, id, body, filtered_quotas) def _update(self, req, id, body, filtered_quotas): context = req.environ['nova.context'] @@ -249,23 +221,12 @@ class QuotaSetsController(wsgi.Controller): self._get_quotas(context, id, user_id=user_id), filtered_quotas=filtered_quotas) - @wsgi.Controller.api_version("2.0", MAX_PROXY_API_SUPPORT_VERSION) + @wsgi.Controller.api_version('2.0') @wsgi.expected_errors(400) @validation.query_schema(quota_sets.defaults_query) def defaults(self, req, id): - return self._defaults(req, id, []) - - @wsgi.Controller.api_version(MIN_WITHOUT_PROXY_API_SUPPORT_VERSION, '2.56') - @wsgi.expected_errors(400) - @validation.query_schema(quota_sets.defaults_query) - def defaults(self, req, id): # noqa - return self._defaults(req, id, FILTERED_QUOTAS_2_36) - - @wsgi.Controller.api_version('2.57') # noqa - @wsgi.expected_errors(400) - @validation.query_schema(quota_sets.defaults_query) - def defaults(self, req, id): # noqa - return self._defaults(req, id, FILTERED_QUOTAS_2_57) + filtered_quotas = self._get_filtered_quotas(req) + return self._defaults(req, id, filtered_quotas) def _defaults(self, req, id, filtered_quotas): context = req.environ['nova.context'] diff --git a/nova/api/openstack/compute/schemas/services.py b/nova/api/openstack/compute/schemas/services.py index 2ebd4b9f6505..951643918f3f 100644 --- a/nova/api/openstack/compute/schemas/services.py +++ b/nova/api/openstack/compute/schemas/services.py @@ -53,7 +53,7 @@ service_update_v211 = { # be specified in the body. If status=='disabled', then 'disabled_reason' is # also checked in the body but is not required. Requesting status='enabled' and # including a 'disabled_reason' results in a 400, but this is checked in code. -service_update_v2_53 = { +service_update_v253 = { 'type': 'object', 'properties': { 'status': { diff --git a/nova/api/openstack/compute/schemas/simple_tenant_usage.py b/nova/api/openstack/compute/schemas/simple_tenant_usage.py index 7ac34416e662..20ef7961e863 100644 --- a/nova/api/openstack/compute/schemas/simple_tenant_usage.py +++ b/nova/api/openstack/compute/schemas/simple_tenant_usage.py @@ -51,8 +51,8 @@ show_query_v240 = copy.deepcopy(show_query) show_query_v240['properties'].update( parameter_types.pagination_parameters) -index_query_275 = copy.deepcopy(index_query_v240) -index_query_275['additionalProperties'] = False +index_query_v275 = copy.deepcopy(index_query_v240) +index_query_v275['additionalProperties'] = False -show_query_275 = copy.deepcopy(show_query_v240) -show_query_275['additionalProperties'] = False +show_query_v275 = copy.deepcopy(show_query_v240) +show_query_v275['additionalProperties'] = False diff --git a/nova/api/openstack/compute/services.py b/nova/api/openstack/compute/services.py index 4dda9e91a466..b37e947cf435 100644 --- a/nova/api/openstack/compute/services.py +++ b/nova/api/openstack/compute/services.py @@ -369,9 +369,9 @@ class ServiceController(wsgi.Controller): 'in-progress migrations. Complete the ' 'migrations or delete the instances first.')) - @validation.query_schema(services.index_query_schema_275, '2.75') - @validation.query_schema(services.index_query_schema, '2.0', '2.74') @wsgi.expected_errors(()) + @validation.query_schema(services.index_query_schema, '2.0', '2.74') + @validation.query_schema(services.index_query_schema_275, '2.75') def index(self, req): """Return a list of all running services. Filter by host & service name @@ -385,10 +385,10 @@ class ServiceController(wsgi.Controller): return {'services': _services} - @wsgi.Controller.api_version('2.1', '2.52') @wsgi.expected_errors((400, 404)) @validation.schema(services.service_update, '2.0', '2.10') @validation.schema(services.service_update_v211, '2.11', '2.52') + @validation.schema(services.service_update_v253, '2.53') def update(self, req, id, body): """Perform service update @@ -396,7 +396,17 @@ class ServiceController(wsgi.Controller): to identify the service on which to perform the action. There is no service ID passed on the path, just the action, for example PUT /os-services/disable. + + Starting with microversion 2.53, the service uuid is passed in on the + path of the request to uniquely identify the service record on which to + perform a given update, which is defined in the body of the request. """ + if api_version_request.is_supported(req, min_version='2.53'): + return self._update_v253(req, id, body) + else: + return self._update_v21(req, id, body) + + def _update_v21(self, req, id, body): context = req.environ['nova.context'] context.can(services_policies.BASE_POLICY_NAME % 'update', target={}) if api_version_request.is_supported(req, min_version='2.11'): @@ -407,16 +417,7 @@ class ServiceController(wsgi.Controller): return self._perform_action(req, id, body, actions) - @wsgi.Controller.api_version('2.53') # noqa F811 - @wsgi.expected_errors((400, 404)) - @validation.schema(services.service_update_v2_53, '2.53') - def update(self, req, id, body): # noqa - """Perform service update - - Starting with microversion 2.53, the service uuid is passed in on the - path of the request to uniquely identify the service record on which to - perform a given update, which is defined in the body of the request. - """ + def _update_v253(self, req, id, body): service_id = id # Validate that the service ID is a UUID. if not uuidutils.is_uuid_like(service_id): diff --git a/nova/api/openstack/compute/simple_tenant_usage.py b/nova/api/openstack/compute/simple_tenant_usage.py index 251323c91e6d..35b00bc62d64 100644 --- a/nova/api/openstack/compute/simple_tenant_usage.py +++ b/nova/api/openstack/compute/simple_tenant_usage.py @@ -21,6 +21,7 @@ import iso8601 from oslo_utils import timeutils from webob import exc +from nova.api.openstack import api_version_request from nova.api.openstack import common from nova.api.openstack.compute.schemas import simple_tenant_usage as schema from nova.api.openstack.compute.views import usages as usages_view @@ -178,7 +179,7 @@ class SimpleTenantUsageController(wsgi.Controller): info['ended_at'] = ( timeutils.normalize_time(instance.terminated_at) if - instance.terminated_at else None) + instance.terminated_at else None) if info['ended_at']: info['state'] = 'terminated' @@ -261,35 +262,17 @@ class SimpleTenantUsageController(wsgi.Controller): detailed = env.get('detailed', ['0'])[0] == '1' return (period_start, period_stop, detailed) - @wsgi.Controller.api_version("2.40") - @validation.query_schema(schema.index_query_275, '2.75') + @validation.query_schema(schema.index_query, '2.1', '2.39') @validation.query_schema(schema.index_query_v240, '2.40', '2.74') + @validation.query_schema(schema.index_query_v275, '2.75') @wsgi.expected_errors(400) def index(self, req): """Retrieve tenant_usage for all tenants.""" - return self._index(req, links=True) + links = False + if api_version_request.is_supported(req, '2.40'): + links = True - @wsgi.Controller.api_version("2.1", "2.39") # noqa - @validation.query_schema(schema.index_query) - @wsgi.expected_errors(400) - def index(self, req): # noqa - """Retrieve tenant_usage for all tenants.""" - return self._index(req) - - @wsgi.Controller.api_version("2.40") - @validation.query_schema(schema.show_query_275, '2.75') - @validation.query_schema(schema.show_query_v240, '2.40', '2.74') - @wsgi.expected_errors(400) - def show(self, req, id): - """Retrieve tenant_usage for a specified tenant.""" - return self._show(req, id, links=True) - - @wsgi.Controller.api_version("2.1", "2.39") # noqa - @validation.query_schema(schema.show_query) - @wsgi.expected_errors(400) - def show(self, req, id): # noqa - """Retrieve tenant_usage for a specified tenant.""" - return self._show(req, id) + return self._index(req, links=links) def _index(self, req, links=False): context = req.environ['nova.context'] @@ -327,6 +310,18 @@ class SimpleTenantUsageController(wsgi.Controller): return tenant_usages + @validation.query_schema(schema.show_query, '2.1', '2.39') + @validation.query_schema(schema.show_query_v240, '2.40', '2.74') + @validation.query_schema(schema.show_query_v275, '2.75') + @wsgi.expected_errors(400) + def show(self, req, id): + """Retrieve tenant_usage for a specified tenant.""" + links = False + if api_version_request.is_supported(req, '2.40'): + links = True + + return self._show(req, id, links=links) + def _show(self, req, id, links=False): tenant_id = id context = req.environ['nova.context']