api: Add new, simpler api_version decorator

Get rid of the whole API version switching madness and make our schema
generation _significantly_ simpler.

This looks a lot larger than it actually is. In most cases, this is
simply 's/wsgi.Controller.api_version/wsgi.api_version/'.

Change-Id: I180bfad84c38653709c216282099d9b3fb64c5a7
Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
This commit is contained in:
Stephen Finucane
2024-11-04 15:08:28 +00:00
parent a722640b2f
commit 08dd30d3fc
29 changed files with 206 additions and 483 deletions

View File

@@ -240,9 +240,9 @@ Adding a new API method
In the controller class:: In the controller class::
@wsgi.Controller.api_version("2.4") @wsgi.api_version("2.4")
def my_api_method(self, req, id): def my_api_method(self, req, id):
.... ...
This method would only be available if the caller had specified an This method would only be available if the caller had specified an
``OpenStack-API-Version`` of >= ``2.4``. If they had specified a ``OpenStack-API-Version`` of >= ``2.4``. If they had specified a
@@ -254,36 +254,14 @@ Removing an API method
In the controller class:: In the controller class::
@wsgi.Controller.api_version("2.1", "2.4") @wsgi.api_version("2.1", "2.4")
def my_api_method(self, req, id): def my_api_method(self, req, id):
.... ...
This method would only be available if the caller had specified an This method would only be available if the caller had specified an
``OpenStack-API-Version`` of <= ``2.4``. If ``2.5`` or later ``OpenStack-API-Version`` of <= ``2.4``. If ``2.5`` or later
is specified the server will respond with ``HTTP/404``. is specified the server will respond with ``HTTP/404``.
Changing a method's behavior
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In the controller class::
@wsgi.Controller.api_version("2.1", "2.3")
def my_api_method(self, req, id):
.... method_1 ...
@wsgi.Controller.api_version("2.4") # noqa
def my_api_method(self, req, id):
.... method_2 ...
If a caller specified ``2.1``, ``2.2`` or ``2.3`` (or received the
default of ``2.1``) they would see the result from ``method_1``,
``2.4`` or later ``method_2``.
It is vital that the two methods have the same name, so the second of
them will need ``# noqa`` to avoid failing flake8's ``F811`` rule. The
two methods may be different in any kind of semantics (schema
validation, return values, response codes, etc)
A change in schema only A change in schema only
~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~
@@ -291,26 +269,23 @@ If there is no change to the method, only to the schema that is used for
validation, you can add a version range to the ``validation.schema`` validation, you can add a version range to the ``validation.schema``
decorator:: decorator::
@wsgi.Controller.api_version("2.1") @wsgi.api_version("2.1")
@validation.schema(dummy_schema.dummy, "2.3", "2.8") @validation.schema(dummy_schema.dummy, "2.3", "2.8")
@validation.schema(dummy_schema.dummy2, "2.9") @validation.schema(dummy_schema.dummy2, "2.9")
def update(self, req, id, body): def update(self, req, id, body):
.... ...
This method will be available from version ``2.1``, validated according to This method will be available from version ``2.1``, validated according to
``dummy_schema.dummy`` from ``2.3`` to ``2.8``, and validated according to ``dummy_schema.dummy`` from ``2.3`` to ``2.8``, and validated according to
``dummy_schema.dummy2`` from ``2.9`` onward. ``dummy_schema.dummy2`` from ``2.9`` onward.
Other API method changes
~~~~~~~~~~~~~~~~~~~~~~~~
When not using decorators When you want to change more than the API request or response schema, you can
~~~~~~~~~~~~~~~~~~~~~~~~~ directly test for the requested version with a method as long as you have
access to the api request object (commonly called ``req``). Every API method
When you don't want to use the ``@api_version`` decorator on a method has an api_version_request object attached to the req object and that can be
or you want to change behavior within a method (say it leads to
simpler or simply a lot less code) you can directly test for the
requested version with a method as long as you have access to the api
request object (commonly called ``req``). Every API method has an
api_version_request object attached to the req object and that can be
used to modify behavior based on its value:: used to modify behavior based on its value::
def index(self, req): def index(self, req):

View File

@@ -287,7 +287,7 @@ class AggregateController(wsgi.Controller):
(show_uuid or key != 'uuid')): (show_uuid or key != 'uuid')):
yield key, getattr(aggregate, key) yield key, getattr(aggregate, key)
@wsgi.Controller.api_version('2.81') @wsgi.api_version('2.81')
@wsgi.response(202) @wsgi.response(202)
@wsgi.expected_errors((400, 404)) @wsgi.expected_errors((400, 404))
@validation.schema(aggregate_images.aggregate_images) @validation.schema(aggregate_images.aggregate_images)

View File

@@ -61,7 +61,7 @@ class BareMetalNodeController(wsgi.Controller):
) )
return self._ironic_connection return self._ironic_connection
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors((404, 501)) @wsgi.expected_errors((404, 501))
@validation.query_schema(schema.index_query) @validation.query_schema(schema.index_query)
@validation.response_body_schema(schema.index_response) @validation.response_body_schema(schema.index_response)
@@ -86,7 +86,7 @@ class BareMetalNodeController(wsgi.Controller):
return {'nodes': nodes} return {'nodes': nodes}
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors((404, 501)) @wsgi.expected_errors((404, 501))
@validation.query_schema(schema.show_query) @validation.query_schema(schema.show_query)
@validation.response_body_schema(schema.show_response) @validation.response_body_schema(schema.show_response)
@@ -117,20 +117,20 @@ class BareMetalNodeController(wsgi.Controller):
return {'node': node} return {'node': node}
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors(400) @wsgi.expected_errors(400)
@validation.schema(schema.create) @validation.schema(schema.create)
@validation.response_body_schema(schema.create_response) @validation.response_body_schema(schema.create_response)
def create(self, req, body): def create(self, req, body):
_no_ironic_proxy("node-create") _no_ironic_proxy("node-create")
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors(400) @wsgi.expected_errors(400)
@validation.response_body_schema(schema.delete_response) @validation.response_body_schema(schema.delete_response)
def delete(self, req, id): def delete(self, req, id):
_no_ironic_proxy("node-delete") _no_ironic_proxy("node-delete")
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.action('add_interface') @wsgi.action('add_interface')
@wsgi.expected_errors(400) @wsgi.expected_errors(400)
@validation.schema(schema.add_interface) @validation.schema(schema.add_interface)
@@ -138,7 +138,7 @@ class BareMetalNodeController(wsgi.Controller):
def _add_interface(self, req, id, body): def _add_interface(self, req, id, body):
_no_ironic_proxy("port-create") _no_ironic_proxy("port-create")
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.action('remove_interface') @wsgi.action('remove_interface')
@wsgi.expected_errors(400) @wsgi.expected_errors(400)
@validation.schema(schema.remove_interface) @validation.schema(schema.remove_interface)

View File

@@ -108,7 +108,7 @@ class FlavorsController(wsgi.Controller):
return self._view_builder.show(req, flavor, include_description, return self._view_builder.show(req, flavor, include_description,
include_extra_specs=include_extra_specs) include_extra_specs=include_extra_specs)
@wsgi.Controller.api_version('2.55') @wsgi.api_version('2.55')
@wsgi.expected_errors((400, 404)) @wsgi.expected_errors((400, 404))
@validation.schema(schema.update, '2.55') @validation.schema(schema.update, '2.55')
@validation.response_body_schema(schema.update_response, '2.55', '2.60') @validation.response_body_schema(schema.update_response, '2.55', '2.60')

View File

@@ -41,7 +41,7 @@ class FloatingIPPoolsController(wsgi.Controller):
super(FloatingIPPoolsController, self).__init__() super(FloatingIPPoolsController, self).__init__()
self.network_api = neutron.API() self.network_api = neutron.API()
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors(()) @wsgi.expected_errors(())
@validation.query_schema(schema.index_query) @validation.query_schema(schema.index_query)
@validation.response_body_schema(schema.index_response) @validation.response_body_schema(schema.index_response)

View File

