Skip to content

Commit 7c1d6f7

Browse files
committed
compute: Add functional tests for --block-device
This mostly reuses the existing tests for '--block-device-mapping', which can hopefully be removed at some point in the future. This highlights two issues with the implementation of this option. Firstly, the 'boot_index' parameter is not required so don't mandate it. Secondly, and more significantly, we were defaulting the destination type for the 'image' source type to 'local'. Nova only allows you to attach a single image to local mapping [1], which means this default would only make sense if you were expecting users to use the '--block-device' option exclusively and omit the '--image' option. This is the *less common* case so this is a bad default. Default instead to a destination type of 'volume' like everything else, and require users specifying '--block-device' alone to pass 'destination_type=local' explicitly. [1] https://github.com/openstack/nova/blob/c8a6f8d2e/nova/block_device.py#L193-L206 Change-Id: I1718be965f57c3bbdb8a14f3cfac967dd4c55b4d Signed-off-by: Stephen Finucane <sfinucan@redhat.com>
1 parent a507fb5 commit 7c1d6f7

3 files changed

Lines changed: 169 additions & 53 deletions

File tree

openstackclient/compute/v2/server.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -832,13 +832,12 @@ def get_parser(self, prog_name):
832832
action=parseractions.MultiKeyValueAction,
833833
dest='block_devices',
834834
default=[],
835-
required_keys=[
836-
'boot_index',
837-
],
835+
required_keys=[],
838836
optional_keys=[
839837
'uuid', 'source_type', 'destination_type',
840-
'disk_bus', 'device_type', 'device_name', 'guest_format',
841-
'volume_size', 'volume_type', 'delete_on_termination', 'tag',
838+
'disk_bus', 'device_type', 'device_name', 'volume_size',
839+
'guest_format', 'boot_index', 'delete_on_termination', 'tag',
840+
'volume_type',
842841
],
843842
help=_(
844843
'Create a block device on the server.\n'
@@ -1336,14 +1335,15 @@ def _match_image(image_api, wanted_properties):
13361335
block_device_mapping_v2.append(mapping)
13371336

13381337
for mapping in parsed_args.block_devices:
1339-
try:
1340-
mapping['boot_index'] = int(mapping['boot_index'])
1341-
except ValueError:
1342-
msg = _(
1343-
'The boot_index key of --block-device should be an '
1344-
'integer'
1345-
)
1346-
raise exceptions.CommandError(msg)
1338+
if 'boot_index' in mapping:
1339+
try:
1340+
mapping['boot_index'] = int(mapping['boot_index'])
1341+
except ValueError:
1342+
msg = _(
1343+
'The boot_index key of --block-device should be an '
1344+
'integer'
1345+
)
1346+
raise exceptions.CommandError(msg)
13471347

13481348
if 'tag' in mapping and (
13491349
compute_client.api_version < api_versions.APIVersion('2.42')
@@ -1383,9 +1383,9 @@ def _match_image(image_api, wanted_properties):
13831383
)
13841384
raise exceptions.CommandError(msg)
13851385
else:
1386-
if mapping['source_type'] in ('image', 'blank'):
1386+
if mapping['source_type'] in ('blank',):
13871387
mapping['destination_type'] = 'local'
1388-
else: # volume, snapshot
1388+
else: # volume, image, snapshot
13891389
mapping['destination_type'] = 'volume'
13901390

13911391
if 'delete_on_termination' in mapping:

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

Lines changed: 148 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -574,7 +574,90 @@ def test_server_boot_from_volume(self):
574574
cmd_output['status'],
575575
)
576576

