Add REST API support for get me a network
This adds the 2.37 microversion to the REST API for automatically allocating a network, i.e. get me a network. The majority of the changes to the REST API concern request validation. 'networks' is now required in the server POST body after this microversion. The 'auto' or 'none' special network uuid values are used, but if specified, can not be specified with any other requested network values. The other special case that is checked is when the minimum compute service version is not new enough to support this change, i.e. a Mitaka compute will not have the network API code that knows how to deal with the special auto/none network IDs. Because the REST API is checking the service version, the service caches the service version after the first check. Once all computes are updated to Newton then a restart of the nova-api service(s) will be required to flush the cache. A release note is provided for this situation. The api-ref docs are also updated for this microversion including an example API sample request. The matching Tempest change to test this is here: I89b18709e0cfbbcbf9be96a91a13a1356cdf85b0 The matching python-novaclient change is here: I6636ddcd3be7bf393d2d69cc6c1ba5c7d65ff674 Implements blueprint get-me-a-network Change-Id: I89b18709e0cfbbcbf9be96a91a13a1356cdf85b0
This commit is contained in:
parent
15e536518a
commit
d727795d66
@ -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
|
||||
|
@ -224,6 +224,8 @@ keypair to the server when you create it. To create a keypair, make a
|
||||
<http://developer.openstack.org/api-ref-compute-v2.1.html#createKeypair>`__
|
||||
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
|
||||
|
8
doc/api_samples/servers/v2.37/server-create-req.json
Normal file
8
doc/api_samples/servers/v2.37/server-create-req.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"server": {
|
||||
"name": "auto-allocate-network",
|
||||
"imageRef": "70a599e0-31e7-49b7-b260-868f441e862b",
|
||||
"flavorRef": "http://openstack.example.com/flavors/1",
|
||||
"networks": "auto"
|
||||
}
|
||||
}
|
22
doc/api_samples/servers/v2.37/server-create-resp.json
Normal file
22
doc/api_samples/servers/v2.37/server-create-resp.json
Normal file
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"status": "CURRENT",
|
||||
"version": "2.36",
|
||||
"version": "2.37",
|
||||
"min_version": "2.1",
|
||||
"updated": "2013-07-23T11:33:21Z"
|
||||
}
|
||||
|
@ -22,7 +22,7 @@
|
||||
}
|
||||
],
|
||||
"status": "CURRENT",
|
||||
"version": "2.36",
|
||||
"version": "2.37",
|
||||
"min_version": "2.1",
|
||||
"updated": "2013-07-23T11:33:21Z"
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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': {
|
||||
|
@ -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."""
|
||||
|
||||
|
@ -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.
|
||||
|
@ -0,0 +1,8 @@
|
||||
{
|
||||
"server": {
|
||||
"name": "auto-allocate-network",
|
||||
"imageRef": "%(image_id)s",
|
||||
"flavorRef": "%(host)s/flavors/1",
|
||||
"networks": "auto"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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-<uuid> 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 {}
|
||||
|
38
releasenotes/notes/get-me-a-network-992eabc81b5e5347.yaml
Normal file
38
releasenotes/notes/get-me-a-network-992eabc81b5e5347.yaml
Normal file
@ -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.
|
Loading…
Reference in New Issue
Block a user