@@ -82,7 +82,7 @@ class FloatingIPController(wsgi.Controller):
self.compute_api = compute.API() self.compute_api = compute.API()
self.network_api = neutron.API() self.network_api = neutron.API()
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors((400, 404)) @wsgi.expected_errors((400, 404))
@validation.query_schema(schema.show_query) @validation.query_schema(schema.show_query)
def show(self, req, id): def show(self, req, id):
@@ -101,7 +101,7 @@ class FloatingIPController(wsgi.Controller):
return _translate_floating_ip_view(floating_ip) return _translate_floating_ip_view(floating_ip)
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors(()) @wsgi.expected_errors(())
@validation.query_schema(schema.index_query) @validation.query_schema(schema.index_query)
def index(self, req): def index(self, req):
@@ -115,7 +115,7 @@ class FloatingIPController(wsgi.Controller):
return {'floating_ips': [_translate_floating_ip_view(ip)['floating_ip'] return {'floating_ips': [_translate_floating_ip_view(ip)['floating_ip']
for ip in floating_ips]} for ip in floating_ips]}
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors((400, 403, 404)) @wsgi.expected_errors((400, 403, 404))
@validation.schema(schema.create) @validation.schema(schema.create)
def create(self, req, body=None): def create(self, req, body=None):
@@ -148,7 +148,7 @@ class FloatingIPController(wsgi.Controller):
return _translate_floating_ip_view(ip) return _translate_floating_ip_view(ip)
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.response(202) @wsgi.response(202)
@wsgi.expected_errors((400, 403, 404, 409)) @wsgi.expected_errors((400, 403, 404, 409))
def delete(self, req, id): def delete(self, req, id):
@@ -186,7 +186,7 @@ class FloatingIPActionController(wsgi.Controller):
self.compute_api = compute.API() self.compute_api = compute.API()
self.network_api = neutron.API() self.network_api = neutron.API()
@wsgi.Controller.api_version("2.1", "2.43") @wsgi.api_version("2.1", "2.43")
@wsgi.expected_errors((400, 403, 404)) @wsgi.expected_errors((400, 403, 404))
@wsgi.action('addFloatingIp') @wsgi.action('addFloatingIp')
@validation.schema(schema.add_floating_ip) @validation.schema(schema.add_floating_ip)
@@ -267,7 +267,7 @@ class FloatingIPActionController(wsgi.Controller):
return webob.Response(status_int=202) return webob.Response(status_int=202)
@wsgi.Controller.api_version("2.1", "2.43") @wsgi.api_version("2.1", "2.43")
@wsgi.expected_errors((400, 403, 404, 409)) @wsgi.expected_errors((400, 403, 404, 409))
@wsgi.action('removeFloatingIp') @wsgi.action('removeFloatingIp')
@validation.schema(schema.remove_floating_ip) @validation.schema(schema.remove_floating_ip)

View File

@@ -38,7 +38,7 @@ class HostController(wsgi.Controller):
super(HostController, self).__init__() super(HostController, self).__init__()
self.api = compute.HostAPI() self.api = compute.HostAPI()
@wsgi.Controller.api_version("2.1", "2.42") @wsgi.api_version("2.1", "2.42")
@validation.query_schema(hosts.index_query) @validation.query_schema(hosts.index_query)
@wsgi.expected_errors(()) @wsgi.expected_errors(())
def index(self, req): def index(self, req):
@@ -88,7 +88,7 @@ class HostController(wsgi.Controller):
'zone': service['availability_zone']}) 'zone': service['availability_zone']})
return {'hosts': hosts} return {'hosts': hosts}
@wsgi.Controller.api_version("2.1", "2.42") @wsgi.api_version("2.1", "2.42")
@wsgi.expected_errors((400, 404, 501)) @wsgi.expected_errors((400, 404, 501))
@validation.schema(hosts.update) @validation.schema(hosts.update)
def update(self, req, id, body): def update(self, req, id, body):
@@ -180,7 +180,7 @@ class HostController(wsgi.Controller):
raise webob.exc.HTTPBadRequest(explanation=e.format_message()) raise webob.exc.HTTPBadRequest(explanation=e.format_message())
return {"host": host_name, "power_action": result} return {"host": host_name, "power_action": result}
@wsgi.Controller.api_version("2.1", "2.42") @wsgi.api_version("2.1", "2.42")
@wsgi.expected_errors((400, 404, 501)) @wsgi.expected_errors((400, 404, 501))
@validation.query_schema(hosts.startup_query) @validation.query_schema(hosts.startup_query)
def startup(self, req, id): def startup(self, req, id):
@@ -189,7 +189,7 @@ class HostController(wsgi.Controller):
target={}) target={})
return self._host_power_action(req, host_name=id, action="startup") return self._host_power_action(req, host_name=id, action="startup")
@wsgi.Controller.api_version("2.1", "2.42") @wsgi.api_version("2.1", "2.42")
@wsgi.expected_errors((400, 404, 501)) @wsgi.expected_errors((400, 404, 501))
@validation.query_schema(hosts.shutdown_query) @validation.query_schema(hosts.shutdown_query)
def shutdown(self, req, id): def shutdown(self, req, id):
@@ -198,7 +198,7 @@ class HostController(wsgi.Controller):
target={}) target={})
return self._host_power_action(req, host_name=id, action="shutdown") return self._host_power_action(req, host_name=id, action="shutdown")
@wsgi.Controller.api_version("2.1", "2.42") @wsgi.api_version("2.1", "2.42")
@wsgi.expected_errors((400, 404, 501)) @wsgi.expected_errors((400, 404, 501))
@validation.query_schema(hosts.reboot_query) @validation.query_schema(hosts.reboot_query)
def reboot(self, req, id): def reboot(self, req, id):
@@ -256,7 +256,7 @@ class HostController(wsgi.Controller):
instance['ephemeral_gb']) instance['ephemeral_gb'])
return project_map return project_map
@wsgi.Controller.api_version("2.1", "2.42") @wsgi.api_version("2.1", "2.42")
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
@validation.query_schema(hosts.show_query) @validation.query_schema(hosts.show_query)
def show(self, req, id): def show(self, req, id):

View File

@@ -359,7 +359,7 @@ class HypervisorsController(wsgi.Controller):
), ),
} }
@wsgi.Controller.api_version('2.1', '2.87') @wsgi.api_version('2.1', '2.87')
@wsgi.expected_errors((400, 404, 501)) @wsgi.expected_errors((400, 404, 501))
@validation.query_schema(schema.uptime_query) @validation.query_schema(schema.uptime_query)
def uptime(self, req, id): def uptime(self, req, id):
@@ -412,7 +412,7 @@ class HypervisorsController(wsgi.Controller):
return {'hypervisor': hypervisor} return {'hypervisor': hypervisor}
@wsgi.Controller.api_version('2.1', '2.52') @wsgi.api_version('2.1', '2.52')
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
@validation.query_schema(schema.search_query) @validation.query_schema(schema.search_query)
def search(self, req, id): def search(self, req, id):
@@ -451,7 +451,7 @@ class HypervisorsController(wsgi.Controller):
return {'hypervisors': hypervisors} return {'hypervisors': hypervisors}
@wsgi.Controller.api_version('2.1', '2.52') @wsgi.api_version('2.1', '2.52')
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
@validation.query_schema(schema.servers_query) @validation.query_schema(schema.servers_query)
def servers(self, req, id): def servers(self, req, id):
@@ -497,7 +497,7 @@ class HypervisorsController(wsgi.Controller):
return {'hypervisors': hypervisors} return {'hypervisors': hypervisors}
@wsgi.Controller.api_version('2.1', '2.87') @wsgi.api_version('2.1', '2.87')
@wsgi.expected_errors(()) @wsgi.expected_errors(())
@validation.query_schema(schema.statistics_query) @validation.query_schema(schema.statistics_query)
def statistics(self, req): def statistics(self, req):

View File

