Add support for microversion 2.67: BDMv2 volume_type

This adds the nova boot command and python API binding
support for creating a server with block device mappings
defined using a specific volume type.

Depends-On: https://review.openstack.org/606398/
Depends-On: https://review.openstack.org/#/c/610349/

Part of blueprint boot-instance-specific-storage-backend

Change-Id: I484ee065119b5783db212ea64efa60e87c40338c
This commit is contained in:
Matt Riedemann 2018-10-11 11:31:06 -04:00 committed by zhangbailin
parent 0fdb154d9c
commit 4fef02de71
8 changed files with 187 additions and 5 deletions

View File

@ -1036,9 +1036,13 @@ Boot a new server.
to 0, for others need to be specified),
shutdown=shutdown behaviour (either preserve
or remove, for local destination set to
remove) and tag=device metadata tag
(optional). (Supported by API versions '2.42'
- '2.latest')
remove), tag=device metadata tag
(optional; supported by API versions '2.42'
- '2.latest'), and volume_type=type of volume
to create (either ID or name) when source is
`blank`, `image` or `snapshot` and dest is `volume`
(optional; supported by API versions '2.67'
- '2.latest').
``--swap <swap_size>``
Create and attach a local swap block device of

View File

@ -25,4 +25,4 @@ API_MIN_VERSION = api_versions.APIVersion("2.1")
# when client supported the max version, and bumped sequentially, otherwise
# the client may break due to server side new version may include some
# backward incompatible change.
API_MAX_VERSION = api_versions.APIVersion("2.66")
API_MAX_VERSION = api_versions.APIVersion("2.67")

View File

@ -104,7 +104,7 @@ class FixturedTestCase(testscenarios.TestWithScenarios, TestCase):
if not isinstance(body, six.string_types):
# json load if the input body to match against is not a string
req_data = jsonutils.loads(req_data)
self.assertEqual(req_data, body)
self.assertEqual(body, req_data)
class TestResponse(requests.Response):

View File

@ -1609,3 +1609,48 @@ class ServersV263Test(ServersV257Test):
'1234', fakes.FAKE_IMAGE_UUID_1,
trusted_image_certificates=['id1', 'id2'])
self.assertIn('trusted_image_certificates', six.text_type(ex))
class ServersV267Test(ServersV263Test):
"""Tests for creating a server with a block_device_mapping_v2 entry
using volume_type for microversion 2.67.
"""
api_version = '2.67'
def test_create_server_boot_from_volume_with_volume_type(self):
bdm = [{"volume_size": 1,
"uuid": "11111111-1111-1111-1111-111111111111",
"delete_on_termination": True,
"source_type": "snapshot",
"destination_type": "volume",
"boot_index": 0,
"volume_type": "rbd"}]
s = self.cs.servers.create(
name="bfv server", image='', flavor=1,
nics='auto', block_device_mapping_v2=bdm)
self.assert_request_id(s, fakes.FAKE_REQUEST_ID_LIST)
self.assert_called('POST', '/servers', {
'server': {
'flavorRef': '1',
'imageRef': '',
'name': 'bfv server',
'networks': 'auto',
'block_device_mapping_v2': bdm,
'min_count': 1,
'max_count': 1,
}})
def test_create_server_boot_from_volume_with_volume_type_pre_267(self):
self.cs.api_version = api_versions.APIVersion('2.66')
bdm = [{"volume_size": 1,
"uuid": "11111111-1111-1111-1111-111111111111",
"delete_on_termination": True,
"source_type": "snapshot",
"destination_type": "volume",
"boot_index": 0,
"volume_type": "rbd"}]
ex = self.assertRaises(ValueError, self.cs.servers.create,
name="bfv server", image='', flavor=1,
nics='none', block_device_mapping_v2=bdm)
self.assertIn("Block device volume_type is not supported before "
"microversion 2.67", six.text_type(ex))

View File

@ -624,6 +624,78 @@ class ShellTest(utils.TestCase):
'size=1,format=ext4,type=disk,shutdown=foobar '
'some-server' % FAKE_UUID_1))
def test_boot_from_volume_with_volume_type_old_microversion(self):
ex = self.assertRaises(
exceptions.CommandError, self.run_command,
'boot --flavor 1 --block-device id=%s,source=image,dest=volume,'
'size=1,bootindex=0,shutdown=remove,tag=foo,volume_type=lvm '
'bfv-server' % FAKE_UUID_1, api_version='2.66')
self.assertIn("'volume_type' in block device mapping is not supported "
"in API version", six.text_type(ex))
def test_boot_from_volume_with_volume_type(self):
"""Tests creating a volume-backed server from a source image and
specifying the type of volume to create with microversion 2.67.
"""
self.run_command(
'boot --flavor 1 --block-device id=%s,source=image,dest=volume,'
'size=1,bootindex=0,shutdown=remove,tag=foo,volume_type=lvm '
'bfv-server' % FAKE_UUID_1, api_version='2.67')
self.assert_called_anytime(
'POST', '/servers',
{'server': {
'flavorRef': '1',
'name': 'bfv-server',
'block_device_mapping_v2': [
{
'uuid': FAKE_UUID_1,
'source_type': 'image',
'destination_type': 'volume',
'volume_size': '1',
'delete_on_termination': True,
'tag': 'foo',
'boot_index': '0',
'volume_type': 'lvm'
},
],
'networks': 'auto',
'imageRef': '',
'min_count': 1,
'max_count': 1,
}})
def test_boot_from_volume_without_volume_type_2_67(self):
"""Tests creating a volume-backed server from a source image but
without specifying the type of volume to create with microversion 2.67.
The volume_type parameter should be omitted in the request to the
API server.
"""
self.run_command(
'boot --flavor 1 --block-device id=%s,source=image,dest=volume,'
'size=1,bootindex=0,shutdown=remove,tag=foo bfv-server' %
FAKE_UUID_1, api_version='2.67')
self.assert_called_anytime(
'POST', '/servers',
{'server': {
'flavorRef': '1',
'name': 'bfv-server',
'block_device_mapping_v2': [
{
'uuid': FAKE_UUID_1,
'source_type': 'image',
'destination_type': 'volume',
'volume_size': '1',
'delete_on_termination': True,
'tag': 'foo',
'boot_index': '0',
},
],
'networks': 'auto',
'imageRef': '',
'min_count': 1,
'max_count': 1,
}})
def test_boot_metadata(self):
self.run_command('boot --image %s --flavor 1 --meta foo=bar=pants'
' --meta spam=eggs some-server ' % FAKE_UUID_1)
@ -3913,6 +3985,7 @@ class ShellTest(utils.TestCase):
62, # There are no version-wrapped shell method changes for this.
63, # There are no version-wrapped shell method changes for this.
65, # There are no version-wrapped shell method changes for this.
67, # There are no version-wrapped shell method changes for this.
])
versions_supported = set(range(0,
novaclient.API_MAX_VERSION.ver_minor + 1))

