Skip to content

Commit 83a0db0

Browse files
Jenkinsopenstack-gerrit
authored andcommitted
Merge "Fix "server create" command failed when --nic auto or none"
2 parents bf10900 + c051c5f commit 83a0db0

5 files changed

Lines changed: 232 additions & 13 deletions

File tree

doc/source/command-objects/server.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@ Create a new server
173173
v6-fixed-ip: IPv6 fixed address for NIC (optional).
174174
none: (v2.37+) no network is attached.
175175
auto: (v2.37+) the compute service will automatically allocate a network.
176+
Specifying a --nic of auto or none cannot be used with any other
177+
--nic value.
176178

177179
.. option:: --hint <key=value>
178180

openstackclient/compute/v2/server.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ def get_parser(self, prog_name):
402402
parser.add_argument(
403403
'--nic',
404404
metavar="<net-id=net-uuid,v4-fixed-ip=ip-addr,v6-fixed-ip=ip-addr,"
405-
"port-id=port-uuid>",
405+
"port-id=port-uuid,auto,none>",
406406
action='append',
407407
default=[],
408408
help=_("Create a NIC on the server. "
@@ -414,7 +414,8 @@ def get_parser(self, prog_name):
414414
"v6-fixed-ip: IPv6 fixed address for NIC (optional), "
415415
"none: (v2.37+) no network is attached, "
416416
"auto: (v2.37+) the compute service will automatically "
417-
"allocate a network."),
417+
"allocate a network. Specifying a --nic of auto or none "
418+
"cannot be used with any other --nic value."),
418419
)
419420
parser.add_argument(
420421
'--hint',
@@ -547,14 +548,21 @@ def take_action(self, parsed_args):
547548
block_device_mapping_v2.append(mapping)
548549

549550
nics = []
550-
if parsed_args.nic in ('auto', 'none'):
551-
nics = [parsed_args.nic]
552-
else:
553-
for nic_str in parsed_args.nic:
551+
auto_or_none = False
552+
for nic_str in parsed_args.nic:
553+
# Handle the special auto/none cases
554+
if nic_str in ('auto', 'none'):
555+
auto_or_none = True
556+
nics.append(nic_str)
557+
else:
554558
nic_info = {"net-id": "", "v4-fixed-ip": "",
555559
"v6-fixed-ip": "", "port-id": ""}
556-
nic_info.update(dict(kv_str.split("=", 1)
557-
for kv_str in nic_str.split(",")))
560+
try:
561+
nic_info.update(dict(kv_str.split("=", 1)
562+
for kv_str in nic_str.split(",")))
563+
except ValueError:
564+
msg = _('Invalid --nic argument %s.') % nic_str
565+
raise exceptions.CommandError(msg)
558566
if bool(nic_info["net-id"]) == bool(nic_info["port-id"]):
559567
msg = _("either net-id or port-id should be specified "
560568
"but not both")
@@ -581,6 +589,18 @@ def take_action(self, parsed_args):
581589
raise exceptions.CommandError(msg)
582590
nics.append(nic_info)
583591

592+
if nics:
593+
if auto_or_none:
594+
if len(nics) > 1:
595+
msg = _('Specifying a --nic of auto or none cannot '
596+
'be used with any other --nic value.')
597+
raise exceptions.CommandError(msg)
598+
nics = nics[0]
599+
else:
600+
# Default to empty list if nothing was specified, let nova side to
601+
# decide the default behavior.
602+
nics = []
603+
584604
hints = {}
585605
for hint in parsed_args.hint:
586606
key, _sep, value = hint.partition('=')

openstackclient/tests/functional/compute/v2/test_server.py

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@
1414
import time
1515

1616
from tempest.lib.common.utils import data_utils
17+
from tempest.lib import exceptions
1718

1819
from openstackclient.tests.functional import base
1920
from openstackclient.tests.functional.volume.v2 import test_volume
20-
from tempest.lib import exceptions
2121

2222

2323
class ServerTests(base.TestCase):
@@ -324,7 +324,7 @@ def test_server_reboot(self):
324324
self.assertEqual("", raw_output)
325325
self.wait_for_status("ACTIVE")
326326

327-
def test_server_boot_from_volume(self):
327+
def test_server_create_from_volume(self):
328328
"""Test server create from volume, server delete
329329
330330
Test steps:
@@ -442,14 +442,57 @@ def test_server_boot_from_volume(self):
442442
cmd_output['status'],
443443
)
444444

445-
def wait_for_status(self, expected_status='ACTIVE', wait=900, interval=30):
445+
def test_server_create_with_none_network(self):
446+
"""Test server create with none network option."""
447+
server_name = data_utils.rand_name('TestServer')
448+
server = json.loads(self.openstack(
449+
# auto/none enable in nova micro version (v2.37+)
450+
'--os-compute-api-version 2.latest ' +
451+
'server create -f json ' +
452+
'--flavor ' + self.flavor_name + ' ' +
453+
'--image ' + self.image_name + ' ' +
454+
'--nic none ' +
455+
server_name
456+
))
457+
self.assertIsNotNone(server["id"])
458+
self.addCleanup(self.openstack, 'server delete --wait ' + server_name)
459+
self.assertEqual(server_name, server['name'])
460+
self.wait_for_status(server_name=server_name)
461+
server = json.loads(self.openstack(
462+
'server show -f json ' + server_name
463+
))
464+
self.assertIsNotNone(server['addresses'])
465+
self.assertEqual('', server['addresses'])
466+
467+
def test_server_create_with_empty_network_option_latest(self):
468+
"""Test server create with empty network option in nova 2.latest."""
469+
server_name = data_utils.rand_name('TestServer')
470+
try:
471+
self.openstack(
472+
# auto/none enable in nova micro version (v2.37+)
473+
'--os-compute-api-version 2.latest ' +
474+
'server create -f json ' +
475+
'--flavor ' + self.flavor_name + ' ' +
476+
'--image ' + self.image_name + ' ' +
477+
server_name
478+
)
479+
except exceptions.CommandFailed as e:
480+
self.assertIn('nics are required after microversion 2.36',
481+
e.stderr)
482+
else:
483+
self.fail('CommandFailed should be raised.')
484+
485+
def wait_for_status(self, expected_status='ACTIVE',
486+
wait=900, interval=30, server_name=None):
446487
"""Wait until server reaches expected status."""
447488
# TODO(thowe): Add a server wait command to osc
448489
failures = ['ERROR']
449490
total_sleep = 0
450491
opts = self.get_opts(['status'])
492+
if not server_name:
493+
server_name = self.NAME
451494
while total_sleep < wait:
452-
status = self.openstack('server show ' + self.NAME + opts)
495+
status = self.openstack('server show ' + server_name + opts)
453496
status = status.rstrip()
454497
print('Waiting for {} current status: {}'.format(expected_status,
455498
status))
@@ -459,7 +502,7 @@ def wait_for_status(self, expected_status='ACTIVE', wait=900, interval=30):
459502
time.sleep(interval)
460503
total_sleep += interval
461504

462-
status = self.openstack('server show ' + self.NAME + opts)
505+
status = self.openstack('server show ' + server_name + opts)
463506
status = status.rstrip()
464507
self.assertEqual(status, expected_status)
465508
# give it a little bit more time

openstackclient/tests/unit/compute/v2/test_server.py

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,152 @@ def test_server_create_with_network(self):
467467
self.assertEqual(self.columns, columns)
468468
self.assertEqual(self.datalist(), data)
469469

470+
def test_server_create_with_auto_network(self):
471+
arglist = [
472+
'--image', 'image1',
473+
'--flavor', 'flavor1',
474+
'--nic', 'auto',
475+
self.new_server.name,
476+
]
477+
verifylist = [
478+
('image', 'image1'),
479+
('flavor', 'flavor1'),
480+
('nic', ['auto']),
481+
('config_drive', False),
482+
('server_name', self.new_server.name),
483+
]
484+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
485+
486+
columns, data = self.cmd.take_action(parsed_args)
487+
488+
# Set expected values
489+
kwargs = dict(
490+
meta=None,
491+
files={},
492+
reservation_id=None,
493+
min_count=1,
494+
max_count=1,
495+
security_groups=[],
496+
userdata=None,
497+
key_name=None,
498+
availability_zone=None,
499+
block_device_mapping_v2=[],
500+
nics='auto',
501+
scheduler_hints={},
502+
config_drive=None,
503+
)
504+
# ServerManager.create(name, image, flavor, **kwargs)
505+
self.servers_mock.create.assert_called_with(
506+
self.new_server.name,
507+
self.image,
508+
self.flavor,
509+
**kwargs
510+
)
511+
512+
self.assertEqual(self.columns, columns)
513+
self.assertEqual(self.datalist(), data)
514+
515+
def test_server_create_with_none_network(self):
516+
arglist = [
517+
'--image', 'image1',
518+
'--flavor', 'flavor1',
519+
'--nic', 'none',
520+
self.new_server.name,
521+
]
522+
verifylist = [
523+
('image', 'image1'),
524+
('flavor', 'flavor1'),
525+
('nic', ['none']),
526+
('config_drive', False),
527+
('server_name', self.new_server.name),
528+
]
529+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
530+
531+
columns, data = self.cmd.take_action(parsed_args)
532+
533+
# Set expected values
534+
kwargs = dict(
535+
meta=None,
536+
files={},
537+
reservation_id=None,
538+
min_count=1,
539+
max_count=1,
540+
security_groups=[],
541+
userdata=None,
542+
key_name=None,
543+
availability_zone=None,
544+
block_device_mapping_v2=[],
545+
nics='none',
546+
scheduler_hints={},
547+
config_drive=None,
548+
)
549+
# ServerManager.create(name, image, flavor, **kwargs)
550+
self.servers_mock.create.assert_called_with(
551+
self.new_server.name,
552+
self.image,
553+
self.flavor,
554+
**kwargs
555+
)
556+
557+
self.assertEqual(self.columns, columns)
558+
self.assertEqual(self.datalist(), data)
559+
560+
def test_server_create_with_conflict_network_options(self):
561+
arglist = [
562+
'--image', 'image1',
563+
'--flavor', 'flavor1',
564+
'--nic', 'none',
565+
'--nic', 'auto',
566+
'--nic', 'port-id=port1',
567+
self.new_server.name,
568+
]
569+
verifylist = [
570+
('image', 'image1'),
571+
('flavor', 'flavor1'),
572+
('nic', ['none', 'auto', 'port-id=port1']),
573+
('config_drive', False),
574+
('server_name', self.new_server.name),
575+
]
576+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
577+
578+
get_endpoints = mock.Mock()
579+
get_endpoints.return_value = {'network': []}
580+
self.app.client_manager.auth_ref = mock.Mock()
581+
self.app.client_manager.auth_ref.service_catalog = mock.Mock()
582+
self.app.client_manager.auth_ref.service_catalog.get_endpoints = (
583+
get_endpoints)
584+
585+
find_port = mock.Mock()
586+
network_client = self.app.client_manager.network
587+
network_client.find_port = find_port
588+
port_resource = mock.Mock()
589+
port_resource.id = 'port1_uuid'
590+
find_port.return_value = port_resource
591+
592+
self.assertRaises(exceptions.CommandError,
593+
self.cmd.take_action, parsed_args)
594+
self.assertNotCalled(self.servers_mock.create)
595+
596+
def test_server_create_with_invalid_network_options(self):
597+
arglist = [
598+
'--image', 'image1',
599+
'--flavor', 'flavor1',
600+
'--nic', 'abcdefgh',
601+
self.new_server.name,
602+
]
603+
verifylist = [
604+
('image', 'image1'),
605+
('flavor', 'flavor1'),
606+
('nic', ['abcdefgh']),
607+
('config_drive', False),
608+
('server_name', self.new_server.name),
609+
]
610+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
611+
612+
self.assertRaises(exceptions.CommandError,
613+
self.cmd.take_action, parsed_args)
614+
self.assertNotCalled(self.servers_mock.create)
615+
470616
@mock.patch.object(common_utils, 'wait_for_status', return_value=True)
471617
def test_server_create_with_wait_ok(self, mock_wait_for_status):
472618
arglist = [
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
fixes:
3+
- |
4+
Fix ``server create`` command failed when ``--nic`` auto or none.
5+
``auto`` and ``none`` options was added into --nic argument of server
6+
create command, but that don't work and raise internal error when execute
7+
command. The patch fix that issue.
8+
[Bug `1663520 <https://bugs.launchpad.net/python-openstackclient/+bug/1663520>`_]

0 commit comments

Comments
 (0)