@@ -43,7 +43,7 @@ class ImageMetadataController(wsgi.Controller):
msg = _("Image not found.") msg = _("Image not found.")
raise exc.HTTPNotFound(explanation=msg) raise exc.HTTPNotFound(explanation=msg)
@wsgi.Controller.api_version("2.1", MAX_IMAGE_META_PROXY_API_VERSION) @wsgi.api_version("2.1", MAX_IMAGE_META_PROXY_API_VERSION)
@wsgi.expected_errors((403, 404)) @wsgi.expected_errors((403, 404))
@validation.query_schema(image_metadata.index_query) @validation.query_schema(image_metadata.index_query)
def index(self, req, image_id): def index(self, req, image_id):
@@ -52,7 +52,7 @@ class ImageMetadataController(wsgi.Controller):
metadata = self._get_image(context, image_id)['properties'] metadata = self._get_image(context, image_id)['properties']
return dict(metadata=metadata) return dict(metadata=metadata)
@wsgi.Controller.api_version("2.1", MAX_IMAGE_META_PROXY_API_VERSION) @wsgi.api_version("2.1", MAX_IMAGE_META_PROXY_API_VERSION)
@wsgi.expected_errors((403, 404)) @wsgi.expected_errors((403, 404))
@validation.query_schema(image_metadata.show_query) @validation.query_schema(image_metadata.show_query)
def show(self, req, image_id, id): def show(self, req, image_id, id):
@@ -63,7 +63,7 @@ class ImageMetadataController(wsgi.Controller):
else: else:
raise exc.HTTPNotFound() raise exc.HTTPNotFound()
@wsgi.Controller.api_version("2.1", MAX_IMAGE_META_PROXY_API_VERSION) @wsgi.api_version("2.1", MAX_IMAGE_META_PROXY_API_VERSION)
@wsgi.expected_errors((400, 403, 404)) @wsgi.expected_errors((400, 403, 404))
@validation.schema(image_metadata.create) @validation.schema(image_metadata.create)
def create(self, req, image_id, body): def create(self, req, image_id, body):
@@ -80,7 +80,7 @@ class ImageMetadataController(wsgi.Controller):
raise exc.HTTPForbidden(explanation=e.format_message()) raise exc.HTTPForbidden(explanation=e.format_message())
return dict(metadata=image['properties']) return dict(metadata=image['properties'])
@wsgi.Controller.api_version("2.1", MAX_IMAGE_META_PROXY_API_VERSION) @wsgi.api_version("2.1", MAX_IMAGE_META_PROXY_API_VERSION)
@wsgi.expected_errors((400, 403, 404)) @wsgi.expected_errors((400, 403, 404))
@validation.schema(image_metadata.update) @validation.schema(image_metadata.update)
def update(self, req, image_id, id, body): def update(self, req, image_id, id, body):
@@ -103,7 +103,7 @@ class ImageMetadataController(wsgi.Controller):
raise exc.HTTPForbidden(explanation=e.format_message()) raise exc.HTTPForbidden(explanation=e.format_message())
return dict(meta=meta) return dict(meta=meta)
@wsgi.Controller.api_version("2.1", MAX_IMAGE_META_PROXY_API_VERSION) @wsgi.api_version("2.1", MAX_IMAGE_META_PROXY_API_VERSION)
@wsgi.expected_errors((400, 403, 404)) @wsgi.expected_errors((400, 403, 404))
@validation.schema(image_metadata.update_all) @validation.schema(image_metadata.update_all)
def update_all(self, req, image_id, body): def update_all(self, req, image_id, body):
@@ -119,7 +119,7 @@ class ImageMetadataController(wsgi.Controller):
raise exc.HTTPForbidden(explanation=e.format_message()) raise exc.HTTPForbidden(explanation=e.format_message())
return dict(metadata=metadata) return dict(metadata=metadata)
@wsgi.Controller.api_version("2.1", MAX_IMAGE_META_PROXY_API_VERSION) @wsgi.api_version("2.1", MAX_IMAGE_META_PROXY_API_VERSION)
@wsgi.expected_errors((403, 404)) @wsgi.expected_errors((403, 404))
@wsgi.response(204) @wsgi.response(204)
def delete(self, req, image_id, id): def delete(self, req, image_id, id):

View File

@@ -74,7 +74,7 @@ class ImagesController(wsgi.Controller):
return filters return filters
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
@validation.query_schema(schema.show_query) @validation.query_schema(schema.show_query)
def show(self, req, id): def show(self, req, id):
@@ -93,7 +93,7 @@ class ImagesController(wsgi.Controller):
return self._view_builder.show(req, image) return self._view_builder.show(req, image)
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors((403, 404)) @wsgi.expected_errors((403, 404))
@wsgi.response(204) @wsgi.response(204)
def delete(self, req, id): def delete(self, req, id):
@@ -114,7 +114,7 @@ class ImagesController(wsgi.Controller):
explanation = _("You are not allowed to delete the image.") explanation = _("You are not allowed to delete the image.")
raise webob.exc.HTTPForbidden(explanation=explanation) raise webob.exc.HTTPForbidden(explanation=explanation)
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors(400) @wsgi.expected_errors(400)
@validation.query_schema(schema.index_query) @validation.query_schema(schema.index_query)
def index(self, req): def index(self, req):
@@ -134,7 +134,7 @@ class ImagesController(wsgi.Controller):
raise webob.exc.HTTPBadRequest(explanation=e.format_message()) raise webob.exc.HTTPBadRequest(explanation=e.format_message())
return self._view_builder.index(req, images) return self._view_builder.index(req, images)
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors(400) @wsgi.expected_errors(400)
@validation.query_schema(schema.detail_query) @validation.query_schema(schema.detail_query)
def detail(self, req): def detail(self, req):

View File

@@ -33,7 +33,7 @@ class MultinicController(wsgi.Controller):
super(MultinicController, self).__init__() super(MultinicController, self).__init__()
self.compute_api = compute.API() self.compute_api = compute.API()
@wsgi.Controller.api_version("2.1", "2.43") @wsgi.api_version("2.1", "2.43")
@wsgi.response(202) @wsgi.response(202)
@wsgi.action('addFixedIp') @wsgi.action('addFixedIp')
@wsgi.expected_errors((400, 404)) @wsgi.expected_errors((400, 404))
@@ -52,7 +52,7 @@ class MultinicController(wsgi.Controller):
except exception.NoMoreFixedIps as e: except exception.NoMoreFixedIps as e:
raise exc.HTTPBadRequest(explanation=e.format_message()) raise exc.HTTPBadRequest(explanation=e.format_message())
@wsgi.Controller.api_version("2.1", "2.43") @wsgi.api_version("2.1", "2.43")
@wsgi.response(202) @wsgi.response(202)
@wsgi.action('removeFixedIp') @wsgi.action('removeFixedIp')
@wsgi.expected_errors((400, 404)) @wsgi.expected_errors((400, 404))

View File

@@ -77,7 +77,7 @@ class NetworkController(wsgi.Controller):
# TODO(stephenfin): 'network_api' is only being passed for use by tests # TODO(stephenfin): 'network_api' is only being passed for use by tests
self.network_api = network_api or neutron.API() self.network_api = network_api or neutron.API()
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors(()) @wsgi.expected_errors(())
@validation.query_schema(schema.index_query) @validation.query_schema(schema.index_query)
def index(self, req): def index(self, req):
@@ -88,7 +88,7 @@ class NetworkController(wsgi.Controller):
result = [network_dict(context, net_ref) for net_ref in networks] result = [network_dict(context, net_ref) for net_ref in networks]
return {'networks': result} return {'networks': result}
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
@validation.query_schema(schema.show_query) @validation.query_schema(schema.show_query)
def show(self, req, id): def show(self, req, id):

View File

@@ -110,7 +110,7 @@ class QuotaSetsController(wsgi.Controller):
else: else:
return [] return []
@wsgi.Controller.api_version('2.1') @wsgi.api_version('2.1')
@wsgi.expected_errors(400) @wsgi.expected_errors(400)
@validation.query_schema(quota_sets.show_query, '2.0', '2.74') @validation.query_schema(quota_sets.show_query, '2.0', '2.74')
@validation.query_schema(quota_sets.show_query_v275, '2.75') @validation.query_schema(quota_sets.show_query_v275, '2.75')
@@ -148,7 +148,7 @@ class QuotaSetsController(wsgi.Controller):
self._get_quotas(context, id, user_id=user_id, usages=True), self._get_quotas(context, id, user_id=user_id, usages=True),
filtered_quotas=filtered_quotas) filtered_quotas=filtered_quotas)
@wsgi.Controller.api_version('2.1') @wsgi.api_version('2.1')
@wsgi.expected_errors(400) @wsgi.expected_errors(400)
@validation.schema(quota_sets.update, '2.0', '2.35') @validation.schema(quota_sets.update, '2.0', '2.35')
@validation.schema(quota_sets.update_v236, '2.36', '2.56') @validation.schema(quota_sets.update_v236, '2.36', '2.56')
@@ -221,7 +221,7 @@ class QuotaSetsController(wsgi.Controller):
self._get_quotas(context, id, user_id=user_id), self._get_quotas(context, id, user_id=user_id),
filtered_quotas=filtered_quotas) filtered_quotas=filtered_quotas)
@wsgi.Controller.api_version('2.0') @wsgi.api_version('2.0')
@wsgi.expected_errors(400) @wsgi.expected_errors(400)
@validation.query_schema(quota_sets.defaults_query) @validation.query_schema(quota_sets.defaults_query)
def defaults(self, req, id): def defaults(self, req, id):

View File

