Skip to content

Commit 6a199bd

Browse files
mriedemDean Troyer
authored andcommitted
Support type=image with --block-device-mapping option
The --block-device-mapping option on the server create command currently only supports booting from volume and volume snapshot. A common boot-from-volume scenario is providing an image and letting nova orchestrate the creation of the image-backed volume and attaching it to the server. This adds support for type=image in the --block-device-mapping option. The volume size is required in this case. Note that the CLI currently says if type=snapshot that size is also required but that's technically not true. When booting from a volume snapshot, the compute API will use the size of the volume snapshot to create the volume if an explicit size is not provided. For the purposes of this patch, we need the size anyway for the image being the block device mapping source type. Change-Id: I57b3c261d8309f7b9f62a3e91612bce592a887a3 Story: 2006302 Task: 36016
1 parent c474319 commit 6a199bd

3 files changed

Lines changed: 119 additions & 6 deletions

File tree

openstackclient/compute/v2/server.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -575,14 +575,17 @@ def get_parser(self, prog_name):
575575
# NOTE(RuiChen): Add '\n' at the end of line to put each item in
576576
# the separated line, avoid the help message looks
577577
# messy, see _SmartHelpFormatter in cliff.
578+
# FIXME(mriedem): Technically <id> can be the name or ID.
578579
help=_('Create a block device on the server.\n'
579580
'Block device mapping in the format\n'
580581
'<dev-name>=<id>:<type>:<size(GB)>:<delete-on-terminate>\n'
581582
'<dev-name>: block device name, like: vdb, xvdc '
582583
'(required)\n'
583-
'<id>: UUID of the volume or snapshot (required)\n'
584-
'<type>: volume or snapshot; default: volume (optional)\n'
585-
'<size(GB)>: volume size if create from snapshot '
584+
'<id>: UUID of the volume, volume snapshot or image '
585+
'(required)\n'
586+
'<type>: volume, snapshot or image; default: volume '
587+
'(optional)\n'
588+
'<size(GB)>: volume size if create from image or snapshot '
586589
'(optional)\n'
587590
'<delete-on-terminate>: true or false; default: false '
588591
'(optional)\n'
@@ -793,7 +796,7 @@ def _match_image(image_api, wanted_properties):
793796
mapping = {'device_name': dev_name}
794797
# 1. decide source and destination type
795798
if (len(dev_map) > 1 and
796-
dev_map[1] in ('volume', 'snapshot')):
799+
dev_map[1] in ('volume', 'snapshot', 'image')):
797800
mapping['source_type'] = dev_map[1]
798801
else:
799802
mapping['source_type'] = 'volume'
@@ -808,14 +811,29 @@ def _match_image(image_api, wanted_properties):
808811
snapshot_id = utils.find_resource(
809812
volume_client.volume_snapshots, dev_map[0]).id
810813
mapping['uuid'] = snapshot_id
814+
elif mapping['source_type'] == 'image':
815+
# NOTE(mriedem): In case --image is specified with the same
816+
# image, that becomes the root disk for the server. If the
817+
# block device is specified with a root device name, e.g.
818+
# vda, then the compute API will likely fail complaining
819+
# that there is a conflict. So if using the same image ID,
820+
# which doesn't really make sense but it's allowed, the
821+
# device name would need to be a non-root device, e.g. vdb.
822+
# Otherwise if the block device image is different from the
823+
# one specified by --image, then the compute service will
824+
# create a volume from the image and attach it to the
825+
# server as a non-root volume.
826+
image_id = utils.find_resource(
827+
image_client.images, dev_map[0]).id
828+
mapping['uuid'] = image_id
811829
# 3. append size and delete_on_termination if exist
812830
if len(dev_map) > 2 and dev_map[2]:
813831
mapping['volume_size'] = dev_map[2]
814832
if len(dev_map) > 3 and dev_map[3]:
815833
mapping['delete_on_termination'] = dev_map[3]
816834
else:
817-
msg = _("Volume or snapshot (name or ID) must be specified if "
818-
"--block-device-mapping is specified")
835+
msg = _("Volume, volume snapshot or image (name or ID) must "
836+
"be specified if --block-device-mapping is specified")
819837
raise exceptions.CommandError(msg)
820838
block_device_mapping_v2.append(mapping)
821839

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

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,93 @@ def test_server_boot_with_bdm_snapshot(self):
614614
# the attached volume had been deleted
615615
pass
616616

