Nova uses a framework we call ‘API Microversions’ for allowing changes to the API while preserving backward compatibility. The basic idea is that a user has to explicitly ask for their request to be treated with a particular version of the API. So breaking changes can be added to the API without breaking users who don’t specifically ask for it. This is done with an HTTP header X-OpenStack-Nova-API-Version which is a monotonically increasing semantic version number starting from 2.1.
If a user makes a request without specifying a version, they will get the DEFAULT_API_VERSION as defined in nova/api/openstack/wsgi.py. This value is currently 2.1 and is expected to remain so for quite a long time.
There is a special value latest which can be specified, which will allow a client to always recieve the most recent version of API responses from the server.
For full details please read the Kilo spec for microversions
In nova/api/openstack/wsgi.py we define an @api_version decorator which is intended to be used on top-level Controller methods. It is not appropriate for lower-level methods. Some examples:
In the controller class:
@wsgi.Controller.api_version("2.4")
def my_api_method(self, req, id):
....
This method would only be available if the caller had specified an X-OpenStack-Nova-API-Version of >= 2.4. If they had specified a lower version (or not specified it and received the default of 2.1) the server would respond with HTTP/404.
In the controller class:
@wsgi.Controller.api_version("2.1", "2.4")
def my_api_method(self, req, id):
....
This method would only be available if the caller had specified an X-OpenStack-Nova-API-Version of <= 2.4. If 2.5 or later is specified the server will respond with HTTP/404.
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 method may have only small changes between microversions, in which case you can decorate a private method:
@api_version("2.1", "2.4")
def _version_specific_func(self, req, arg1):
pass
@api_version(min_version="2.5") #noqa
def _version_specific_func(self, req, arg1):
pass
def show(self, req, id):
.... common stuff ....
self._version_specific_func(req, "foo")
.... common stuff ....
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 decorator:
@wsgi.Controller.api_version("2.1")
@validation.schema(dummy_schema.dummy, "2.3", "2.8")
@validation.schema(dummy_schema.dummy2, "2.9")
def update(self, req, id, body):
....
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.dummy2 from 2.9 onward.
If you are adding a patch which adds a new microversion, it is necessary to add changes to other places which describe your change:
Testing a microversioned API method is very similar to a normal controller method test, you just need to add the X-OpenStack-Nova-API-Version header, for example:
req = fakes.HTTPRequest.blank('/testable/url/endpoint')
req.headers = {'X-OpenStack-Nova-API-Version': '2.2'}
req.api_version_request = api_version.APIVersionRequest('2.6')
controller = controller.TestableController()
res = controller.index(req)
... assertions about the response ...
For many examples of testing, the canonical examples are in nova/tests/unit/api/openstack/compute/test_microversions.py.