Add method to set bootable flag on volumes

If a person wants to create a bootable volume not from an image, they
need to set a flag, which is done with this action call.

Change-Id: I765eb97501a5ba9e54325c8c56573bb7311deb72
This commit is contained in:
Monty Taylor 2017-09-11 08:26:34 -06:00 committed by David Shrewsbury
parent b653090489
commit 8cda430e8b
3 changed files with 127 additions and 2 deletions

View File

@ -0,0 +1,4 @@
---
features:
- Added a ``set_volume_bootable`` call to allow toggling the bootable state
of a volume.

View File

@ -4806,7 +4806,7 @@ class OpenStackCloud(
def create_volume(
self, size,
wait=True, timeout=None, image=None, **kwargs):
wait=True, timeout=None, image=None, bootable=None, **kwargs):
"""Create a volume.
:param size: Size, in GB of the volume to create.
@ -4816,6 +4816,8 @@ class OpenStackCloud(
:param timeout: Seconds to wait for volume creation. None is forever.
:param image: (optional) Image name, ID or object from which to create
the volume
:param bootable: (optional) Make this volume bootable. If set, wait
will also be set to true.
:param kwargs: Keyword arguments as expected for cinder client.
:returns: The created volume object.
@ -4823,6 +4825,9 @@ class OpenStackCloud(
:raises: OpenStackCloudTimeout if wait time exceeded.
:raises: OpenStackCloudException on operation error.
"""
if bootable is not None:
wait = True
if image:
image_obj = self.get_image(image)
if not image_obj:
@ -4858,6 +4863,10 @@ class OpenStackCloud(
continue
if volume['status'] == 'available':
if bootable is not None:
self.set_volume_bootable(volume, bootable=bootable)
# no need to re-fetch to update the flag, just set it.
volume['bootable'] = bootable
return volume
if volume['status'] == 'error':
@ -4865,6 +4874,31 @@ class OpenStackCloud(
return self._normalize_volume(volume)
def set_volume_bootable(self, name_or_id, bootable=True):
"""Set a volume's bootable flag.
:param name_or_id: Name, unique ID of the volume or a volume dict.
:param bool bootable: Whether the volume should be bootable.
(Defaults to True)
:raises: OpenStackCloudTimeout if wait time exceeded.
:raises: OpenStackCloudException on operation error.
"""
volume = self.get_volume(name_or_id)
if not volume:
raise OpenStackCloudException(
"Volume {name_or_id} does not exist".format(
name_or_id=name_or_id))
self._volume_client.post(
'volumes/{id}/action'.format(id=volume['id']),
json={'os-set_bootable': {'bootable': bootable}},
error_message="Error setting bootable on volume {volume}".format(
volume=volume['id'])
)
def delete_volume(self, name_or_id=None, wait=True, timeout=None,
force=False):
"""Delete a volume.

View File

@ -292,7 +292,8 @@ class TestVolume(base.RequestsMockTestCase):
uri=self.get_mock_url(
'volumev2', 'public',
append=['volumes', volume.id, 'action']),
json={'os-force_delete': None}),
validate=dict(
json={'os-force_delete': None})),
dict(method='GET',
uri=self.get_mock_url(
'volumev2', 'public', append=['volumes', 'detail']),
@ -300,6 +301,42 @@ class TestVolume(base.RequestsMockTestCase):
self.assertTrue(self.cloud.delete_volume(volume['id'], force=True))
self.assert_calls()
def test_set_volume_bootable(self):
vol = {'id': 'volume001', 'status': 'attached',
'name': '', 'attachments': []}
volume = meta.obj_to_munch(fakes.FakeVolume(**vol))
self.register_uris([
dict(method='GET',
uri=self.get_mock_url(
'volumev2', 'public', append=['volumes', 'detail']),
json={'volumes': [volume]}),
dict(method='POST',
uri=self.get_mock_url(
'volumev2', 'public',
append=['volumes', volume.id, 'action']),
json={'os-set_bootable': {'bootable': True}}),
])
self.cloud.set_volume_bootable(volume['id'])
self.assert_calls()
def test_set_volume_bootable_false(self):
vol = {'id': 'volume001', 'status': 'attached',
'name': '', 'attachments': []}
volume = meta.obj_to_munch(fakes.FakeVolume(**vol))
self.register_uris([
dict(method='GET',
uri=self.get_mock_url(
'volumev2', 'public', append=['volumes', 'detail']),
json={'volumes': [volume]}),
dict(method='POST',
uri=self.get_mock_url(
'volumev2', 'public',
append=['volumes', volume.id, 'action']),
json={'os-set_bootable': {'bootable': False}}),
])
self.cloud.set_volume_bootable(volume['id'])
self.assert_calls()
def test_list_volumes_with_pagination(self):
vol1 = meta.obj_to_munch(fakes.FakeVolume('01', 'available', 'vol1'))
vol2 = meta.obj_to_munch(fakes.FakeVolume('02', 'available', 'vol2'))
@ -448,3 +485,53 @@ class TestVolume(base.RequestsMockTestCase):
self.cloud._normalize_volume(vol1),
self.cloud.get_volume_by_id('01'))
self.assert_calls()
def test_create_volume(self):
vol1 = meta.obj_to_munch(fakes.FakeVolume('01', 'available', 'vol1'))
self.register_uris([
dict(method='POST',
uri=self.get_mock_url(
'volumev2', 'public', append=['volumes']),
json={'volume': vol1},
validate=dict(json={
'volume': {
'size': 50,
'name': 'vol1',
}})),
dict(method='GET',
uri=self.get_mock_url(
'volumev2', 'public',
append=['volumes', 'detail']),
json={'volumes': [vol1]}),
])
self.cloud.create_volume(50, name='vol1')
self.assert_calls()
def test_create_bootable_volume(self):
vol1 = meta.obj_to_munch(fakes.FakeVolume('01', 'available', 'vol1'))
self.register_uris([
dict(method='POST',
uri=self.get_mock_url(
'volumev2', 'public', append=['volumes']),
json={'volume': vol1},
validate=dict(json={
'volume': {
'size': 50,
'name': 'vol1',
}})),
dict(method='GET',
uri=self.get_mock_url(
'volumev2', 'public',
append=['volumes', 'detail']),
json={'volumes': [vol1]}),
dict(method='POST',
uri=self.get_mock_url(
'volumev2', 'public',
append=['volumes', '01', 'action']),
validate=dict(
json={'os-set_bootable': {'bootable': True}})),
])
self.cloud.create_volume(50, name='vol1', bootable=True)
self.assert_calls()