617+
def test_server_boot_with_bdm_image(self):
618+
# Tests creating a server where the root disk is backed by the given
619+
# --image but a --block-device-mapping with type=image is provided so
620+
# that the compute service creates a volume from that image and
621+
# attaches it as a non-root volume on the server. The block device is
622+
# marked as delete_on_termination=True so it will be automatically
623+
# deleted when the server is deleted.
624+
625+
# create server with bdm type=image
626+
# NOTE(mriedem): This test is a bit unrealistic in that specifying the
627+
# same image in the block device as the --image option does not really
628+
# make sense, but we just want to make sure everything is processed
629+
# as expected where nova creates a volume from the image and attaches
630+
# that volume to the server.
631+
server_name = uuid.uuid4().hex
632+
server = json.loads(self.openstack(
633+
'server create -f json ' +
634+
'--flavor ' + self.flavor_name + ' ' +
635+
'--image ' + self.image_name + ' ' +
636+
'--block-device-mapping '
637+
# This means create a 1GB volume from the specified image, attach
638+
# it to the server at /dev/vdb and delete the volume when the
639+
# server is deleted.
640+
'vdb=' + self.image_name + ':image:1:true ' +
641+
self.network_arg + ' ' +
642+
'--wait ' +
643+
server_name
644+
))
645+
self.assertIsNotNone(server["id"])
646+
self.assertEqual(
647+
server_name,
648+
server['name'],
649+
)
650+
self.wait_for_status(server_name, 'ACTIVE')
651+
652+
# check server volumes_attached, format is
653+
# {"volumes_attached": "id='2518bc76-bf0b-476e-ad6b-571973745bb5'",}
654+
cmd_output = json.loads(self.openstack(
655+
'server show -f json ' +
656+
server_name
657+
))
658+
volumes_attached = cmd_output['volumes_attached']
659+
self.assertTrue(volumes_attached.startswith('id='))
660+
attached_volume_id = volumes_attached.replace('id=', '')
661+
662+
# check the volume that attached on server
663+
cmd_output = json.loads(self.openstack(
664+
'volume show -f json ' +
665+
attached_volume_id
666+
))
667+
attachments = cmd_output['attachments']
668+
self.assertEqual(
669+
1,
670+
len(attachments),
671+
)
672+
self.assertEqual(
673+
server['id'],
674+
attachments[0]['server_id'],
675+
)
676+
self.assertEqual(
677+
"in-use",
678+
cmd_output['status'],
679+
)
680+
# TODO(mriedem): If we can parse the volume_image_metadata field from
681+
# the volume show output we could assert the image_name is what we
682+
# specified. volume_image_metadata is something like this:
683+
# {u'container_format': u'bare', u'min_ram': u'0',
684+
# u'disk_format': u'qcow2', u'image_name': u'cirros-0.4.0-x86_64-disk',
685+
# u'image_id': u'05496c83-e2df-4c2f-9e48-453b6e49160d',
686+
# u'checksum': u'443b7623e27ecf03dc9e01ee93f67afe', u'min_disk': u'0',
687+
# u'size': u'12716032'}
688+
689+
# delete server, then check the attached volume has been deleted
690+
self.openstack('server delete --wait ' + server_name)
691+
cmd_output = json.loads(self.openstack(
692+
'volume list -f json'
693+
))
694+
target_volume = [each_volume
695+
for each_volume in cmd_output
696+
if each_volume['ID'] == attached_volume_id]
697+
if target_volume:
698+
# check the attached volume is 'deleting' status
699+
self.assertEqual('deleting', target_volume[0]['Status'])
700+
else:
701+
# the attached volume had been deleted
702+
pass
703+
617704
def test_server_create_with_none_network(self):
618705
"""Test server create with none network option."""
619706
server_name = uuid.uuid4().hex
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
features:
3+
- |
4+
The ``server create --block-device-mapping`` option now supports
5+
an ``image`` type in addition to ``volume`` and ``snapshot``. When
6+
specifying an image block device the compute service will create a volume
7+
from the image of the specified size and attach it to the server.
8+
[Story `2006302 <https://storyboard.openstack.org/#!/story/2006302>`_]

0 commit comments

Comments
 (0)