View File

@ -1305,6 +1305,14 @@ class ServerManager(base.BootingManagerWithFind):
raise exceptions.UnsupportedAttribute("trusted_image_certificates",
"2.63")
if (block_device_mapping_v2 and
self.api_version < api_versions.APIVersion('2.67')):
for bdm in block_device_mapping_v2:
if bdm.get('volume_type'):
raise ValueError(
"Block device volume_type is not supported before "
"microversion 2.67")
boot_kwargs = dict(
meta=meta, files=files, userdata=userdata,
reservation_id=reservation_id, min_count=min_count,

View File

@ -71,6 +71,7 @@ CLIENT_BDM2_KEYS = {
'type': 'device_type',
'shutdown': 'delete_on_termination',
'tag': 'tag',
'volume_type': 'volume_type' # added in 2.67
}
@ -145,6 +146,8 @@ def _parse_block_device_mapping_v2(cs, args, image):
'delete_on_termination': False}
bdm.append(bdm_dict)
supports_volume_type = cs.api_version == api_versions.APIVersion('2.67')
for device_spec in args.block_device:
spec_dict = _parse_device_spec(device_spec)
bdm_dict = {}
@ -155,6 +158,12 @@ def _parse_block_device_mapping_v2(cs, args, image):
"in API version %(version)s.")
% {'version': cs.api_version.get_string()})
if 'volume_type' in spec_dict and not supports_volume_type:
raise exceptions.CommandError(
_("'volume_type' in block device mapping is not supported "
"in API version %(version)s.")
% {'version': cs.api_version.get_string()})
for key, value in spec_dict.items():
bdm_dict[CLIENT_BDM2_KEYS[key]] = value
@ -709,6 +718,7 @@ def _boot(cs, args):
action='append',
default=[],
start_version='2.42',
end_version='2.66',
help=_("Block device mapping with the keys: "
"id=UUID (image_id, snapshot_id or volume_id only if using source "
"image, snapshot or volume) "
@ -732,6 +742,38 @@ def _boot(cs, args):
"shutdown=shutdown behaviour (either preserve or remove, "
"for local destination set to remove) and "
"tag=device metadata tag (optional)."))
@utils.arg(
'--block-device',
metavar="key1=value1[,key2=value2...]",
action='append',
default=[],
start_version='2.67',
help=_("Block device mapping with the keys: "
"id=UUID (image_id, snapshot_id or volume_id only if using source "
"image, snapshot or volume) "
"source=source type (image, snapshot, volume or blank), "
"dest=destination type of the block device (volume or local), "
"bus=device's bus (e.g. uml, lxc, virtio, ...; if omitted, "
"hypervisor driver chooses a suitable default, "
"honoured only if device type is supplied) "
"type=device type (e.g. disk, cdrom, ...; defaults to 'disk') "
"device=name of the device (e.g. vda, xda, ...; "
"if omitted, hypervisor driver chooses suitable device "
"depending on selected bus; note the libvirt driver always "
"uses default device names), "
"size=size of the block device in MB(for swap) and in "
"GiB(for other formats) "
"(if omitted, hypervisor driver calculates size), "
"format=device will be formatted (e.g. swap, ntfs, ...; optional), "
"bootindex=integer used for ordering the boot disks "
"(for image backed instances it is equal to 0, "
"for others need to be specified), "
"shutdown=shutdown behaviour (either preserve or remove, "
"for local destination set to remove) and "
"tag=device metadata tag (optional), "
"volume_type=type of volume to create (either ID or name) when "
"source is blank, image or snapshot and dest is volume (optional)."
))
@utils.arg(
'--swap',
metavar="<swap_size>",

View File

@ -0,0 +1,10 @@
---
features:
- |
Support is added for the `2.67 microversion`_ which allows specifying
a ``volume_type`` with the ``--block-device`` option on the ``nova boot``
command. The ``novaclient.v2.servers.ServerManager.create()`` method now
also supports a ``volume_type`` entry in the ``block_device_mapping_v2``
parameter.
.. _2.67 microversion: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id60