diff --git a/api-ref/source/parameters.yaml b/api-ref/source/parameters.yaml index f43685272b95..f72bf124e337 100644 --- a/api-ref/source/parameters.yaml +++ b/api-ref/source/parameters.yaml @@ -3001,6 +3001,9 @@ network_uuid: To provision the server instance with a NIC for a network, specify the UUID of the network in the ``uuid`` attribute in a ``networks`` object. Required if you omit the ``port`` attribute. + + Starting with microversion 2.37, this value is strictly enforced to be in + UUID format. in: body required: false type: string @@ -3020,6 +3023,16 @@ networks: metadata API and the config drive and is associated to hardware metadata for that network interface, such as bus (ex: PCI), bus address (ex: 0000:00:02.0), and MAC address. + + Starting with microversion 2.37, this field is required and the special + values *auto* and *none* can be specified for networks. *auto* tells the + Compute service to use a network that is available to the project, if one + exists. If one does not exist, the Compute service will attempt to + automatically allocate a network for the project (if possible). *none* + tells the Compute service to not allocate a network for the instance. The + *auto* and *none* values cannot be used with any other network values, + including other network uuids, ports or fixed IPs. These are requested as + strings for the networks value, not in a list. See the associated example. in: body required: false type: object diff --git a/api-ref/source/servers.inc b/api-ref/source/servers.inc index 55c3523cf152..b7d2e6aac2f9 100644 --- a/api-ref/source/servers.inc +++ b/api-ref/source/servers.inc @@ -224,6 +224,8 @@ keypair to the server when you create it. To create a keypair, make a `__ request. +.. note:: Starting with microversion 2.37 the ``networks`` field is required. + Preconditions - The user must have sufficient server quota to create the number of @@ -314,9 +316,9 @@ Request .. literalinclude:: ../../doc/api_samples/servers/server-create-req.json :language: javascript -**Example Create Server (v2.32)** +**Example Create Server With Automatic Networking (v2.37)** -.. literalinclude:: ../../doc/api_samples/servers/v2.32/server-create-req.json +.. literalinclude:: ../../doc/api_samples/servers/v2.37/server-create-req.json :language: javascript Response diff --git a/doc/api_samples/servers/v2.37/server-create-req.json b/doc/api_samples/servers/v2.37/server-create-req.json new file mode 100644 index 000000000000..672a33852f8c --- /dev/null +++ b/doc/api_samples/servers/v2.37/server-create-req.json @@ -0,0 +1,8 @@ +{ + "server": { + "name": "auto-allocate-network", + "imageRef": "70a599e0-31e7-49b7-b260-868f441e862b", + "flavorRef": "http://openstack.example.com/flavors/1", + "networks": "auto" + } +} diff --git a/doc/api_samples/servers/v2.37/server-create-resp.json b/doc/api_samples/servers/v2.37/server-create-resp.json new file mode 100644 index 000000000000..ff1c136f252d --- /dev/null +++ b/doc/api_samples/servers/v2.37/server-create-resp.json @@ -0,0 +1,22 @@ +{ + "server": { + "adminPass": "rySfUy7xL4C5", + "OS-DCF:diskConfig": "AUTO", + "id": "19923676-e78b-46fb-af62-a5942aece2ac", + "links": [ + { + "href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/servers/19923676-e78b-46fb-af62-a5942aece2ac", + "rel": "self" + }, + { + "href": "http://openstack.example.com/6f70656e737461636b20342065766572/servers/19923676-e78b-46fb-af62-a5942aece2ac", + "rel": "bookmark" + } + ], + "security_groups": [ + { + "name": "default" + } + ] + } +} \ No newline at end of file diff --git a/doc/api_samples/versions/v21-version-get-resp.json b/doc/api_samples/versions/v21-version-get-resp.json index 2c78868b6229..731af39aa821 100644 --- a/doc/api_samples/versions/v21-version-get-resp.json +++ b/doc/api_samples/versions/v21-version-get-resp.json @@ -19,7 +19,7 @@ } ], "status": "CURRENT", - "version": "2.36", + "version": "2.37", "min_version": "2.1", "updated": "2013-07-23T11:33:21Z" } diff --git a/doc/api_samples/versions/versions-get-resp.json b/doc/api_samples/versions/versions-get-resp.json index 643a7c3ffa71..c2310bee180f 100644 --- a/doc/api_samples/versions/versions-get-resp.json +++ b/doc/api_samples/versions/versions-get-resp.json @@ -22,7 +22,7 @@ } ], "status": "CURRENT", - "version": "2.36", + "version": "2.37", "min_version": "2.1", "updated": "2013-07-23T11:33:21Z" } diff --git a/nova/api/openstack/api_version_request.py b/nova/api/openstack/api_version_request.py index 11f7280332da..3862da7cc1c6 100644 --- a/nova/api/openstack/api_version_request.py +++ b/nova/api/openstack/api_version_request.py @@ -90,6 +90,9 @@ REST_API_VERSION_HISTORY = """REST API Version History: * 2.35 - Adds keypairs pagination support. * 2.36 - Deprecates all the API which proxy to another service and fping API. + * 2.37 - Adds support for auto-allocating networking, otherwise known as + "Get me a Network". Also enforces server.networks.uuid to be in + UUID format. """ # The minimum and maximum versions of the API supported @@ -98,7 +101,7 @@ REST_API_VERSION_HISTORY = """REST API Version History: # Note(cyeoh): This only applies for the v2.1 API once microversions # support is fully merged. It does not affect the V2 API. _MIN_API_VERSION = "2.1" -_MAX_API_VERSION = "2.36" +_MAX_API_VERSION = "2.37" DEFAULT_API_VERSION = _MIN_API_VERSION # All the proxy APIs which related network, images and baremetal diff --git a/nova/api/openstack/compute/schemas/servers.py b/nova/api/openstack/compute/schemas/servers.py index 30293285d792..fce497e821b7 100644 --- a/nova/api/openstack/compute/schemas/servers.py +++ b/nova/api/openstack/compute/schemas/servers.py @@ -79,6 +79,32 @@ base_create_v232['properties']['server'][ 'properties']['tag'] = server_tags.tag +# 2.37 builds on 2.32 and makes the following changes: +# 1. server.networks is required +# 2. server.networks is now either an enum or a list +# 3. server.networks.uuid is now required to be a uuid +base_create_v237 = copy.deepcopy(base_create_v232) +base_create_v237['properties']['server']['required'].append('networks') +base_create_v237['properties']['server']['properties']['networks'] = { + 'oneOf': [ + {'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'fixed_ip': parameter_types.ip_address, + 'port': { + 'oneOf': [{'type': 'string', 'format': 'uuid'}, + {'type': 'null'}] + }, + 'uuid': {'type': 'string', 'format': 'uuid'}, + }, + 'additionalProperties': False, + }, + }, + {'type': 'string', 'enum': ['none', 'auto']}, + ]} + + base_update = { 'type': 'object', 'properties': { diff --git a/nova/api/openstack/compute/servers.py b/nova/api/openstack/compute/servers.py index af86141a6560..a387675d9cb6 100644 --- a/nova/api/openstack/compute/servers.py +++ b/nova/api/openstack/compute/servers.py @@ -77,6 +77,7 @@ class ServersController(wsgi.Controller): schema_server_rebuild_v219 = schema_servers.base_rebuild_v219 schema_server_create_v232 = schema_servers.base_create_v232 + schema_server_create_v237 = schema_servers.base_create_v237 @staticmethod def _add_location(robj): @@ -169,6 +170,9 @@ class ServersController(wsgi.Controller): invoke_kwds={"extension_info": self.extension_info}, propagate_map_exceptions=True) if list(self.create_schema_manager): + self.create_schema_manager.map(self._create_extension_schema, + self.schema_server_create_v237, + '2.37') self.create_schema_manager.map(self._create_extension_schema, self.schema_server_create_v232, '2.32') @@ -400,9 +404,51 @@ class ServersController(wsgi.Controller): expl = _("Duplicate networks (%s) are not allowed") % net_id raise exc.HTTPBadRequest(explanation=expl) + @staticmethod + def _validate_auto_or_none_network_request(requested_networks): + """Validates a set of network requests with 'auto' or 'none' in it. + + :param requested_networks: nova.objects.NetworkRequestList + :returns: nova.objects.NetworkRequestList - the same list as the input + if validation is OK, None if the request can't be honored due to + the minimum nova-compute service version in the deployment is not + new enough to support auto-allocated networking. + """ + + # Check the minimum nova-compute service version since Mitaka + # computes can't handle network IDs that aren't UUIDs. + # TODO(mriedem): Remove this check in Ocata. + min_compute_version = objects.Service.get_minimum_version( + nova_context.get_admin_context(), 'nova-compute') + + # NOTE(mriedem): If the minimum compute version is not new enough + # for supporting auto-allocation, change the network request to + # None so it works the same as it did in Mitaka when you don't + # request anything. + if min_compute_version < 12: + LOG.debug("User requested network '%s' but the minimum " + "nova-compute version in the deployment is not new " + "enough to support that request, so treating it as " + "though a specific network was not requested.", + requested_networks[0].network_id) + return None + return requested_networks + def _get_requested_networks(self, requested_networks, supports_device_tagging=False): """Create a list of requested networks from the networks attribute.""" + + # Starting in the 2.37 microversion, requested_networks is either a + # list or a string enum with value 'auto' or 'none'. The auto/none + # values are verified via jsonschema so we don't check them again here. + if isinstance(requested_networks, six.string_types): + req_nets = objects.NetworkRequestList( + objects=[objects.NetworkRequest( + network_id=requested_networks)]) + # Check the minimum nova-compute service version for supporting + # these special values. + return self._validate_auto_or_none_network_request(req_nets) + networks = [] network_uuids = [] for network in requested_networks: @@ -461,7 +507,8 @@ class ServersController(wsgi.Controller): @validation.schema(schema_server_create_v20, '2.0', '2.0') @validation.schema(schema_server_create, '2.1', '2.18') @validation.schema(schema_server_create_v219, '2.19', '2.31') - @validation.schema(schema_server_create_v232, '2.32') + @validation.schema(schema_server_create_v232, '2.32', '2.36') + @validation.schema(schema_server_create_v237, '2.37') def create(self, req, body): """Creates a new server for a given user.""" diff --git a/nova/api/openstack/rest_api_version_history.rst b/nova/api/openstack/rest_api_version_history.rst index f44e4a3235b5..c3bff4549938 100644 --- a/nova/api/openstack/rest_api_version_history.rst +++ b/nova/api/openstack/rest_api_version_history.rst @@ -385,3 +385,23 @@ user documentation. '/os-snapshots' '/os-baremetal-nodes' '/os-fping' + +2.37 +---- + + Added support for automatic allocation of networking, also known as "Get Me a + Network". With this microversion, when requesting the creation of a new + server (or servers) the ``networks`` entry in the ``server`` portion of the + request body is required. The ``networks`` object in the request can either + be a list or an enum with values: + + #. *none* which means no networking will be allocated for the created + server(s). + #. *auto* which means either a network that is already available to the + project will be used, or if one does not exist, will be automatically + created for the project. Automatic network allocation for a project only + happens once for a project. Subsequent requests using *auto* for the same + project will reuse the network that was previously allocated. + + Also, the ``uuid`` field in the ``networks`` object in the server create + request is now strictly enforced to be in UUID format. diff --git a/nova/tests/functional/api_sample_tests/api_samples/servers/v2.37/server-create-req.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/servers/v2.37/server-create-req.json.tpl new file mode 100644 index 000000000000..74e1545127e0 --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/servers/v2.37/server-create-req.json.tpl @@ -0,0 +1,8 @@ +{ + "server": { + "name": "auto-allocate-network", + "imageRef": "%(image_id)s", + "flavorRef": "%(host)s/flavors/1", + "networks": "auto" + } +} diff --git a/nova/tests/functional/api_sample_tests/api_samples/servers/v2.37/server-create-resp.json.tpl b/nova/tests/functional/api_sample_tests/api_samples/servers/v2.37/server-create-resp.json.tpl new file mode 100644 index 000000000000..4b30e0cfbdb8 --- /dev/null +++ b/nova/tests/functional/api_sample_tests/api_samples/servers/v2.37/server-create-resp.json.tpl @@ -0,0 +1,22 @@ +{ + "server": { + "OS-DCF:diskConfig": "AUTO", + "adminPass": "%(password)s", + "id": "%(id)s", + "links": [ + { + "href": "%(versioned_compute_endpoint)s/servers/%(uuid)s", + "rel": "self" + }, + { + "href": "%(compute_endpoint)s/servers/%(uuid)s", + "rel": "bookmark" + } + ], + "security_groups": [ + { + "name": "default" + } + ] + } +} diff --git a/nova/tests/functional/api_sample_tests/test_servers.py b/nova/tests/functional/api_sample_tests/test_servers.py index a2686436a160..647bae93c887 100644 --- a/nova/tests/functional/api_sample_tests/test_servers.py +++ b/nova/tests/functional/api_sample_tests/test_servers.py @@ -157,6 +157,15 @@ class ServersSampleJson232Test(ServersSampleBase): self._post_server(use_common_server_api_samples=False) +class ServersSampleJson237Test(ServersSampleBase): + microversion = '2.37' + sample_dir = 'servers' + scenarios = [('v2_37', {'api_major_version': 'v2.1'})] + + def test_servers_post(self): + self._post_server(use_common_server_api_samples=False) + + class ServersUpdateSampleJsonTest(ServersSampleBase): def test_update_server(self): diff --git a/nova/tests/functional/wsgi/test_servers.py b/nova/tests/functional/wsgi/test_servers.py index 9c7a44cc9cef..12e71aeb1615 100644 --- a/nova/tests/functional/wsgi/test_servers.py +++ b/nova/tests/functional/wsgi/test_servers.py @@ -52,6 +52,7 @@ class ServersPreSchedulingTestCase(test.TestCase): 'name': 'foo', 'imageRef': image_ref, 'flavorRef': '1', + 'networks': 'none', } } create_resp = self.api.api_post('servers', body) @@ -68,6 +69,7 @@ class ServersPreSchedulingTestCase(test.TestCase): 'name': 'foo', 'imageRef': image_ref, 'flavorRef': '1', + 'networks': 'none', } } create_resp = self.api.api_post('servers', body) diff --git a/nova/tests/unit/api/openstack/compute/test_serversV21.py b/nova/tests/unit/api/openstack/compute/test_serversV21.py index 29a9a4730c13..8a1a7f344dca 100644 --- a/nova/tests/unit/api/openstack/compute/test_serversV21.py +++ b/nova/tests/unit/api/openstack/compute/test_serversV21.py @@ -26,6 +26,7 @@ from mox3 import mox from oslo_policy import policy as oslo_policy from oslo_serialization import jsonutils from oslo_utils import timeutils +import six from six.moves import range import six.moves.urllib.parse as urlparse import testtools @@ -3441,6 +3442,124 @@ class ServersControllerCreateTestV232(test.NoDBTestCase): self._create_server() +class ServersControllerCreateTestV237(test.NoDBTestCase): + """Tests server create scenarios with the v2.37 microversion. + + These tests are mostly about testing the validation on the 2.37 + server create request with emphasis on negative scenarios. + """ + def setUp(self): + super(ServersControllerCreateTestV237, self).setUp() + # Set the use_neutron flag to process requested networks. + self.flags(use_neutron=True) + # Create the server controller. + ext_info = extension_info.LoadedExtensionInfo() + self.controller = servers.ServersController(extension_info=ext_info) + # Define a basic server create request body which tests can customize. + self.body = { + 'server': { + 'name': 'auto-allocate-test', + 'imageRef': '6b0edabb-8cde-4684-a3f4-978960a51378', + 'flavorRef': '2', + }, + } + # Create a fake request using the 2.37 microversion. + self.req = fakes.HTTPRequestV21.blank('/fake/servers', version='2.37') + self.req.method = 'POST' + self.req.headers['content-type'] = 'application/json' + + def _create_server(self, networks): + self.body['server']['networks'] = networks + self.req.body = jsonutils.dump_as_bytes(self.body) + return self.controller.create(self.req, body=self.body).obj['server'] + + def test_create_server_auth_pre_2_37_fails(self): + """Negative test to make sure you can't pass 'auto' before 2.37""" + self.req.api_version_request = \ + api_version_request.APIVersionRequest('2.36') + self.assertRaises(exception.ValidationError, self._create_server, + 'auto') + + def test_create_server_no_requested_networks_fails(self): + """Negative test for a server create request with no networks requested + which should fail with the v2.37 schema validation. + """ + self.assertRaises(exception.ValidationError, self._create_server, None) + + def test_create_server_network_id_not_uuid_fails(self): + """Negative test for a server create request where the requested + network id is not one of the auto/none enums. + """ + self.assertRaises(exception.ValidationError, self._create_server, + 'not-auto-or-none') + + def test_create_server_network_id_empty_string_fails(self): + """Negative test for a server create request where the requested + network id is the empty string. + """ + self.assertRaises(exception.ValidationError, self._create_server, '') + + @mock.patch.object(objects.Flavor, 'get_by_flavor_id', + side_effect=exception.FlavorNotFound(flavor_id='2')) + def test_create_server_auto_flavornotfound(self, + get_flavor): + """Tests that requesting auto networking is OK. This test + short-circuits on a FlavorNotFound error. + """ + ex = self.assertRaises( + webob.exc.HTTPBadRequest, self._create_server, 'auto') + # make sure it was a flavor not found error and not something else + self.assertIn('Flavor 2 could not be found', six.text_type(ex)) + + @mock.patch.object(objects.Flavor, 'get_by_flavor_id', + side_effect=exception.FlavorNotFound(flavor_id='2')) + def test_create_server_none_flavornotfound(self, + get_flavor): + """Tests that requesting none for networking is OK. This test + short-circuits on a FlavorNotFound error. + """ + ex = self.assertRaises( + webob.exc.HTTPBadRequest, self._create_server, 'none') + # make sure it was a flavor not found error and not something else + self.assertIn('Flavor 2 could not be found', six.text_type(ex)) + + @mock.patch.object(objects.Flavor, 'get_by_flavor_id', + side_effect=exception.FlavorNotFound(flavor_id='2')) + def test_create_server_multiple_specific_nics_flavornotfound(self, + get_flavor): + """Tests that requesting multiple specific network IDs is OK. This test + short-circuits on a FlavorNotFound error. + """ + ex = self.assertRaises( + webob.exc.HTTPBadRequest, self._create_server, + [{'uuid': 'e3b686a8-b91d-4a61-a3fc-1b74bb619ddb'}, + {'uuid': 'e0f00941-f85f-46ec-9315-96ded58c2f14'}]) + # make sure it was a flavor not found error and not something else + self.assertIn('Flavor 2 could not be found', six.text_type(ex)) + + def test_create_server_legacy_neutron_network_id_fails(self): + """Tests that we no longer support the legacy br- format for + a network id. + """ + uuid = 'br-00000000-0000-0000-0000-000000000000' + self.assertRaises(exception.ValidationError, self._create_server, + [{'uuid': uuid}]) + + @mock.patch.object(objects.Service, 'get_minimum_version', + return_value=11) + def test_validate_auto_or_none_network_request_old_computes(self, + mock_get_ver): + """Tests that the network request is nulled out when the minimum + nova-compute is not running new enough code to support 'auto'. + """ + req_nets = objects.NetworkRequestList( + objects=[objects.NetworkRequest(network_id='auto')]) + self.assertIsNone( + self.controller._validate_auto_or_none_network_request( + req_nets)) + mock_get_ver.assert_called_once_with(mock.ANY, 'nova-compute') + + class ServersControllerCreateTestWithMock(test.TestCase): image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6' flavor_ref = 'http://localhost/123/flavors/3' @@ -4130,7 +4249,7 @@ class FakeExt(extensions.V21APIExtensionBase): pass def fake_schema_extension_point(self, version): - if version in ('2.1', '2.19', '2.32'): + if version in ('2.1', '2.19', '2.32', '2.37'): return self.fake_schema elif version == '2.0': return {} diff --git a/releasenotes/notes/get-me-a-network-992eabc81b5e5347.yaml b/releasenotes/notes/get-me-a-network-992eabc81b5e5347.yaml new file mode 100644 index 000000000000..0d9a2f7a694e --- /dev/null +++ b/releasenotes/notes/get-me-a-network-992eabc81b5e5347.yaml @@ -0,0 +1,38 @@ +--- +features: + - | + The 2.37 microversion adds support for automatic allocation of network + resources for a project when ``networks: auto`` is specified in a server + create request. If the project does not have any networks available to it + and the ``auto-allocated-topology`` API is available in the Neutron + networking service, Nova will call that API to allocate resources for the + project. There is some setup required in the deployment for the + ``auto-allocated-topology`` API to work in Neutron. See the + `Additional features`_ section of the OpenStack Networking Guide + for more details for setting up this feature in Neutron. + + .. note:: The API does not default to 'auto'. However, python-novaclient + will default to passing 'auto' for this microversion if no specific + network values are provided to the CLI. + + .. note:: This feature is not available until all of the compute services + in the deployment are running Newton code. This is to avoid sending a + server create request to a Mitaka compute that can not understand a + network ID of 'auto' or 'none'. If this is the case, the API will treat + the request as if ``networks`` was not in the server create request body. + Once all computes are upgraded to Newton, a restart of the nova-api + service will be required to use this new feature. + + .. _Additional features: http://docs.openstack.org/networking-guide/intro-os-networking-features.html + +upgrade: + - | + The 2.37 microversion enforces the following: + + * ``networks`` is required in the server create request body for the API. + Specifying ``networks: auto`` is similar to not requesting specific + networks when creating a server before 2.37. + * The ``uuid`` field in the ``networks`` object of a server create request + is now required to be in UUID format, it cannot be a random string. More + specifically, the API used to support a nic uuid with a "br-" prefix but + that is a legacy artifact which is no longer supported.