577-
def test_server_boot_with_bdm_snapshot(self):
577+
def _test_server_boot_with_bdm_volume(self, use_legacy):
578+
"""Test server create from volume, server delete"""
579+
# get volume status wait function
580+
volume_wait_for = volume_common.BaseVolumeTests.wait_for_status
581+
582+
# create source empty volume
583+
volume_name = uuid.uuid4().hex
584+
cmd_output = json.loads(self.openstack(
585+
'volume create -f json ' +
586+
'--size 1 ' +
587+
volume_name
588+
))
589+
volume_id = cmd_output["id"]
590+
self.assertIsNotNone(volume_id)
591+
self.addCleanup(self.openstack, 'volume delete ' + volume_name)
592+
self.assertEqual(volume_name, cmd_output['name'])
593+
volume_wait_for("volume", volume_name, "available")
594+
595+
if use_legacy:
596+
bdm_arg = f'--block-device-mapping vdb={volume_name}'
597+
else:
598+
bdm_arg = (
599+
f'--block-device '
600+
f'device_name=vdb,source_type=volume,boot_index=1,'
601+
f'uuid={volume_id}'
602+
)
603+
604+
# create server
605+
server_name = uuid.uuid4().hex
606+
server = json.loads(self.openstack(
607+
'server create -f json ' +
608+
'--flavor ' + self.flavor_name + ' ' +
609+
'--image ' + self.image_name + ' ' +
610+
bdm_arg + ' ' +
611+
self.network_arg + ' ' +
612+
'--wait ' +
613+
server_name
614+
))
615+
self.assertIsNotNone(server["id"])
616+
self.addCleanup(self.openstack, 'server delete --wait ' + server_name)
617+
self.assertEqual(
618+
server_name,
619+
server['name'],
620+
)
621+
622+
# check server volumes_attached, format is
623+
# {"volumes_attached": "id='2518bc76-bf0b-476e-ad6b-571973745bb5'",}
624+
cmd_output = json.loads(self.openstack(
625+
'server show -f json ' +
626+
server_name
627+
))
628+
volumes_attached = cmd_output['volumes_attached']
629+
self.assertIsNotNone(volumes_attached)
630+
631+
# check volumes
632+
cmd_output = json.loads(self.openstack(
633+
'volume show -f json ' +
634+
volume_name
635+
))
636+
attachments = cmd_output['attachments']
637+
self.assertEqual(
638+
1,
639+
len(attachments),
640+
)
641+
self.assertEqual(
642+
server['id'],
643+
attachments[0]['server_id'],
644+
)
645+
self.assertEqual(
646+
"in-use",
647+
cmd_output['status'],
648+
)
649+
650+
def test_server_boot_with_bdm_volume(self):
651+
"""Test server create from image with bdm volume, server delete"""
652+
self._test_server_boot_with_bdm_volume(use_legacy=False)
653+
654+
# TODO(stephenfin): Remove when we drop support for the
655+
# '--block-device-mapping' option
656+
def test_server_boot_with_bdm_volume_legacy(self):
657+
"""Test server create from image with bdm volume, server delete"""
658+
self._test_server_boot_with_bdm_volume(use_legacy=True)
659+
660+
def _test_server_boot_with_bdm_snapshot(self, use_legacy):
578661
"""Test server create from image with bdm snapshot, server delete"""
579662
# get volume status wait function
580663
volume_wait_for = volume_common.BaseVolumeTests.wait_for_status
@@ -588,12 +671,8 @@ def test_server_boot_with_bdm_snapshot(self):
588671
empty_volume_name
589672
))
590673
self.assertIsNotNone(cmd_output["id"])
591-
self.addCleanup(self.openstack,
592-
'volume delete ' + empty_volume_name)
593-
self.assertEqual(
594-
empty_volume_name,
595-
cmd_output['name'],
596-
)
674+
self.addCleanup(self.openstack, 'volume delete ' + empty_volume_name)
675+
self.assertEqual(empty_volume_name, cmd_output['name'])
597676
volume_wait_for("volume", empty_volume_name, "available")
598677

599678
# create snapshot of source empty volume
@@ -603,7 +682,8 @@ def test_server_boot_with_bdm_snapshot(self):
603682
'--volume ' + empty_volume_name + ' ' +
604683
empty_snapshot_name
605684
))
606-
self.assertIsNotNone(cmd_output["id"])
685+
empty_snapshot_id = cmd_output["id"]
686+
self.assertIsNotNone(empty_snapshot_id)
607687
# Deleting volume snapshot take time, so we need to wait until the
608688
# snapshot goes. Entries registered by self.addCleanup will be called
609689
# in the reverse order, so we need to register wait_for_delete first.
@@ -617,14 +697,26 @@ def test_server_boot_with_bdm_snapshot(self):
617697
)
618698
volume_wait_for("volume snapshot", empty_snapshot_name, "available")
619699

