Skip to content

Commit 07fcb73

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Support type=image with --block-device-mapping option"
2 parents b0ec909 + 6a199bd commit 07fcb73

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)