Merge "Support type=image with --block-device-mapping option"
This commit is contained in:
commit
07fcb733fc
openstackclient
releasenotes/notes
@ -575,14 +575,17 @@ class CreateServer(command.ShowOne):
|
|||||||
# NOTE(RuiChen): Add '\n' at the end of line to put each item in
|
# NOTE(RuiChen): Add '\n' at the end of line to put each item in
|
||||||
# the separated line, avoid the help message looks
|
# the separated line, avoid the help message looks
|
||||||
# messy, see _SmartHelpFormatter in cliff.
|
# messy, see _SmartHelpFormatter in cliff.
|
||||||
|
# FIXME(mriedem): Technically <id> can be the name or ID.
|
||||||
help=_('Create a block device on the server.\n'
|
help=_('Create a block device on the server.\n'
|
||||||
'Block device mapping in the format\n'
|
'Block device mapping in the format\n'
|
||||||
'<dev-name>=<id>:<type>:<size(GB)>:<delete-on-terminate>\n'
|
'<dev-name>=<id>:<type>:<size(GB)>:<delete-on-terminate>\n'
|
||||||
'<dev-name>: block device name, like: vdb, xvdc '
|
'<dev-name>: block device name, like: vdb, xvdc '
|
||||||
'(required)\n'
|
'(required)\n'
|
||||||
'<id>: UUID of the volume or snapshot (required)\n'
|
'<id>: UUID of the volume, volume snapshot or image '
|
||||||
'<type>: volume or snapshot; default: volume (optional)\n'
|
'(required)\n'
|
||||||
'<size(GB)>: volume size if create from snapshot '
|
'<type>: volume, snapshot or image; default: volume '
|
||||||
|
'(optional)\n'
|
||||||
|
'<size(GB)>: volume size if create from image or snapshot '
|
||||||
'(optional)\n'
|
'(optional)\n'
|
||||||
'<delete-on-terminate>: true or false; default: false '
|
'<delete-on-terminate>: true or false; default: false '
|
||||||
'(optional)\n'
|
'(optional)\n'
|
||||||
@ -793,7 +796,7 @@ class CreateServer(command.ShowOne):
|
|||||||
mapping = {'device_name': dev_name}
|
mapping = {'device_name': dev_name}
|
||||||
# 1. decide source and destination type
|
# 1. decide source and destination type
|
||||||
if (len(dev_map) > 1 and
|
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]
|
mapping['source_type'] = dev_map[1]
|
||||||
else:
|
else:
|
||||||
mapping['source_type'] = 'volume'
|
mapping['source_type'] = 'volume'
|
||||||
@ -808,14 +811,29 @@ class CreateServer(command.ShowOne):
|
|||||||
snapshot_id = utils.find_resource(
|
snapshot_id = utils.find_resource(
|
||||||
volume_client.volume_snapshots, dev_map[0]).id
|
volume_client.volume_snapshots, dev_map[0]).id
|
||||||
mapping['uuid'] = snapshot_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
|
# 3. append size and delete_on_termination if exist
|
||||||
if len(dev_map) > 2 and dev_map[2]:
|
if len(dev_map) > 2 and dev_map[2]:
|
||||||
mapping['volume_size'] = dev_map[2]
|
mapping['volume_size'] = dev_map[2]
|
||||||
if len(dev_map) > 3 and dev_map[3]:
|
if len(dev_map) > 3 and dev_map[3]:
|
||||||
mapping['delete_on_termination'] = dev_map[3]
|
mapping['delete_on_termination'] = dev_map[3]
|
||||||
else:
|
else:
|
||||||
msg = _("Volume or snapshot (name or ID) must be specified if "
|
msg = _("Volume, volume snapshot or image (name or ID) must "
|
||||||
"--block-device-mapping is specified")
|
"be specified if --block-device-mapping is specified")
|
||||||
raise exceptions.CommandError(msg)
|
raise exceptions.CommandError(msg)
|
||||||
block_device_mapping_v2.append(mapping)
|
block_device_mapping_v2.append(mapping)
|
||||||
|
|
||||||
|
@ -614,6 +614,93 @@ class ServerTests(common.ComputeTestCase):
|
|||||||
# the attached volume had been deleted
|
# the attached volume had been deleted
|
||||||
pass
|
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):
|
def test_server_create_with_none_network(self):
|
||||||
"""Test server create with none network option."""
|
"""Test server create with none network option."""
|
||||||
server_name = uuid.uuid4().hex
|
server_name = uuid.uuid4().hex
|
||||||
|
@ -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 <https://storyboard.openstack.org/#!/story/2006302>`_]
|
Loading…
x
Reference in New Issue
Block a user