700+
if use_legacy:
701+
bdm_arg = (
702+
f'--block-device-mapping '
703+
f'vdb={empty_snapshot_name}:snapshot:1:true'
704+
)
705+
else:
706+
bdm_arg = (
707+
f'--block-device '
708+
f'device_name=vdb,uuid={empty_snapshot_id},'
709+
f'source_type=snapshot,volume_size=1,'
710+
f'delete_on_termination=true,boot_index=1'
711+
)
712+
620713
# create server with bdm snapshot
621714
server_name = uuid.uuid4().hex
622715
server = json.loads(self.openstack(
623716
'server create -f json ' +
624717
'--flavor ' + self.flavor_name + ' ' +
625718
'--image ' + self.image_name + ' ' +
626-
'--block-device-mapping '
627-
'vdb=' + empty_snapshot_name + ':snapshot:1:true ' +
719+
bdm_arg + ' ' +
628720
self.network_arg + ' ' +
629721
'--wait ' +
630722
server_name
@@ -681,14 +773,50 @@ def test_server_boot_with_bdm_snapshot(self):
681773
# the attached volume had been deleted
682774
pass
683775

684-
def test_server_boot_with_bdm_image(self):
776+
def test_server_boot_with_bdm_snapshot(self):
777+
"""Test server create from image with bdm snapshot, server delete"""
778+
self._test_server_boot_with_bdm_snapshot(use_legacy=False)
779+
780+
# TODO(stephenfin): Remove when we drop support for the
781+
# '--block-device-mapping' option
782+
def test_server_boot_with_bdm_snapshot_legacy(self):
783+
"""Test server create from image with bdm snapshot, server delete"""
784+
self._test_server_boot_with_bdm_snapshot(use_legacy=True)
785+
786+
def _test_server_boot_with_bdm_image(self, use_legacy):
685787
# Tests creating a server where the root disk is backed by the given
686788
# --image but a --block-device-mapping with type=image is provided so
687789
# that the compute service creates a volume from that image and
688790
# attaches it as a non-root volume on the server. The block device is
689791
# marked as delete_on_termination=True so it will be automatically
690792
# deleted when the server is deleted.
691793

794+
if use_legacy:
795+
# This means create a 1GB volume from the specified image, attach
796+
# it to the server at /dev/vdb and delete the volume when the
797+
# server is deleted.
798+
bdm_arg = (
799+
f'--block-device-mapping '
800+
f'vdb={self.image_name}:image:1:true '
801+
)
802+
else:
803+
# get image ID
804+
cmd_output = json.loads(self.openstack(
805+
'image show -f json ' +
806+
self.image_name
807+
))
808+
image_id = cmd_output['id']
809+
810+
# This means create a 1GB volume from the specified image, attach
811+
# it to the server at /dev/vdb and delete the volume when the
812+
# server is deleted.
813+
bdm_arg = (
814+
f'--block-device '
815+
f'device_name=vdb,uuid={image_id},'
816+
f'source_type=image,volume_size=1,'
817+
f'delete_on_termination=true,boot_index=1'
818+
)
819+
692820
# create server with bdm type=image
693821
# NOTE(mriedem): This test is a bit unrealistic in that specifying the
694822
# same image in the block device as the --image option does not really
@@ -700,11 +828,7 @@ def test_server_boot_with_bdm_image(self):
700828
'server create -f json ' +
701829
'--flavor ' + self.flavor_name + ' ' +
702830
'--image ' + self.image_name + ' ' +
703-
'--block-device-mapping '
704-
# This means create a 1GB volume from the specified image, attach
705-
# it to the server at /dev/vdb and delete the volume when the
706-
# server is deleted.
707-
'vdb=' + self.image_name + ':image:1:true ' +
831+
bdm_arg + ' ' +
708832
self.network_arg + ' ' +
709833
'--wait ' +
710834
server_name
@@ -768,6 +892,14 @@ def test_server_boot_with_bdm_image(self):
768892
# the attached volume had been deleted
769893
pass
770894

895+
def test_server_boot_with_bdm_image(self):
896+
self._test_server_boot_with_bdm_image(use_legacy=False)
897+
898+
# TODO(stephenfin): Remove when we drop support for the
899+
# '--block-device-mapping' option
900+
def test_server_boot_with_bdm_image_legacy(self):
901+
self._test_server_boot_with_bdm_image(use_legacy=True)
902+
771903
def test_boot_from_volume(self):
772904
# Tests creating a server using --image and --boot-from-volume where
773905
# the compute service will create a root volume of the specified size

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

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2029,7 +2029,7 @@ def test_server_create_with_snapshot(self):
20292029
self.assertEqual(self.datalist(), data)
20302030

20312031
def test_server_create_with_block_device(self):
2032-
block_device = f'uuid={self.volume.id},source_type=volume,boot_index=1'
2032+
block_device = f'uuid={self.volume.id},source_type=volume'
20332033
arglist = [
20342034
'--image', 'image1',
20352035
'--flavor', self.flavor.id,
@@ -2043,7 +2043,6 @@ def test_server_create_with_block_device(self):
20432043
{
20442044
'uuid': self.volume.id,
20452045
'source_type': 'volume',
2046-
'boot_index': '1',
20472046
},
20482047
]),
20492048
('server_name', self.new_server.name),
@@ -2069,7 +2068,6 @@ def test_server_create_with_block_device(self):
20692068
'uuid': self.volume.id,
20702069
'source_type': 'volume',
20712070
'destination_type': 'volume',
2072-
'boot_index': 1,
20732071
}],
20742072
'nics': [],
20752073
'scheduler_hints': {},
@@ -2171,20 +2169,6 @@ def test_server_create_with_block_device_full(self):
21712169
self.assertEqual(self.columns, columns)
21722170
self.assertEqual(self.datalist(), data)
21732171

