Skip to content

Commit c051c5f

Browse files
kiwikKevin_Zheng
andcommitted
Fix "server create" command failed when --nic auto or none
"auto" and "none" options was added into --nic argument of server create command in patch https://review.openstack.org/#/c/412698/ , but that don't work and raise internal error when execute command. The patch fix that issue and add unit and functional tests. Change-Id: Ia718c3bac0a5172a0cdbe9f0d97972a9346c1172 Co-Authored-By: Kevin_Zheng <zhengzhenyu@huawei.com> Closes-Bug: #1663520
1 parent 3b562ff commit c051c5f

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):
@@ -319,7 +319,7 @@ def test_server_reboot(self):
319319
self.assertEqual("", raw_output)
320320
self.wait_for_status("ACTIVE")
321321

322-
def test_server_boot_from_volume(self):
322+
def test_server_create_from_volume(self):
323323
"""Test server create from volume, server delete
324324
325325
Test steps:
@@ -437,14 +437,57 @@ def test_server_boot_from_volume(self):
437437
cmd_output['status'],
438438
)
439439

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

457-
status = self.openstack('server show ' + self.NAME + opts)
500+
status = self.openstack('server show ' + server_name + opts)
458501
status = status.rstrip()
459502
self.assertEqual(status, expected_status)
460503
# 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)