summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatt Riedemann <mriedem@us.ibm.com>2016-08-09 13:08:09 -0400
committerMatt Riedemann <mriedem@us.ibm.com>2016-08-19 21:32:52 -0400
commit030ce53d4ea6a6d80bad97a9f16a96a4a22bfc9e (patch)
tree8f43f35237757fa637cdd5f388f439c246f63193
parent84d86d3f3cfd8e64967c624b022685e53b7e75f3 (diff)
downloadpython-novaclient-030ce53d4ea6a6d80bad97a9f16a96a4a22bfc9e.tar.gz
Add support for v2.37 and auto-allocated networking
This adds support for the v2.37 microversion. The networks part of the server create request is required in this microversion so if nothing is specified for --nic arguments on the command line we default to 'auto' for backward compatibility in the CLI. Part of blueprint get-me-a-network Change-Id: I6636ddcd3be7bf393d2d69cc6c1ba5c7d65ff674
-rw-r--r--novaclient/__init__.py2
-rw-r--r--novaclient/tests/functional/v2/test_servers.py56
-rw-r--r--novaclient/tests/unit/v2/test_servers.py73
-rw-r--r--novaclient/tests/unit/v2/test_shell.py59
-rw-r--r--novaclient/v2/servers.py71
-rw-r--r--novaclient/v2/shell.py59
-rw-r--r--releasenotes/notes/microversion-2.37-d03da96406a45e67.yaml27
7 files changed, 315 insertions, 32 deletions
diff --git a/novaclient/__init__.py b/novaclient/__init__.py
index 942f43ca..f9270766 100644
--- a/novaclient/__init__.py
+++ b/novaclient/__init__.py
@@ -25,4 +25,4 @@ API_MIN_VERSION = api_versions.APIVersion("2.1")
# when client supported the max version, and bumped sequentially, otherwise
# the client may break due to server side new version may include some
# backward incompatible change.
-API_MAX_VERSION = api_versions.APIVersion("2.36")
+API_MAX_VERSION = api_versions.APIVersion("2.37")
diff --git a/novaclient/tests/functional/v2/test_servers.py b/novaclient/tests/functional/v2/test_servers.py
index 6d03b791..3d8c5128 100644
--- a/novaclient/tests/functional/v2/test_servers.py
+++ b/novaclient/tests/functional/v2/test_servers.py
@@ -172,3 +172,59 @@ class TestServersTagsV226(base.ClientTestBase):
uuid = self._boot_server_with_tags()
self.nova("server-tag-delete-all %s" % uuid)
self.assertEqual([], self.client.servers.tag_list(uuid))
+
+
+class TestServersAutoAllocateNetworkCLI(base.ClientTestBase):
+
+ COMPUTE_API_VERSION = '2.37'
+
+ def _find_network_in_table(self, table):
+ # Example:
+ # +-----------------+-----------------------------------+
+ # | Property | Value |
+ # +-----------------+-----------------------------------+
+ # | private network | 192.168.154.128 |
+ # +-----------------+-----------------------------------+
+ for line in table.split('\n'):
+ if '|' in line:
+ l_property, l_value = line.split('|')[1:3]
+ if ' network' in l_property.strip():
+ return ' '.join(l_property.strip().split()[:-1])
+
+ def test_boot_server_with_auto_network(self):
+ """Tests that the CLI defaults to 'auto' when --nic isn't specified.
+ """
+ server_info = self.nova('boot', params=(
+ '%(name)s --flavor %(flavor)s --poll '
+ '--image %(image)s ' % {'name': self.name_generate('server'),
+ 'flavor': self.flavor.id,
+ 'image': self.image.id}))
+ server_id = self._get_value_from_the_table(server_info, 'id')
+ self.addCleanup(self.wait_for_resource_delete,
+ server_id, self.client.servers)
+ self.addCleanup(self.client.servers.delete, server_id)
+ # get the server details to verify there is a network, we don't care
+ # what the network name is, we just want to see an entry show up
+ server_info = self.nova('show', params=server_id)
+ network = self._find_network_in_table(server_info)
+ self.assertIsNotNone(
+ network, 'Auto-allocated network not found: %s' % server_info)
+
+ def test_boot_server_with_no_network(self):
+ """Tests that '--nic none' is honored.
+ """
+ server_info = self.nova('boot', params=(
+ '%(name)s --flavor %(flavor)s --poll '
+ '--image %(image)s --nic none' %
+ {'name': self.name_generate('server'),
+ 'flavor': self.flavor.id,
+ 'image': self.image.id}))
+ server_id = self._get_value_from_the_table(server_info, 'id')
+ self.addCleanup(self.wait_for_resource_delete,
+ server_id, self.client.servers)
+ self.addCleanup(self.client.servers.delete, server_id)
+ # get the server details to verify there is not a network
+ server_info = self.nova('show', params=server_id)
+ network = self._find_network_in_table(server_info)
+ self.assertIsNone(
+ network, 'Unexpected network allocation: %s' % server_info)
diff --git a/novaclient/tests/unit/v2/test_servers.py b/novaclient/tests/unit/v2/test_servers.py
index 06d99309..2e057b03 100644
--- a/novaclient/tests/unit/v2/test_servers.py
+++ b/novaclient/tests/unit/v2/test_servers.py
@@ -42,6 +42,11 @@ class ServersTest(utils.FixturedTestCase):
if self.api_version:
self.cs.api_version = api_versions.APIVersion(self.api_version)
+ def _get_server_create_default_nics(self):
+ """Callback for default nics kwarg when creating a server.
+ """
+ return None
+
def test_list_servers(self):
sl = self.cs.servers.list()
self.assert_request_id(sl, fakes.FAKE_REQUEST_ID_LIST)
@@ -132,7 +137,8 @@ class ServersTest(utils.FixturedTestCase):
files={
'/etc/passwd': 'some data', # a file
'/tmp/foo.txt': six.StringIO('data'), # a stream
- }
+ },
+ nics=self._get_server_create_default_nics()
)
self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST)
self.assert_called('POST', '/servers')
@@ -191,7 +197,8 @@ class ServersTest(utils.FixturedTestCase):
meta={'foo': 'bar'},
userdata="hello moto",
key_name="fakekey",
- block_device_mapping_v2=bdm
+ block_device_mapping_v2=bdm,
+ nics=self._get_server_create_default_nics()
)
self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST)
self.assert_called('POST', '/os-volumes_boot')
@@ -239,7 +246,8 @@ class ServersTest(utils.FixturedTestCase):
userdata="hello moto",
key_name="fakekey",
access_ip_v6=access_ip_v6,
- access_ip_v4=access_ip_v4
+ access_ip_v4=access_ip_v4,
+ nics=self._get_server_create_default_nics()
)
self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST)
self.assert_called('POST', '/servers')
@@ -256,6 +264,7 @@ class ServersTest(utils.FixturedTestCase):
'/etc/passwd': 'some data', # a file
'/tmp/foo.txt': six.StringIO('data'), # a stream
},
+ nics=self._get_server_create_default_nics(),
)
self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST)
self.assert_called('POST', '/servers')
@@ -273,6 +282,7 @@ class ServersTest(utils.FixturedTestCase):
'/etc/passwd': 'some data', # a file
'/tmp/foo.txt': six.StringIO('data'), # a stream
},
+ nics=self._get_server_create_default_nics(),
)
self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST)
self.assert_called('POST', '/servers')
@@ -290,6 +300,7 @@ class ServersTest(utils.FixturedTestCase):
'/etc/passwd': 'some data', # a file
'/tmp/foo.txt': six.StringIO('data'), # a stream
},
+ nics=self._get_server_create_default_nics(),
)
self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST)
self.assert_called('POST', '/servers')
@@ -303,7 +314,8 @@ class ServersTest(utils.FixturedTestCase):
image=1,
flavor=1,
admin_pass=test_password,
- key_name=test_key
+ key_name=test_key,
+ nics=self._get_server_create_default_nics()
)
self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST)
self.assert_called('POST', '/servers')
@@ -328,6 +340,7 @@ class ServersTest(utils.FixturedTestCase):
'/etc/passwd': 'some data', # a file
'/tmp/foo.txt': six.StringIO('data'), # a stream
},
+ nics=self._get_server_create_default_nics(),
)
self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST)
self.assert_called('POST', '/servers')
@@ -343,7 +356,8 @@ class ServersTest(utils.FixturedTestCase):
name="My server",
image=1,
flavor=1,
- disk_config=disk_config
+ disk_config=disk_config,
+ nics=self._get_server_create_default_nics(),
)
self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST)
self.assert_called('POST', '/servers')
@@ -976,6 +990,16 @@ class ServersTest(utils.FixturedTestCase):
key_name="fakekey"
)
+ def test_create_server_with_nics_auto(self):
+ """Negative test for specifying nics='auto' before 2.37
+ """
+ self.assertRaises(ValueError,
+ self.cs.servers.create,
+ name='test',
+ image='d9d8d53c-4b4a-4144-a5e5-b30d9f1fe46a',
+ flavor='1',
+ nics='auto')
+
class ServersV26Test(ServersTest):
@@ -1074,7 +1098,8 @@ class ServersV219Test(ServersV217Test):
flavor=1,
meta={'foo': 'bar'},
userdata="hello moto",
- key_name="fakekey"
+ key_name="fakekey",
+ nics=self._get_server_create_default_nics()
)
self.assert_called('POST', '/servers')
@@ -1260,3 +1285,39 @@ class ServersV232Test(ServersV226Test):
image=1, flavor=1, meta={'foo': 'bar'},
userdata="hello moto", key_name="fakekey",
block_device_mapping_v2=bdm)
+
+
+class ServersV2_37Test(ServersV226Test):
+
+ api_version = "2.37"
+
+ def _get_server_create_default_nics(self):
+ return 'auto'
+
+ def test_create_server_no_nics(self):
+ """Tests that nics are required in microversion 2.37+
+ """
+ self.assertRaises(ValueError, self.cs.servers.create,
+ name='test',
+ image='d9d8d53c-4b4a-4144-a5e5-b30d9f1fe46a',
+ flavor='1')
+
+ def test_create_server_with_nics_auto(self):
+ s = self.cs.servers.create(
+ name='test', image='d9d8d53c-4b4a-4144-a5e5-b30d9f1fe46a',
+ flavor='1', nics=self._get_server_create_default_nics())
+ self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST)
+ self.assert_called('POST', '/servers')
+ self.assertIsInstance(s, servers.Server)
+
+ def test_add_floating_ip(self):
+ # self.cs.floating_ips.list() is not available after 2.35
+ pass
+
+ def test_add_floating_ip_to_fixed(self):
+ # self.cs.floating_ips.list() is not available after 2.35
+ pass
+
+ def test_remove_floating_ip(self):
+ # self.cs.floating_ips.list() is not available after 2.35
+ pass
diff --git a/novaclient/tests/unit/v2/test_shell.py b/novaclient/tests/unit/v2/test_shell.py
index db8f1abf..34eec493 100644
--- a/novaclient/tests/unit/v2/test_shell.py
+++ b/novaclient/tests/unit/v2/test_shell.py
@@ -606,6 +606,64 @@ class ShellTest(utils.TestCase):
cmd, api_version='2.32')
self.assertIn('tag=tag', six.text_type(ex))
+ def test_boot_invalid_nics_v2_36_auto(self):
+ """This is a negative test to make sure we fail with the correct
+ message. --nic auto isn't allowed before 2.37.
+ """
+ cmd = ('boot --image %s --flavor 1 --nic auto test' % FAKE_UUID_1)
+ ex = self.assertRaises(exceptions.CommandError, self.run_command,
+ cmd, api_version='2.36')
+ self.assertNotIn('auto,none', six.text_type(ex))
+
+ def test_boot_invalid_nics_v2_37(self):
+ """This is a negative test to make sure we fail with the correct
+ message.
+ """
+ cmd = ('boot --image %s --flavor 1 '
+ '--nic net-id=1 --nic auto some-server' % FAKE_UUID_1)
+ ex = self.assertRaises(exceptions.CommandError, self.run_command,
+ cmd, api_version='2.37')
+ self.assertIn('auto,none', six.text_type(ex))
+
+ def test_boot_nics_auto_allocate_default(self):
+ """Tests that if microversion>=2.37 is specified and no --nics are
+ specified that a single --nic with net-id=auto is used.
+ """
+ cmd = 'boot --image %s --flavor 1 some-server' % FAKE_UUID_1
+ self.run_command(cmd, api_version='2.37')
+ self.assert_called_anytime(
+ 'POST', '/servers',
+ {
+ 'server': {
+ 'flavorRef': '1',
+ 'name': 'some-server',
+ 'imageRef': FAKE_UUID_1,
+ 'min_count': 1,
+ 'max_count': 1,
+ 'networks': 'auto',
+ },
+ },
+ )
+
+ def test_boot_nics_auto_allocate_none(self):
+ """Tests specifying '--nic none' with microversion 2.37
+ """
+ cmd = 'boot --image %s --flavor 1 --nic none some-server' % FAKE_UUID_1
+ self.run_command(cmd, api_version='2.37')
+ self.assert_called_anytime(
+ 'POST', '/servers',
+ {
+ 'server': {
+ 'flavorRef': '1',
+ 'name': 'some-server',
+ 'imageRef': FAKE_UUID_1,
+ 'min_count': 1,
+ 'max_count': 1,
+ 'networks': 'none',
+ },
+ },
+ )
+
def test_boot_nics_ipv6(self):
cmd = ('boot --image %s --flavor 1 '
'--nic net-id=a=c,v6-fixed-ip=2001:db9:0:1::10 some-server' %
@@ -3062,6 +3120,7 @@ class ShellTest(utils.TestCase):
32, # doesn't require separate version-wrapped methods in
# novaclient
34, # doesn't require any changes in novaclient
+ 37, # There are no versioned wrapped shell method changes for this
])
versions_supported = set(range(0,
novaclient.API_MAX_VERSION.ver_minor + 1))
diff --git a/novaclient/v2/servers.py b/novaclient/v2/servers.py
index ebeaaee3..e24fa43a 100644
--- a/novaclient/v2/servers.py
+++ b/novaclient/v2/servers.py
@@ -709,27 +709,32 @@ class ServerManager(base.BootingManagerWithFind):
body['server']['block_device_mapping_v2'] = block_device_mapping_v2
if nics is not None:
- # NOTE(tr3buchet): nics can be an empty list
- all_net_data = []
- for nic_info in nics:
- net_data = {}
- # if value is empty string, do not send value in body
- if nic_info.get('net-id'):
- net_data['uuid'] = nic_info['net-id']
- if (nic_info.get('v4-fixed-ip') and
- nic_info.get('v6-fixed-ip')):
- raise base.exceptions.CommandError(_(
- "Only one of 'v4-fixed-ip' and 'v6-fixed-ip' may be"
- " provided."))
- elif nic_info.get('v4-fixed-ip'):
- net_data['fixed_ip'] = nic_info['v4-fixed-ip']
- elif nic_info.get('v6-fixed-ip'):
- net_data['fixed_ip'] = nic_info['v6-fixed-ip']
- if nic_info.get('port-id'):
- net_data['port'] = nic_info['port-id']
- if nic_info.get('tag'):
- net_data['tag'] = nic_info['tag']
- all_net_data.append(net_data)
+ # With microversion 2.37+ nics can be an enum of 'auto' or 'none'
+ # or a list of dicts.
+ if isinstance(nics, six.string_types):
+ all_net_data = nics
+ else:
+ # NOTE(tr3buchet): nics can be an empty list
+ all_net_data = []
+ for nic_info in nics:
+ net_data = {}
+ # if value is empty string, do not send value in body
+ if nic_info.get('net-id'):
+ net_data['uuid'] = nic_info['net-id']
+ if (nic_info.get('v4-fixed-ip') and
+ nic_info.get('v6-fixed-ip')):
+ raise base.exceptions.CommandError(_(
+ "Only one of 'v4-fixed-ip' and 'v6-fixed-ip' "
+ "may be provided."))
+ elif nic_info.get('v4-fixed-ip'):
+ net_data['fixed_ip'] = nic_info['v4-fixed-ip']
+ elif nic_info.get('v6-fixed-ip'):
+ net_data['fixed_ip'] = nic_info['v6-fixed-ip']
+ if nic_info.get('port-id'):
+ net_data['port'] = nic_info['port-id']
+ if nic_info.get('tag'):
+ net_data['tag'] = nic_info['tag']
+ all_net_data.append(net_data)
body['server']['networks'] = all_net_data
if disk_config is not None:
@@ -1219,6 +1224,14 @@ class ServerManager(base.BootingManagerWithFind):
base.getid(server))
return base.TupleWithMeta((resp, body), resp)
+ def _validate_create_nics(self, nics):
+ # nics are required with microversion 2.37+ and can be a string or list
+ if self.api_version > api_versions.APIVersion('2.36'):
+ if not nics:
+ raise ValueError('nics are required after microversion 2.36')
+ elif nics and not isinstance(nics, list):
+ raise ValueError('nics must be a list')
+
def create(self, name, image, flavor, meta=None, files=None,
reservation_id=None, min_count=None,
max_count=None, security_groups=None, userdata=None,
@@ -1259,9 +1272,17 @@ class ServerManager(base.BootingManagerWithFind):
device mappings for this server.
:param block_device_mapping_v2: (optional extension) A dict of block
device mappings for this server.
- :param nics: (optional extension) an ordered list of nics to be
- added to this server, with information about
- connected networks, fixed IPs, port etc.
+ :param nics: An ordered list of nics (dicts) to be added to this
+ server, with information about connected networks,
+ fixed IPs, port etc.
+ Beginning in microversion 2.37 this field is required and
+ also supports a single string value of 'auto' or 'none'.
+ The 'auto' value means the Compute service will
+ automatically allocate a network for the project if one
+ is not available. This is the same behavior as not
+ passing anything for nics before microversion 2.37. The
+ 'none' value tells the Compute service to not allocate
+ any networking for the server.
:param scheduler_hints: (optional extension) arbitrary key-value pairs
specified by the client to help boot an instance
:param config_drive: (optional extension) value for config drive
@@ -1289,6 +1310,8 @@ class ServerManager(base.BootingManagerWithFind):
if "description" in kwargs and self.api_version < descr_microversion:
raise exceptions.UnsupportedAttribute("description", "2.19")
+ self._validate_create_nics(nics)
+
tags_microversion = api_versions.APIVersion("2.32")
if self.api_version < tags_microversion:
if nics:
diff --git a/novaclient/v2/shell.py b/novaclient/v2/shell.py
index b49656a5..a823cd4a 100644
--- a/novaclient/v2/shell.py
+++ b/novaclient/v2/shell.py
@@ -188,7 +188,16 @@ def _parse_block_device_mapping_v2(args, image):
def _parse_nics(cs, args):
- if cs.api_version >= api_versions.APIVersion('2.32'):
+ supports_auto_alloc = cs.api_version >= api_versions.APIVersion('2.37')
+ if supports_auto_alloc:
+ err_msg = (_("Invalid nic argument '%s'. Nic arguments must be of "
+ "the form --nic <auto,none,net-id=net-uuid,"
+ "net-name=network-name,v4-fixed-ip=ip-addr,"
+ "v6-fixed-ip=ip-addr,port-id=port-uuid,tag=tag>, "
+ "with only one of net-id, net-name or port-id "
+ "specified. Specifying a --nic of auto or none cannot "
+ "be used with any other --nic value."))
+ elif cs.api_version >= api_versions.APIVersion('2.32'):
err_msg = (_("Invalid nic argument '%s'. Nic arguments must be of "
"the form --nic <net-id=net-uuid,"
"net-name=network-name,v4-fixed-ip=ip-addr,"
@@ -202,6 +211,7 @@ def _parse_nics(cs, args):
"v6-fixed-ip=ip-addr,port-id=port-uuid>, "
"with only one of net-id, net-name or port-id "
"specified."))
+ auto_or_none = False
nics = []
for nic_str in args.nics:
nic_info = {"net-id": "", "v4-fixed-ip": "", "v6-fixed-ip": "",
@@ -209,6 +219,13 @@ def _parse_nics(cs, args):
for kv_str in nic_str.split(","):
try:
+ # handle the special auto/none cases
+ if kv_str in ('auto', 'none'):
+ if not supports_auto_alloc:
+ raise exceptions.CommandError(err_msg % nic_str)
+ nics.append(kv_str)
+ auto_or_none = True
+ continue
k, v = kv_str.split("=", 1)
except ValueError:
raise exceptions.CommandError(err_msg % nic_str)
@@ -225,6 +242,9 @@ def _parse_nics(cs, args):
else:
raise exceptions.CommandError(err_msg % nic_str)
+ if auto_or_none:
+ continue
+
if nic_info['v4-fixed-ip'] and not netutils.is_valid_ipv4(
nic_info['v4-fixed-ip']):
raise exceptions.CommandError(_("Invalid ipv4 address."))
@@ -238,6 +258,17 @@ def _parse_nics(cs, args):
nics.append(nic_info)
+ if nics:
+ if auto_or_none:
+ if len(nics) > 1:
+ raise exceptions.CommandError(err_msg % nic_str)
+ # change the single list entry to a string
+ nics = nics[0]
+ else:
+ # Default to 'auto' if API version >= 2.37 and nothing was specified
+ if supports_auto_alloc:
+ nics = 'auto'
+
return nics
@@ -577,6 +608,7 @@ def _boot(cs, args):
dest='nics',
default=[],
start_version='2.32',
+ end_version='2.36',
help=_("Create a NIC on the server. "
"Specify option multiple times to create multiple nics. "
"net-id: attach NIC to network with this UUID "
@@ -588,6 +620,31 @@ def _boot(cs, args):
"tag: interface metadata tag (optional) "
"(either port-id or net-id must be provided)."))
@utils.arg(
+ '--nic',
+ metavar="<auto,none,"
+ "net-id=net-uuid,net-name=network-name,port-id=port-uuid,"
+ "v4-fixed-ip=ip-addr,v6-fixed-ip=ip-addr,tag=tag>",
+ action='append',
+ dest='nics',
+ default=[],
+ start_version='2.37',
+ help=_("Create a NIC on the server. "
+ "Specify option multiple times to create multiple nics unless "
+ "using the special 'auto' or 'none' values. "
+ "auto: automatically allocate network resources if none are "
+ "available. This cannot be specified with any other nic value and "
+ "cannot be specified multiple times. "
+ "none: do not attach a NIC at all. This cannot be specified "
+ "with any other nic value and cannot be specified multiple times. "
+ "net-id: attach NIC to network with a specific UUID. "
+ "net-name: attach NIC to network with this name "
+ "(either port-id or net-id or net-name must be provided), "
+ "v4-fixed-ip: IPv4 fixed address for NIC (optional), "
+ "v6-fixed-ip: IPv6 fixed address for NIC (optional), "
+ "port-id: attach NIC to port with this UUID "
+ "tag: interface metadata tag (optional) "
+ "(either port-id or net-id must be provided)."))
+@utils.arg(
'--config-drive',
metavar="<value>",
dest='config_drive',
diff --git a/releasenotes/notes/microversion-2.37-d03da96406a45e67.yaml b/releasenotes/notes/microversion-2.37-d03da96406a45e67.yaml
new file mode 100644
index 00000000..211a4f9c
--- /dev/null
+++ b/releasenotes/notes/microversion-2.37-d03da96406a45e67.yaml
@@ -0,0 +1,27 @@
+---
+features:
+ - |
+ The 2.37 microversion is now supported. This introduces the following
+ changes:
+
+ * CLI: The **--nic** value for the **nova boot** command now takes two
+ special values, 'auto' and 'none'. If --nic is not specified, the
+ CLI defaults to 'auto'.
+ * Python API: The **nics** kwarg is required when creating a server using
+ the *novaclient.v2.servers.ServerManager.create* API. The **nics**
+ value can be a list of dicts or a string with value 'auto' or
+ 'none'.
+
+upgrade:
+ - |
+ With the 2.37 microversion, the **nics** kwarg is required when creating
+ a server using the *novaclient.v2.servers.ServerManager.create* API. The
+ **nics** value can be a list of dicts or an enum string with one of the
+ following values:
+
+ * **auto**: This tells the Compute service to automatically allocate a
+ network for the project if one is not available and then associate
+ an IP from that network with the server. This is the same behavior as
+ passing nics=None before the 2.37 microversion.
+ * **none**: This tells the Compute service to not allocate any networking
+ for the server.