2174-
def test_server_create_with_block_device_no_boot_index(self):
2175-
block_device = \
2176-
f'uuid={self.volume.name},source_type=volume'
2177-
arglist = [
2178-
'--image', 'image1',
2179-
'--flavor', self.flavor.id,
2180-
'--block-device', block_device,
2181-
self.new_server.name,
2182-
]
2183-
self.assertRaises(
2184-
argparse.ArgumentTypeError,
2185-
self.check_parser,
2186-
self.cmd, arglist, [])
2187-
21882172
def test_server_create_with_block_device_invalid_boot_index(self):
21892173
block_device = \
21902174
f'uuid={self.volume.name},source_type=volume,boot_index=foo'
@@ -2201,7 +2185,7 @@ def test_server_create_with_block_device_invalid_boot_index(self):
22012185
self.assertIn('The boot_index key of --block-device ', str(ex))
22022186

22032187
def test_server_create_with_block_device_invalid_source_type(self):
2204-
block_device = f'uuid={self.volume.name},source_type=foo,boot_index=1'
2188+
block_device = f'uuid={self.volume.name},source_type=foo'
22052189
arglist = [
22062190
'--image', 'image1',
22072191
'--flavor', self.flavor.id,
@@ -2216,7 +2200,7 @@ def test_server_create_with_block_device_invalid_source_type(self):
22162200

22172201
def test_server_create_with_block_device_invalid_destination_type(self):
22182202
block_device = \
2219-
f'uuid={self.volume.name},destination_type=foo,boot_index=1'
2203+
f'uuid={self.volume.name},destination_type=foo'
22202204
arglist = [
22212205
'--image', 'image1',
22222206
'--flavor', self.flavor.id,
@@ -2231,7 +2215,7 @@ def test_server_create_with_block_device_invalid_destination_type(self):
22312215

22322216
def test_server_create_with_block_device_invalid_shutdown(self):
22332217
block_device = \
2234-
f'uuid={self.volume.name},delete_on_termination=foo,boot_index=1'
2218+
f'uuid={self.volume.name},delete_on_termination=foo'
22352219
arglist = [
22362220
'--image', 'image1',
22372221
'--flavor', self.flavor.id,
@@ -2250,7 +2234,7 @@ def test_server_create_with_block_device_tag_pre_v242(self):
22502234
'2.41')
22512235

22522236
block_device = \
2253-
f'uuid={self.volume.name},tag=foo,boot_index=1'
2237+
f'uuid={self.volume.name},tag=foo'
22542238
arglist = [
22552239
'--image', 'image1',
22562240
'--flavor', self.flavor.id,
@@ -2269,7 +2253,7 @@ def test_server_create_with_block_device_volume_type_pre_v267(self):
22692253
self.app.client_manager.compute.api_version = api_versions.APIVersion(
22702254
'2.66')
22712255

2272-
block_device = f'uuid={self.volume.name},volume_type=foo,boot_index=1'
2256+
block_device = f'uuid={self.volume.name},volume_type=foo'
22732257
arglist = [
22742258
'--image', 'image1',
22752259
'--flavor', self.flavor.id,

0 commit comments

Comments
 (0)