From 6a199bd14152889f18ad95919a4bee9c0c083d5d Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Mon, 29 Jul 2019 12:59:52 -0400 Subject: [PATCH] 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 --- openstackclient/compute/v2/server.py | 30 +++++-- .../functional/compute/v2/test_server.py | 87 +++++++++++++++++++ ...pport-bdm-type-image-0becfb63bd4fb969.yaml | 8 ++ 3 files changed, 119 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/story-2006302-support-bdm-type-image-0becfb63bd4fb969.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index ea87d9cea1..a0afa389f6 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -575,14 +575,17 @@ class CreateServer(command.ShowOne): # NOTE(RuiChen): Add '\n' at the end of line to put each item in # the separated line, avoid the help message looks # messy, see _SmartHelpFormatter in cliff. + # FIXME(mriedem): Technically can be the name or ID. help=_('Create a block device on the server.\n' 'Block device mapping in the format\n' '=:::\n' ': block device name, like: vdb, xvdc ' '(required)\n' - ': UUID of the volume or snapshot (required)\n' - ': volume or snapshot; default: volume (optional)\n' - ': volume size if create from snapshot ' + ': UUID of the volume, volume snapshot or image ' + '(required)\n' + ': volume, snapshot or image; default: volume ' + '(optional)\n' + ': volume size if create from image or snapshot ' '(optional)\n' ': true or false; default: false ' '(optional)\n' @@ -793,7 +796,7 @@ class CreateServer(command.ShowOne): mapping = {'device_name': dev_name} # 1. decide source and destination type if (len(dev_map) > 1 and - dev_map[1] in ('volume', 'snapshot')): + dev_map[1] in ('volume', 'snapshot', 'image')): mapping['source_type'] = dev_map[1] else: mapping['source_type'] = 'volume' @@ -808,14 +811,29 @@ class CreateServer(command.ShowOne): snapshot_id = utils.find_resource( volume_client.volume_snapshots, dev_map[0]).id mapping['uuid'] = snapshot_id + elif mapping['source_type'] == 'image': + # NOTE(mriedem): In case --image is specified with the same + # image, that becomes the root disk for the server. If the + # block device is specified with a root device name, e.g. + # vda, then the compute API will likely fail complaining + # that there is a conflict. So if using the same image ID, + # which doesn't really make sense but it's allowed, the + # device name would need to be a non-root device, e.g. vdb. + # Otherwise if the block device image is different from the + # one specified by --image, then the compute service will + # create a volume from the image and attach it to the + # server as a non-root volume. + image_id = utils.find_resource( + image_client.images, dev_map[0]).id + mapping['uuid'] = image_id # 3. append size and delete_on_termination if exist if len(dev_map) > 2 and dev_map[2]: mapping['volume_size'] = dev_map[2] if len(dev_map) > 3 and dev_map[3]: mapping['delete_on_termination'] = dev_map[3] else: - msg = _("Volume or snapshot (name or ID) must be specified if " - "--block-device-mapping is specified") + msg = _("Volume, volume snapshot or image (name or ID) must " + "be specified if --block-device-mapping is specified") raise exceptions.CommandError(msg) block_device_mapping_v2.append(mapping) diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index e52a42d3a1..67e2a66ef8 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -614,6 +614,93 @@ class ServerTests(common.ComputeTestCase): # the attached volume had been deleted pass + def test_server_boot_with_bdm_image(self): + # Tests creating a server where the root disk is backed by the given + # --image but a --block-device-mapping with type=image is provided so + # that the compute service creates a volume from that image and + # attaches it as a non-root volume on the server. The block device is + # marked as delete_on_termination=True so it will be automatically + # deleted when the server is deleted. + + # create server with bdm type=image + # NOTE(mriedem): This test is a bit unrealistic in that specifying the + # same image in the block device as the --image option does not really + # make sense, but we just want to make sure everything is processed + # as expected where nova creates a volume from the image and attaches + # that volume to the server. + server_name = uuid.uuid4().hex + server = json.loads(self.openstack( + 'server create -f json ' + + '--flavor ' + self.flavor_name + ' ' + + '--image ' + self.image_name + ' ' + + '--block-device-mapping ' + # This means create a 1GB volume from the specified image, attach + # it to the server at /dev/vdb and delete the volume when the + # server is deleted. + 'vdb=' + self.image_name + ':image:1:true ' + + self.network_arg + ' ' + + '--wait ' + + server_name + )) + self.assertIsNotNone(server["id"]) + self.assertEqual( + server_name, + server['name'], + ) + self.wait_for_status(server_name, 'ACTIVE') + + # check server volumes_attached, format is + # {"volumes_attached": "id='2518bc76-bf0b-476e-ad6b-571973745bb5'",} + cmd_output = json.loads(self.openstack( + 'server show -f json ' + + server_name + )) + volumes_attached = cmd_output['volumes_attached'] + self.assertTrue(volumes_attached.startswith('id=')) + attached_volume_id = volumes_attached.replace('id=', '') + + # check the volume that attached on server + cmd_output = json.loads(self.openstack( + 'volume show -f json ' + + attached_volume_id + )) + attachments = cmd_output['attachments'] + self.assertEqual( + 1, + len(attachments), + ) + self.assertEqual( + server['id'], + attachments[0]['server_id'], + ) + self.assertEqual( + "in-use", + cmd_output['status'], + ) + # TODO(mriedem): If we can parse the volume_image_metadata field from + # the volume show output we could assert the image_name is what we + # specified. volume_image_metadata is something like this: + # {u'container_format': u'bare', u'min_ram': u'0', + # u'disk_format': u'qcow2', u'image_name': u'cirros-0.4.0-x86_64-disk', + # u'image_id': u'05496c83-e2df-4c2f-9e48-453b6e49160d', + # u'checksum': u'443b7623e27ecf03dc9e01ee93f67afe', u'min_disk': u'0', + # u'size': u'12716032'} + + # delete server, then check the attached volume has been deleted + self.openstack('server delete --wait ' + server_name) + cmd_output = json.loads(self.openstack( + 'volume list -f json' + )) + target_volume = [each_volume + for each_volume in cmd_output + if each_volume['ID'] == attached_volume_id] + if target_volume: + # check the attached volume is 'deleting' status + self.assertEqual('deleting', target_volume[0]['Status']) + else: + # the attached volume had been deleted + pass + def test_server_create_with_none_network(self): """Test server create with none network option.""" server_name = uuid.uuid4().hex diff --git a/releasenotes/notes/story-2006302-support-bdm-type-image-0becfb63bd4fb969.yaml b/releasenotes/notes/story-2006302-support-bdm-type-image-0becfb63bd4fb969.yaml new file mode 100644 index 0000000000..6e8e8ba0b8 --- /dev/null +++ b/releasenotes/notes/story-2006302-support-bdm-type-image-0becfb63bd4fb969.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + The ``server create --block-device-mapping`` option now supports + an ``image`` type in addition to ``volume`` and ``snapshot``. When + specifying an image block device the compute service will create a volume + from the image of the specified size and attach it to the server. + [Story `2006302 `_]