@@ -37,7 +37,7 @@ class RemoteConsolesController(wsgi.Controller):
'serial': self.compute_api.get_serial_console, 'serial': self.compute_api.get_serial_console,
'mks': self.compute_api.get_mks_console} 'mks': self.compute_api.get_mks_console}
@wsgi.Controller.api_version("2.1", "2.5") @wsgi.api_version("2.1", "2.5")
@wsgi.expected_errors((400, 404, 409, 501)) @wsgi.expected_errors((400, 404, 409, 501))
@wsgi.action('os-getVNCConsole') @wsgi.action('os-getVNCConsole')
@validation.schema(schema.get_vnc_console) @validation.schema(schema.get_vnc_console)
@@ -69,7 +69,7 @@ class RemoteConsolesController(wsgi.Controller):
return {'console': {'type': console_type, 'url': output['url']}} return {'console': {'type': console_type, 'url': output['url']}}
@wsgi.Controller.api_version("2.1", "2.5") @wsgi.api_version("2.1", "2.5")
@wsgi.expected_errors((400, 404, 409, 501)) @wsgi.expected_errors((400, 404, 409, 501))
@wsgi.action('os-getSPICEConsole') @wsgi.action('os-getSPICEConsole')
@validation.schema(schema.get_spice_console) @validation.schema(schema.get_spice_console)
@@ -98,7 +98,7 @@ class RemoteConsolesController(wsgi.Controller):
return {'console': {'type': console_type, 'url': output['url']}} return {'console': {'type': console_type, 'url': output['url']}}
@wsgi.Controller.api_version("2.1", "2.5") @wsgi.api_version("2.1", "2.5")
@wsgi.expected_errors((400, 404, 409, 501)) @wsgi.expected_errors((400, 404, 409, 501))
@wsgi.action('os-getRDPConsole') @wsgi.action('os-getRDPConsole')
@wsgi.removed('29.0.0', _rdp_console_removal_reason) @wsgi.removed('29.0.0', _rdp_console_removal_reason)
@@ -109,7 +109,7 @@ class RemoteConsolesController(wsgi.Controller):
""" """
raise webob.exc.HTTPBadRequest() raise webob.exc.HTTPBadRequest()
@wsgi.Controller.api_version("2.1", "2.5") @wsgi.api_version("2.1", "2.5")
@wsgi.expected_errors((400, 404, 409, 501)) @wsgi.expected_errors((400, 404, 409, 501))
@wsgi.action('os-getSerialConsole') @wsgi.action('os-getSerialConsole')
@validation.schema(schema.get_serial_console) @validation.schema(schema.get_serial_console)
@@ -140,7 +140,7 @@ class RemoteConsolesController(wsgi.Controller):
return {'console': {'type': console_type, 'url': output['url']}} return {'console': {'type': console_type, 'url': output['url']}}
@wsgi.Controller.api_version("2.6") @wsgi.api_version("2.6")
@wsgi.expected_errors((400, 404, 409, 501)) @wsgi.expected_errors((400, 404, 409, 501))
@validation.schema(schema.create_v26, "2.6", "2.7") @validation.schema(schema.create_v26, "2.6", "2.7")
@validation.schema(schema.create_v28, "2.8", "2.98") @validation.schema(schema.create_v28, "2.8", "2.98")

View File

@@ -134,7 +134,7 @@ class SecurityGroupControllerBase(object):
class SecurityGroupController(SecurityGroupControllerBase, wsgi.Controller): class SecurityGroupController(SecurityGroupControllerBase, wsgi.Controller):
"""The Security group API controller for the OpenStack API.""" """The Security group API controller for the OpenStack API."""
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors((400, 404)) @wsgi.expected_errors((400, 404))
@validation.query_schema(schema.show_query) @validation.query_schema(schema.show_query)
def show(self, req, id): def show(self, req, id):
@@ -154,7 +154,7 @@ class SecurityGroupController(SecurityGroupControllerBase, wsgi.Controller):
return {'security_group': self._format_security_group(context, return {'security_group': self._format_security_group(context,
security_group)} security_group)}
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors((400, 404)) @wsgi.expected_errors((400, 404))
@wsgi.response(202) @wsgi.response(202)
def delete(self, req, id): def delete(self, req, id):
@@ -172,7 +172,7 @@ class SecurityGroupController(SecurityGroupControllerBase, wsgi.Controller):
except exception.Invalid as exp: except exception.Invalid as exp:
raise exc.HTTPBadRequest(explanation=exp.format_message()) raise exc.HTTPBadRequest(explanation=exp.format_message())
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@validation.query_schema(schema.index_query) @validation.query_schema(schema.index_query)
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
def index(self, req): def index(self, req):
@@ -196,7 +196,7 @@ class SecurityGroupController(SecurityGroupControllerBase, wsgi.Controller):
list(sorted(result, list(sorted(result,
key=lambda k: (k['tenant_id'], k['name'])))} key=lambda k: (k['tenant_id'], k['name'])))}
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors((400, 403)) @wsgi.expected_errors((400, 403))
@validation.schema(schema.create) @validation.schema(schema.create)
def create(self, req, body): def create(self, req, body):
@@ -219,7 +219,7 @@ class SecurityGroupController(SecurityGroupControllerBase, wsgi.Controller):
return {'security_group': self._format_security_group(context, return {'security_group': self._format_security_group(context,
group_ref)} group_ref)}
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors((400, 404)) @wsgi.expected_errors((400, 404))
@validation.schema(schema.update) @validation.schema(schema.update)
def update(self, req, id, body): def update(self, req, id, body):
@@ -254,7 +254,7 @@ class SecurityGroupController(SecurityGroupControllerBase, wsgi.Controller):
class SecurityGroupRulesController(SecurityGroupControllerBase, class SecurityGroupRulesController(SecurityGroupControllerBase,
wsgi.Controller): wsgi.Controller):
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors((400, 403, 404)) @wsgi.expected_errors((400, 403, 404))
@validation.schema(schema.create_rules) @validation.schema(schema.create_rules)
def create(self, req, body): def create(self, req, body):
@@ -327,7 +327,7 @@ class SecurityGroupRulesController(SecurityGroupControllerBase,
return security_group_api.new_cidr_ingress_rule( return security_group_api.new_cidr_ingress_rule(
cidr, ip_protocol, from_port, to_port) cidr, ip_protocol, from_port, to_port)
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors((400, 404, 409)) @wsgi.expected_errors((400, 404, 409))
@wsgi.response(202) @wsgi.response(202)
def delete(self, req, id): def delete(self, req, id):

View File

@@ -175,7 +175,7 @@ class ServerGroupController(wsgi.Controller):
for group in limited_list] for group in limited_list]
return {'server_groups': result} return {'server_groups': result}
@wsgi.Controller.api_version("2.1") @wsgi.api_version("2.1")
@wsgi.expected_errors((400, 403, 409)) @wsgi.expected_errors((400, 403, 409))
@validation.schema(schema.create, "2.0", "2.14") @validation.schema(schema.create, "2.0", "2.14")
@validation.schema(schema.create_v215, "2.15", "2.63") @validation.schema(schema.create_v215, "2.15", "2.63")

View File

@@ -65,7 +65,7 @@ class ServerMigrationsController(wsgi.Controller):
super(ServerMigrationsController, self).__init__() super(ServerMigrationsController, self).__init__()
self.compute_api = compute.API() self.compute_api = compute.API()
@wsgi.Controller.api_version("2.22") @wsgi.api_version("2.22")
@wsgi.response(202) @wsgi.response(202)
@wsgi.expected_errors((400, 403, 404, 409)) @wsgi.expected_errors((400, 403, 404, 409))
@wsgi.action('force_complete') @wsgi.action('force_complete')
@@ -91,7 +91,7 @@ class ServerMigrationsController(wsgi.Controller):
common.raise_http_conflict_for_instance_invalid_state( common.raise_http_conflict_for_instance_invalid_state(
state_error, 'force_complete', server_id) state_error, 'force_complete', server_id)
@wsgi.Controller.api_version("2.23") @wsgi.api_version("2.23")
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
@validation.query_schema(schema.index_query) @validation.query_schema(schema.index_query)
def index(self, req, server_id): def index(self, req, server_id):
@@ -114,7 +114,7 @@ class ServerMigrationsController(wsgi.Controller):
output(migration, include_uuid, include_user_project) output(migration, include_uuid, include_user_project)
for migration in migrations]} for migration in migrations]}
@wsgi.Controller.api_version("2.23") @wsgi.api_version("2.23")
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
@validation.query_schema(schema.show_query) @validation.query_schema(schema.show_query)
def show(self, req, server_id, id): def show(self, req, server_id, id):
@@ -153,7 +153,7 @@ class ServerMigrationsController(wsgi.Controller):
return {'migration': output(migration, include_uuid, return {'migration': output(migration, include_uuid,
include_user_project)} include_user_project)}
@wsgi.Controller.api_version("2.24") @wsgi.api_version("2.24")
@wsgi.response(202) @wsgi.response(202)
@wsgi.expected_errors((400, 404, 409)) @wsgi.expected_errors((400, 404, 409))
def delete(self, req, server_id, id): def delete(self, req, server_id, id):

View File

@@ -70,7 +70,7 @@ class ServerSharesController(wsgi.Controller):
) )
return instance return instance
@wsgi.Controller.api_version("2.97") @wsgi.api_version("2.97")
@wsgi.response(200) @wsgi.response(200)
@wsgi.expected_errors((400, 403, 404)) @wsgi.expected_errors((400, 403, 404))
@validation.query_schema(schema.index_query) @validation.query_schema(schema.index_query)
@@ -91,7 +91,7 @@ class ServerSharesController(wsgi.Controller):
return self._view_builder._list_view(db_shares) return self._view_builder._list_view(db_shares)
@wsgi.Controller.api_version("2.97") @wsgi.api_version("2.97")
@wsgi.response(201) @wsgi.response(201)
@wsgi.expected_errors((400, 403, 404, 409)) @wsgi.expected_errors((400, 403, 404, 409))
@validation.schema(schema.create, '2.97') @validation.schema(schema.create, '2.97')
@@ -104,7 +104,8 @@ class ServerSharesController(wsgi.Controller):
Prevent user from using the same tag twice on the same instance. Prevent user from using the same tag twice on the same instance.
""" """
try: try:
objects.ShareMapping.get_by_instance_uuid_and_share_id(context, objects.ShareMapping.get_by_instance_uuid_and_share_id(
context,
share_mapping.instance_uuid, share_mapping.share_id share_mapping.instance_uuid, share_mapping.share_id
) )
raise exception.ShareMappingAlreadyExists( raise exception.ShareMappingAlreadyExists(
@@ -196,7 +197,7 @@ class ServerSharesController(wsgi.Controller):
return view return view
@wsgi.Controller.api_version("2.97") @wsgi.api_version("2.97")
@wsgi.response(200) @wsgi.response(200)
@wsgi.expected_errors((400, 403, 404)) @wsgi.expected_errors((400, 403, 404))
@validation.query_schema(schema.show_query) @validation.query_schema(schema.show_query)
@@ -227,7 +228,7 @@ class ServerSharesController(wsgi.Controller):
return view return view
@wsgi.Controller.api_version("2.97") @wsgi.api_version("2.97")
@wsgi.response(200) @wsgi.response(200)
@wsgi.expected_errors((400, 403, 404, 409)) @wsgi.expected_errors((400, 403, 404, 409))
def delete(self, req, server_id, id): def delete(self, req, server_id, id):

View File

@@ -60,7 +60,7 @@ class ServerTagsController(wsgi.Controller):
server_id) server_id)
return instance return instance
@wsgi.Controller.api_version("2.26") @wsgi.api_version("2.26")
@wsgi.response(204) @wsgi.response(204)
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
@validation.query_schema(schema.show_query) @validation.query_schema(schema.show_query)
@@ -81,7 +81,7 @@ class ServerTagsController(wsgi.Controller):
% {'server_id': server_id, 'tag': id}) % {'server_id': server_id, 'tag': id})
raise webob.exc.HTTPNotFound(explanation=msg) raise webob.exc.HTTPNotFound(explanation=msg)
@wsgi.Controller.api_version("2.26") @wsgi.api_version("2.26")
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
@validation.query_schema(schema.index_query) @validation.query_schema(schema.index_query)
def index(self, req, server_id): def index(self, req, server_id):
@@ -98,7 +98,7 @@ class ServerTagsController(wsgi.Controller):
return {'tags': _get_tags_names(tags)} return {'tags': _get_tags_names(tags)}
@wsgi.Controller.api_version("2.26") @wsgi.api_version("2.26")
@wsgi.expected_errors((400, 404, 409)) @wsgi.expected_errors((400, 404, 409))
@validation.schema(schema.update) @validation.schema(schema.update)
def update(self, req, server_id, id, body): def update(self, req, server_id, id, body):
@@ -151,7 +151,7 @@ class ServerTagsController(wsgi.Controller):
req, server_id, id) req, server_id, id)
return response return response
@wsgi.Controller.api_version("2.26") @wsgi.api_version("2.26")
@wsgi.expected_errors((404, 409)) @wsgi.expected_errors((404, 409))
@validation.schema(schema.update_all) @validation.schema(schema.update_all)
def update_all(self, req, server_id, body): def update_all(self, req, server_id, body):
@@ -176,7 +176,7 @@ class ServerTagsController(wsgi.Controller):
return {'tags': _get_tags_names(tags)} return {'tags': _get_tags_names(tags)}
@wsgi.Controller.api_version("2.26") @wsgi.api_version("2.26")
@wsgi.response(204) @wsgi.response(204)
@wsgi.expected_errors((404, 409)) @wsgi.expected_errors((404, 409))
def delete(self, req, server_id, id): def delete(self, req, server_id, id):
@@ -201,7 +201,7 @@ class ServerTagsController(wsgi.Controller):
notifications_base.send_instance_update_notification( notifications_base.send_instance_update_notification(
context, instance, service="nova-api") context, instance, service="nova-api")
@wsgi.Controller.api_version("2.26") @wsgi.api_version("2.26")
@wsgi.response(204) @wsgi.response(204)
@wsgi.expected_errors((404, 409)) @wsgi.expected_errors((404, 409))
def delete_all(self, req, server_id): def delete_all(self, req, server_id):

View File

@@ -25,7 +25,7 @@ class ServerTopologyController(wsgi.Controller):
super(ServerTopologyController, self).__init__(*args, **kwargs) super(ServerTopologyController, self).__init__(*args, **kwargs)
self.compute_api = compute.API() self.compute_api = compute.API()
@wsgi.Controller.api_version("2.78") @wsgi.api_version("2.78")
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
@validation.query_schema(schema.query_params_v21) @validation.query_schema(schema.query_params_v21)
def index(self, req, server_id): def index(self, req, server_id):

View File

@@ -1502,7 +1502,7 @@ class ServersController(wsgi.Controller):
state_error, 'stop', id state_error, 'stop', id
) )
@wsgi.Controller.api_version("2.17") @wsgi.api_version("2.17")
@wsgi.response(202) @wsgi.response(202)
@wsgi.expected_errors((400, 404, 409)) @wsgi.expected_errors((400, 404, 409))
@wsgi.action('trigger_crash_dump') @wsgi.action('trigger_crash_dump')

View File

@@ -74,7 +74,7 @@ class TenantNetworkController(wsgi.Controller):
project_id=project_id) project_id=project_id)
return self.network_api.get_all(ctx) return self.network_api.get_all(ctx)
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors(()) @wsgi.expected_errors(())
@validation.query_schema(schema.index_query) @validation.query_schema(schema.index_query)
def index(self, req): def index(self, req):
@@ -87,7 +87,7 @@ class TenantNetworkController(wsgi.Controller):
networks.extend(self._default_networks) networks.extend(self._default_networks)
return {'networks': [network_dict(n) for n in networks]} return {'networks': [network_dict(n) for n in networks]}
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
@validation.query_schema(schema.show_query) @validation.query_schema(schema.show_query)
def show(self, req, id): def show(self, req, id):

View File

@@ -109,7 +109,7 @@ class VolumeController(wsgi.Controller):
super(VolumeController, self).__init__() super(VolumeController, self).__init__()
self.volume_api = cinder.API() self.volume_api = cinder.API()
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
@validation.query_schema(volumes_schema.show_query) @validation.query_schema(volumes_schema.show_query)
def show(self, req, id): def show(self, req, id):
@@ -125,7 +125,7 @@ class VolumeController(wsgi.Controller):
return {'volume': _translate_volume_detail_view(context, vol)} return {'volume': _translate_volume_detail_view(context, vol)}
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.response(202) @wsgi.response(202)
@wsgi.expected_errors((400, 404)) @wsgi.expected_errors((400, 404))
def delete(self, req, id): def delete(self, req, id):
@@ -141,7 +141,7 @@ class VolumeController(wsgi.Controller):
except exception.VolumeNotFound as e: except exception.VolumeNotFound as e:
raise exc.HTTPNotFound(explanation=e.format_message()) raise exc.HTTPNotFound(explanation=e.format_message())
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors(()) @wsgi.expected_errors(())
@validation.query_schema(volumes_schema.index_query) @validation.query_schema(volumes_schema.index_query)
def index(self, req): def index(self, req):
@@ -151,7 +151,7 @@ class VolumeController(wsgi.Controller):
target={'project_id': context.project_id}) target={'project_id': context.project_id})
return self._items(req, entity_maker=_translate_volume_summary_view) return self._items(req, entity_maker=_translate_volume_summary_view)
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors(()) @wsgi.expected_errors(())
@validation.query_schema(volumes_schema.detail_query) @validation.query_schema(volumes_schema.detail_query)
def detail(self, req): def detail(self, req):
@@ -170,7 +170,7 @@ class VolumeController(wsgi.Controller):
res = [entity_maker(context, vol) for vol in limited_list] res = [entity_maker(context, vol) for vol in limited_list]
return {'volumes': res} return {'volumes': res}
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors((400, 403, 404)) @wsgi.expected_errors((400, 403, 404))
@validation.schema(volumes_schema.create) @validation.schema(volumes_schema.create)
def create(self, req, body): def create(self, req, body):
@@ -607,7 +607,7 @@ class SnapshotController(wsgi.Controller):
self.volume_api = cinder.API() self.volume_api = cinder.API()
super(SnapshotController, self).__init__() super(SnapshotController, self).__init__()
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
@validation.query_schema(volumes_schema.snapshot_show_query) @validation.query_schema(volumes_schema.snapshot_show_query)
def show(self, req, id): def show(self, req, id):
@@ -623,7 +623,7 @@ class SnapshotController(wsgi.Controller):
return {'snapshot': _translate_snapshot_detail_view(context, vol)} return {'snapshot': _translate_snapshot_detail_view(context, vol)}
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.response(202) @wsgi.response(202)
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
def delete(self, req, id): def delete(self, req, id):
@@ -637,7 +637,7 @@ class SnapshotController(wsgi.Controller):
except exception.SnapshotNotFound as e: except exception.SnapshotNotFound as e:
raise exc.HTTPNotFound(explanation=e.format_message()) raise exc.HTTPNotFound(explanation=e.format_message())
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors(()) @wsgi.expected_errors(())
@validation.query_schema(volumes_schema.index_query) @validation.query_schema(volumes_schema.index_query)
def index(self, req): def index(self, req):
@@ -647,7 +647,7 @@ class SnapshotController(wsgi.Controller):
target={'project_id': context.project_id}) target={'project_id': context.project_id})
return self._items(req, entity_maker=_translate_snapshot_summary_view) return self._items(req, entity_maker=_translate_snapshot_summary_view)
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors(()) @wsgi.expected_errors(())
@validation.query_schema(volumes_schema.detail_query) @validation.query_schema(volumes_schema.detail_query)
def detail(self, req): def detail(self, req):
@@ -666,7 +666,7 @@ class SnapshotController(wsgi.Controller):
res = [entity_maker(context, snapshot) for snapshot in limited_list] res = [entity_maker(context, snapshot) for snapshot in limited_list]
return {'snapshots': res} return {'snapshots': res}
@wsgi.Controller.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION) @wsgi.api_version("2.1", MAX_PROXY_API_SUPPORT_VERSION)
@wsgi.expected_errors((400, 403)) @wsgi.expected_errors((400, 403))
@validation.schema(volumes_schema.snapshot_create) @validation.schema(volumes_schema.snapshot_create)
def create(self, req, body): def create(self, req, body):

View File

@@ -1,35 +0,0 @@
# Copyright 2014 IBM Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
class VersionedMethod(object):
def __init__(self, name, start_version, end_version, func):
"""Versioning information for a single method
@name: Name of the method
@start_version: Minimum acceptable version
@end_version: Maximum acceptable_version
@func: Method to call
Minimum and maximums are inclusive
"""
self.name = name
self.start_version = start_version
self.end_version = end_version
self.func = func
def __str__(self):
return ("Version Method %s: min: %s, max: %s"
% (self.name, self.start_version, self.end_version))

View File

@@ -24,8 +24,7 @@ from oslo_utils import encodeutils
from oslo_utils import strutils from oslo_utils import strutils
import webob import webob
from nova.api.openstack import api_version_request as api_version from nova.api.openstack import api_version_request
from nova.api.openstack import versioned_method
from nova.api import wsgi from nova.api import wsgi
from nova import exception from nova import exception
from nova import i18n from nova import i18n
@@ -59,9 +58,6 @@ _METHODS_WITH_BODY = [
# support is fully merged. It does not affect the V2 API. # support is fully merged. It does not affect the V2 API.
DEFAULT_API_VERSION = "2.1" DEFAULT_API_VERSION = "2.1"
# name of attribute to keep version method information
VER_METHOD_ATTR = 'versioned_methods'
# Names of headers used by clients to request a specific version # Names of headers used by clients to request a specific version
# of the REST API # of the REST API
API_VERSION_REQUEST_HEADER = 'OpenStack-API-Version' API_VERSION_REQUEST_HEADER = 'OpenStack-API-Version'
@@ -81,7 +77,7 @@ class Request(wsgi.Request):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(Request, self).__init__(*args, **kwargs) super(Request, self).__init__(*args, **kwargs)
if not hasattr(self, 'api_version_request'): if not hasattr(self, 'api_version_request'):
self.api_version_request = api_version.APIVersionRequest() self.api_version_request = api_version_request.APIVersionRequest()
def best_match_content_type(self): def best_match_content_type(self):
"""Determine the requested response content-type.""" """Determine the requested response content-type."""
@@ -158,25 +154,25 @@ class Request(wsgi.Request):
legacy_headers=[LEGACY_API_VERSION_REQUEST_HEADER]) legacy_headers=[LEGACY_API_VERSION_REQUEST_HEADER])
if hdr_string is None: if hdr_string is None:
self.api_version_request = api_version.APIVersionRequest( self.api_version_request = api_version_request.APIVersionRequest(
api_version.DEFAULT_API_VERSION) api_version_request.DEFAULT_API_VERSION)
elif hdr_string == 'latest': elif hdr_string == 'latest':
# 'latest' is a special keyword which is equivalent to # 'latest' is a special keyword which is equivalent to
# requesting the maximum version of the API supported # requesting the maximum version of the API supported
self.api_version_request = api_version.max_api_version() self.api_version_request = api_version_request.max_api_version()
else: else:
self.api_version_request = api_version.APIVersionRequest( self.api_version_request = api_version_request.APIVersionRequest(
hdr_string) hdr_string)
# Check that the version requested is within the global # Check that the version requested is within the global
# minimum/maximum of supported API versions # minimum/maximum of supported API versions
if not self.api_version_request.matches( if not self.api_version_request.matches(
api_version.min_api_version(), api_version_request.min_api_version(),
api_version.max_api_version()): api_version_request.max_api_version()):
raise exception.InvalidGlobalAPIVersion( raise exception.InvalidGlobalAPIVersion(
req_ver=self.api_version_request.get_string(), req_ver=self.api_version_request.get_string(),
min_ver=api_version.min_api_version().get_string(), min_ver=api_version_request.min_api_version().get_string(),
max_ver=api_version.max_api_version().get_string()) max_ver=api_version_request.max_api_version().get_string())
def set_legacy_v2(self): def set_legacy_v2(self):
self.environ[ENV_LEGACY_V2] = True self.environ[ENV_LEGACY_V2] = True
@@ -243,8 +239,8 @@ class WSGICodes:
ver = req.api_version_request ver = req.api_version_request
for code, min_version, max_version in self._codes: for code, min_version, max_version in self._codes:
min_ver = api_version.APIVersionRequest(min_version) min_ver = api_version_request.APIVersionRequest(min_version)
max_ver = api_version.APIVersionRequest(max_version) max_ver = api_version_request.APIVersionRequest(max_version)
if ver.matches(min_ver, max_ver): if ver.matches(min_ver, max_ver):
return code return code
@@ -700,6 +696,46 @@ def removed(version: str, reason: str):
return decorator return decorator
def api_version(
min_version: ty.Optional[str] = None,
max_version: ty.Optional[str] = None,
):
"""Mark an API as supporting lower and upper version bounds.
:param min_version: A string of two numerals. X.Y indicating the minimum
version of the JSON-Schema to validate against.
:param max_version: A string of two numerals. X.Y indicating the maximum
version of the JSON-Schema against to.
"""
def decorator(f):
@functools.wraps(f)
def wrapped(*args, **kwargs):
min_ver = api_version_request.APIVersionRequest(min_version)
max_ver = api_version_request.APIVersionRequest(max_version)
# The request object is always the second argument.
# However numerous unittests pass in the request object
# via kwargs instead so we handle that as well.
# TODO(cyeoh): cleanup unittests so we don't have to
# to do this
if 'req' in kwargs:
ver = kwargs['req'].api_version_request
else:
ver = args[1].api_version_request
if not ver.matches(min_ver, max_ver):
raise exception.VersionNotFoundForAPIMethod(version=ver)
return f(*args, **kwargs)
wrapped.min_version = min_version
wrapped.max_version = max_version
return wrapped
return decorator
def expected_errors( def expected_errors(
errors: ty.Union[int, tuple[int, ...]], errors: ty.Union[int, tuple[int, ...]],
min_version: ty.Optional[str] = None, min_version: ty.Optional[str] = None,
@@ -714,8 +750,8 @@ def expected_errors(
def decorator(f): def decorator(f):
@functools.wraps(f) @functools.wraps(f)
def wrapped(*args, **kwargs): def wrapped(*args, **kwargs):
min_ver = api_version.APIVersionRequest(min_version) min_ver = api_version_request.APIVersionRequest(min_version)
max_ver = api_version.APIVersionRequest(max_version) max_ver = api_version_request.APIVersionRequest(max_version)
# The request object is always the second argument. # The request object is always the second argument.
# However numerous unittests pass in the request object # However numerous unittests pass in the request object
@@ -791,36 +827,22 @@ class ControllerMetaclass(type):
def __new__(mcs, name, bases, cls_dict): def __new__(mcs, name, bases, cls_dict):
"""Adds the wsgi_actions dictionary to the class.""" """Adds the wsgi_actions dictionary to the class."""
# Find all actions # Find all actions
actions = {} actions = {}
versioned_methods = None
# start with wsgi actions from base classes # start with wsgi actions from base classes
for base in bases: for base in bases:
actions.update(getattr(base, 'wsgi_actions', {})) actions.update(getattr(base, 'wsgi_actions', {}))
if base.__name__ == "Controller":
# NOTE(cyeoh): This resets the VER_METHOD_ATTR attribute
# between API controller class creations. This allows us
# to use a class decorator on the API methods that doesn't
# require naming explicitly what method is being versioned as
# it can be implicit based on the method decorated. It is a bit
# ugly.
if VER_METHOD_ATTR in base.__dict__:
versioned_methods = getattr(base, VER_METHOD_ATTR)
delattr(base, VER_METHOD_ATTR)
for key, value in cls_dict.items(): for key, value in cls_dict.items():
if not callable(value): if not callable(value):
continue continue
if getattr(value, 'wsgi_action', None): if getattr(value, 'wsgi_action', None):
actions[value.wsgi_action] = key actions[value.wsgi_action] = key
# Add the actions to the class dict # Add the actions to the class dict
cls_dict['wsgi_actions'] = actions cls_dict['wsgi_actions'] = actions
if versioned_methods:
cls_dict[VER_METHOD_ATTR] = versioned_methods
return super(ControllerMetaclass, mcs).__new__(mcs, name, bases, return super(ControllerMetaclass, mcs).__new__(mcs, name, bases,
cls_dict) cls_dict)
@@ -837,103 +859,6 @@ class Controller(metaclass=ControllerMetaclass):
else: else:
self._view_builder = None self._view_builder = None
def __getattribute__(self, key):
def version_select(*args, **kwargs):
"""Look for the method which matches the name supplied and version
constraints and calls it with the supplied arguments.
@return: Returns the result of the method called
@raises: VersionNotFoundForAPIMethod if there is no method which
matches the name and version constraints
"""
# The first arg to all versioned methods is always the request
# object. The version for the request is attached to the
# request object
if len(args) == 0:
ver = kwargs['req'].api_version_request
else:
ver = args[0].api_version_request
func_list = self.versioned_methods[key]
for func in func_list:
if ver.matches(func.start_version, func.end_version):
# Update the version_select wrapper function so
# other decorator attributes like wsgi.response
# are still respected.
functools.update_wrapper(version_select, func.func)
return func.func(self, *args, **kwargs)
# No version match
raise exception.VersionNotFoundForAPIMethod(version=ver)
try:
version_meth_dict = object.__getattribute__(self, VER_METHOD_ATTR)
except AttributeError:
# No versioning on this class
return object.__getattribute__(self, key)
if version_meth_dict and \
key in object.__getattribute__(self, VER_METHOD_ATTR):
return version_select
return object.__getattribute__(self, key)
# NOTE(cyeoh): This decorator MUST appear first (the outermost
# decorator) on an API method for it to work correctly
@classmethod
def api_version(cls, min_ver, max_ver=None):
"""Decorator for versioning api methods.
Add the decorator to any method which takes a request object
as the first parameter and belongs to a class which inherits from
wsgi.Controller.
@min_ver: string representing minimum version
@max_ver: optional string representing maximum version
"""
def decorator(f):
obj_min_ver = api_version.APIVersionRequest(min_ver)
if max_ver:
obj_max_ver = api_version.APIVersionRequest(max_ver)
else:
obj_max_ver = api_version.APIVersionRequest()
# Add to list of versioned methods registered
func_name = f.__name__
new_func = versioned_method.VersionedMethod(
func_name, obj_min_ver, obj_max_ver, f)
func_dict = getattr(cls, VER_METHOD_ATTR, {})
if not func_dict:
setattr(cls, VER_METHOD_ATTR, func_dict)
func_list = func_dict.get(func_name, [])
if not func_list:
func_dict[func_name] = func_list
func_list.append(new_func)
# Ensure the list is sorted by minimum version (reversed)
# so later when we work through the list in order we find
# the method which has the latest version which supports
# the version requested.
is_intersect = Controller.check_for_versions_intersection(
func_list)
if is_intersect:
raise exception.ApiVersionsIntersect(
name=new_func.name,
min_ver=new_func.start_version,
max_ver=new_func.end_version,
)
func_list.sort(key=lambda f: f.start_version, reverse=True)
return f
return decorator
@staticmethod @staticmethod
def is_valid_body(body, entity_name): def is_valid_body(body, entity_name):
if not (body and entity_name in body): if not (body and entity_name in body):
@@ -948,36 +873,6 @@ class Controller(metaclass=ControllerMetaclass):
return is_dict(body[entity_name]) return is_dict(body[entity_name])
@staticmethod
def check_for_versions_intersection(func_list):
"""Determines whether function list contains version intervals
intersections or not. General algorithm:
https://en.wikipedia.org/wiki/Intersection_algorithm
:param func_list: list of VersionedMethod objects
:return: boolean
"""
pairs = []
counter = 0
for f in func_list:
pairs.append((f.start_version, 1, f))
pairs.append((f.end_version, -1, f))
def compare(x):
return x[0]
pairs.sort(key=compare)
for p in pairs:
counter += p[1]
if counter > 1:
return True
return False
class Fault(webob.exc.HTTPException): class Fault(webob.exc.HTTPException):
"""Wrap webob.exc.HTTPException to provide API friendly response.""" """Wrap webob.exc.HTTPException to provide API friendly response."""

View File

@@ -17,6 +17,7 @@ import functools
import webob import webob
from nova.api.openstack import api_version_request
from nova.api.openstack.compute import routes from nova.api.openstack.compute import routes
from nova.api.openstack import wsgi from nova.api.openstack import wsgi
from nova.api import validation from nova.api import validation
@@ -25,54 +26,50 @@ from nova.tests.unit.api.openstack.compute import dummy_schema
class MicroversionsController(wsgi.Controller): class MicroversionsController(wsgi.Controller):
@wsgi.Controller.api_version("2.1") @wsgi.api_version("2.1")
def index(self, req): def index(self, req):
data = {'param': 'val'} if api_version_request.is_supported(req, '3.0'):
return data raise webob.exc.HTTPBadRequest()
@wsgi.Controller.api_version("2.2") # noqa if api_version_request.is_supported(req, '2.2'):
def index(self, req): # noqa data = {'param': 'val2'}
data = {'param': 'val2'} else:
data = {'param': 'val'}
return data return data
@wsgi.Controller.api_version("3.0") # noqa
def index(self, req): # noqa
raise webob.exc.HTTPBadRequest()
# We have a second example controller here to help check # We have a second example controller here to help check
# for accidental dependencies between API controllers # for accidental dependencies between API controllers
# due to base class changes # due to base class changes
class MicroversionsController2(wsgi.Controller): class MicroversionsController2(wsgi.Controller):
@wsgi.Controller.api_version("2.2", "2.5") @wsgi.api_version("2.2", "3.1")
@wsgi.response(200, "2.2", "2.5")
@wsgi.response(202, "2.5", "3.1")
def index(self, req): def index(self, req):
data = {'param': 'controller2_val1'} if api_version_request.is_supported(req, '2.5'):
return data data = {'param': 'controller2_val2'}
else:
@wsgi.Controller.api_version("2.5", "3.1") # noqa data = {'param': 'controller2_val1'}
@wsgi.response(202)
def index(self, req): # noqa
data = {'param': 'controller2_val2'}
return data return data
class MicroversionsController3(wsgi.Controller): class MicroversionsController3(wsgi.Controller):
@wsgi.Controller.api_version("2.1") @wsgi.api_version("2.1")
@validation.schema(dummy_schema.dummy) @validation.schema(dummy_schema.dummy)
def create(self, req, body): def create(self, req, body):
data = {'param': 'create_val1'} data = {'param': 'create_val1'}
return data return data
@wsgi.Controller.api_version("2.1") @wsgi.api_version("2.1")
@validation.schema(dummy_schema.dummy, "2.3", "2.8") @validation.schema(dummy_schema.dummy, "2.3", "2.8")
@validation.schema(dummy_schema.dummy2, "2.9") @validation.schema(dummy_schema.dummy2, "2.9")
def update(self, req, id, body): def update(self, req, id, body):
data = {'param': 'update_val1'} data = {'param': 'update_val1'}
return data return data
@wsgi.Controller.api_version("2.1", "2.2") @wsgi.api_version("2.1", "2.2")
@wsgi.response(202) @wsgi.response(202)
@wsgi.action('foo') @wsgi.action('foo')
def _foo(self, req, id, body): def _foo(self, req, id, body):
@@ -80,24 +77,8 @@ class MicroversionsController3(wsgi.Controller):
return data return data
class MicroversionsController4(wsgi.Controller):
@wsgi.Controller.api_version("2.1")
def _create(self, req):
data = {'param': 'controller4_val1'}
return data
@wsgi.Controller.api_version("2.2") # noqa
def _create(self, req): # noqa
data = {'param': 'controller4_val2'}
return data
def create(self, req, body):
return self._create(req)
class MicroversionsExtendsBaseController(wsgi.Controller): class MicroversionsExtendsBaseController(wsgi.Controller):
@wsgi.Controller.api_version("2.1") @wsgi.api_version("2.1")
def show(self, req, id): def show(self, req, id):
return {'base_param': 'base_val'} return {'base_param': 'base_val'}
@@ -114,10 +95,6 @@ mv3_controller = functools.partial(routes._create_controller,
MicroversionsController3, []) MicroversionsController3, [])
mv4_controller = functools.partial(routes._create_controller,
MicroversionsController4, [])
mv5_controller = functools.partial(routes._create_controller, mv5_controller = functools.partial(routes._create_controller,
MicroversionsExtendsBaseController, []) MicroversionsExtendsBaseController, [])
@@ -138,9 +115,6 @@ ROUTES = (
('/microversions3/{id}/action', { ('/microversions3/{id}/action', {
'POST': [mv3_controller, 'action'] 'POST': [mv3_controller, 'action']
}), }),
('/microversions4', {
'POST': [mv4_controller, 'create']
}),
('/microversions5/{id}', { ('/microversions5/{id}', {
'GET': [mv5_controller, 'show'] 'GET': [mv5_controller, 'show']
}), }),

View File

@@ -72,8 +72,7 @@ class LegacyMicroversionsTest(test.NoDBTestCase):
self.assertIn(self.header_name, res.headers.getall('Vary')) self.assertIn(self.header_name, res.headers.getall('Vary'))
@mock.patch("nova.api.openstack.api_version_request.max_api_version") @mock.patch("nova.api.openstack.api_version_request.max_api_version")
def test_microversions_return_header_non_default(self, def test_microversions_return_header_non_default(self, mock_maxver):
mock_maxver):
mock_maxver.return_value = api_version.APIVersionRequest("2.3") mock_maxver.return_value = api_version.APIVersionRequest("2.3")
req = fakes.HTTPRequest.blank( req = fakes.HTTPRequest.blank(
@@ -255,31 +254,6 @@ class LegacyMicroversionsTest(test.NoDBTestCase):
else: else:
self.assertEqual("compute 2.10", res.headers[self.header_name]) self.assertEqual("compute 2.10", res.headers[self.header_name])
@mock.patch("nova.api.openstack.api_version_request.max_api_version")
def _test_microversions_inner_function(self, version, expected_resp,
mock_maxver):
mock_maxver.return_value = api_version.APIVersionRequest("2.2")
req = fakes.HTTPRequest.blank(
'/v2/%s/microversions4' % fakes.FAKE_PROJECT_ID)
req.headers = self._make_header(version)
req.environ['CONTENT_TYPE'] = "application/json"
req.method = 'POST'
req.body = b''
res = req.get_response(self.app)
self.assertEqual(200, res.status_int)
resp_json = jsonutils.loads(res.body)
self.assertEqual(expected_resp, resp_json['param'])
if 'nova' not in self.header_name.lower():
version = 'compute %s' % version
self.assertEqual(version, res.headers[self.header_name])
def test_microversions_inner_function_v22(self):
self._test_microversions_inner_function('2.2', 'controller4_val2')
def test_microversions_inner_function_v21(self):
self._test_microversions_inner_function('2.1', 'controller4_val1')
@mock.patch("nova.api.openstack.api_version_request.max_api_version") @mock.patch("nova.api.openstack.api_version_request.max_api_version")
def _test_microversions_actions(self, ret_code, ret_header, req_header, def _test_microversions_actions(self, ret_code, ret_header, req_header,
mock_maxver): mock_maxver):

View File

@@ -116,37 +116,12 @@ class SchemaTest(test.NoDBTestCase):
wsgi_action, wsgi_method, action_controller wsgi_action, wsgi_method, action_controller
) in wsgi_actions: ) in wsgi_actions:
func = controller.wsgi_actions[wsgi_action] func = controller.wsgi_actions[wsgi_action]
if hasattr(action_controller, 'versioned_methods'):
if wsgi_method in action_controller.versioned_methods:
# currently all our actions are unversioned and if
# this changes then we need to fix this
funcs = action_controller.versioned_methods[
wsgi_method
]
assert len(funcs) == 1
func = funcs[0].func
# method will always be POST for actions # method will always be POST for actions
_validate_func(func, method) _validate_func(func, method)
else: else:
# body validation # body validation
versioned_methods = getattr( func = getattr(controller.controller, action)
controller.controller, 'versioned_methods', {} _validate_func(func, method)
)
if action in versioned_methods:
# versioned method
for versioned_method in sorted(
versioned_methods[action],
key=lambda v: v.start_version
):
func = versioned_method.func
_validate_func(func, method)
else:
# unversioned method
func = getattr(controller.controller, action)
_validate_func(func, method)
if missing_request_schemas: if missing_request_schemas:
raise test.TestingException( raise test.TestingException(

View File

@@ -17,7 +17,6 @@ import testscenarios
import webob import webob
from nova.api.openstack import api_version_request as api_version from nova.api.openstack import api_version_request as api_version
from nova.api.openstack import versioned_method
from nova.api.openstack import wsgi from nova.api.openstack import wsgi
from nova import exception from nova import exception
from nova import test from nova import test
@@ -854,77 +853,42 @@ class ValidBodyTest(test.NoDBTestCase):
self.assertFalse(self.controller.is_valid_body(body, 'foo')) self.assertFalse(self.controller.is_valid_body(body, 'foo'))
class TestController(test.NoDBTestCase): class APIVersionTestCase(test.NoDBTestCase):
def test_check_for_versions_intersection_negative(self):
func_list = \
[versioned_method.VersionedMethod('foo',
api_version.APIVersionRequest(
'2.1'),
api_version.APIVersionRequest(
'2.4'),
None),
versioned_method.VersionedMethod('foo',
api_version.APIVersionRequest(
'2.11'),
api_version.APIVersionRequest(
'3.1'),
None),
versioned_method.VersionedMethod('foo',
api_version.APIVersionRequest(
'2.8'),
api_version.APIVersionRequest(
'2.9'),
None),
]
result = wsgi.Controller.check_for_versions_intersection(func_list= def test_api_version(self):
func_list) class FakeController(wsgi.Controller):
self.assertFalse(result) @wsgi.api_version('2.10', '2.19')
def fake_func(self, req):
return {'resources': []}
func_list = \ controller = FakeController()
[versioned_method.VersionedMethod('foo',
api_version.APIVersionRequest(
'2.12'),
api_version.APIVersionRequest(
'2.14'),
None),
versioned_method.VersionedMethod('foo',
api_version.APIVersionRequest(
'3.0'),
api_version.APIVersionRequest(
'3.4'),
None)
]
result = wsgi.Controller.check_for_versions_intersection(func_list= req = fakes.HTTPRequest.blank('', version='2.10')
func_list) self.assertEqual({'resources': []}, controller.fake_func(req))
self.assertFalse(result)
def test_check_for_versions_intersection_positive(self): req = fakes.HTTPRequest.blank('', version='2.19')
func_list = \ self.assertEqual({'resources': []}, controller.fake_func(req))
[versioned_method.VersionedMethod('foo',
api_version.APIVersionRequest(
'2.1'),
api_version.APIVersionRequest(
'2.4'),
None),
versioned_method.VersionedMethod('foo',
api_version.APIVersionRequest(
'2.3'),
api_version.APIVersionRequest(
'3.0'),
None),
versioned_method.VersionedMethod('foo',
api_version.APIVersionRequest(
'2.8'),
api_version.APIVersionRequest(
'2.9'),
None),
]
result = wsgi.Controller.check_for_versions_intersection(func_list= req = fakes.HTTPRequest.blank('', version='2.9')
func_list) self.assertRaises(
self.assertTrue(result) exception.VersionNotFoundForAPIMethod, controller.fake_func, req
)
req = fakes.HTTPRequest.blank('', version='2.20')
self.assertRaises(
exception.VersionNotFoundForAPIMethod, controller.fake_func, req
)
def test_api_version_legacy(self):
class FakeController(wsgi.Controller):
@wsgi.api_version('2.0', '2.10')
def fake_func(self, req):
return {'resources': []}
controller = FakeController()
req = fakes.HTTPRequest.blank('')
req.set_legacy_v2()
self.assertEqual({'resources': []}, controller.fake_func(req))
class ExpectedErrorTestCase(test.NoDBTestCase): class ExpectedErrorTestCase(test.NoDBTestCase):