diff --git a/doc/source/support-matrix.ini b/doc/source/support-matrix.ini index 726151c6..aa6c3c58 100644 --- a/doc/source/support-matrix.ini +++ b/doc/source/support-matrix.ini @@ -92,6 +92,21 @@ notes=See notes for attach volume operation. cli= driver-impl-powervm=complete +[operation.extend-volume] +title=Extend block volume attached to instance +status=optional +notes=The extend volume operation provides a means to extend + the size of an attached volume. This allows volume size + to be expanded without interruption of service. + In a cloud model it would be more typical to just + spin up a new instance with large storage, so the ability to + extend the size of an attached volume is for those cases + where the instance is considered to be more of a pet than cattle. + Therefore this operation is not considered to be mandatory to support. +cli=cinder extend +driver-impl-powervm=partial +driver-notes-powervm=Extend volume only supported for fileio volumes. + [operation.maintenance-mode] title=Set the host in a maintenance mode status=optional diff --git a/nova_powervm/tests/virt/powervm/volume/test_fileio.py b/nova_powervm/tests/virt/powervm/volume/test_fileio.py index fa3d4ccb..a250d60b 100644 --- a/nova_powervm/tests/virt/powervm/volume/test_fileio.py +++ b/nova_powervm/tests/virt/powervm/volume/test_fileio.py @@ -20,6 +20,7 @@ from nova_powervm.tests.virt.powervm.volume import test_driver as test_vol from nova_powervm.virt.powervm import exception as p_exc from nova_powervm.virt.powervm.volume import fileio as v_drv from pypowervm import const as pvm_const +from pypowervm import exceptions as pvm_exc from pypowervm.tests import test_fixtures as pvm_fx from pypowervm.wrappers import base_partition as pvm_bp from pypowervm.wrappers import storage as pvm_stg @@ -130,6 +131,22 @@ class TestFileIOVolumeAdapter(test_vol.TestVolumeAdapter): backstore_type=pvm_stg.BackStoreType.FILE_IO) self.assertEqual(0, mock_build_map.call_count) + @mock.patch('pypowervm.tasks.partition.get_mgmt_partition', autospec=True) + @mock.patch('pypowervm.tasks.storage.rescan_vstor', autospec=True) + def test_extend_volume(self, mock_rescan, mock_get_mgmt_partition): + # FileIO driver can only have 1 uuid in vol_drv.vios_uuids + mock_vios = mock.Mock(uuid='uuid1') + mock_get_mgmt_partition.return_value = mock_vios + self.vol_drv.extend_volume() + mock_rescan.assert_called_once_with(self.vol_drv.vios_uuids[0], + "fake_path", adapter=self.adpt) + mock_rescan.side_effect = pvm_exc.JobRequestFailed( + operation_name='RescanVirtualDisk', error='fake_err') + self.assertRaises(p_exc.VolumeExtendFailed, self.vol_drv.extend_volume) + mock_rescan.side_effect = pvm_exc.VstorNotFound( + stor_udid='stor_udid', vios_uuid='uuid') + self.assertRaises(p_exc.VolumeExtendFailed, self.vol_drv.extend_volume) + @mock.patch('pypowervm.entities.Entry.uuid', new_callable=mock.PropertyMock) @mock.patch('pypowervm.tasks.partition.get_mgmt_partition') diff --git a/nova_powervm/virt/powervm/driver.py b/nova_powervm/virt/powervm/driver.py index e61ed33b..9dd72067 100644 --- a/nova_powervm/virt/powervm/driver.py +++ b/nova_powervm/virt/powervm/driver.py @@ -95,7 +95,8 @@ class PowerVMDriver(driver.ComputeDriver): "supports_recreate": True, "supports_migrate_to_same_host": False, "supports_attach_interface": True, - "supports_device_tagging": False + "supports_device_tagging": False, + "supports_extend_volume": True } def __init__(self, virtapi): @@ -757,6 +758,12 @@ class PowerVMDriver(driver.ComputeDriver): # to revise in the future as volume connectors evolve. instance.save() + def extend_volume(self, connection_info, instance): + """Resize an attached volume""" + vol_drv = vol_attach.build_volume_driver( + self.adapter, self.host_uuid, instance, connection_info) + vol_drv.extend_volume() + def detach_volume(self, connection_info, instance, mountpoint, encryption=None): """Detach the volume attached to the instance.""" diff --git a/nova_powervm/virt/powervm/exception.py b/nova_powervm/virt/powervm/exception.py index 10bc80db..ad3fb144 100644 --- a/nova_powervm/virt/powervm/exception.py +++ b/nova_powervm/virt/powervm/exception.py @@ -101,6 +101,11 @@ class VolumeAttachFailed(nex.NovaException): "machine %(instance_name)s. %(reason)s") +class VolumeExtendFailed(nex.NovaException): + msg_fmt = _("Unable to extend volume (id: %(volume_id)s) on virtual " + "machine %(instance_name)s.") + + class VolumeDetachFailed(nex.NovaException): msg_fmt = _("Unable to detach volume (id: %(volume_id)s) from virtual " "machine %(instance_name)s. %(reason)s") diff --git a/nova_powervm/virt/powervm/volume/driver.py b/nova_powervm/virt/powervm/volume/driver.py index af24107d..25edbb68 100644 --- a/nova_powervm/virt/powervm/volume/driver.py +++ b/nova_powervm/virt/powervm/volume/driver.py @@ -17,7 +17,10 @@ import abc import six +from oslo_log import log as logging +from pypowervm import exceptions as pvm_exc from pypowervm.tasks import partition as pvm_partition +from pypowervm.tasks import storage as tsk_stg from pypowervm.utils import transaction as pvm_tx from pypowervm.wrappers import virtual_io_server as pvm_vios @@ -25,6 +28,7 @@ from nova_powervm.virt.powervm import exception as exc from nova_powervm.virt.powervm import vm +LOG = logging.getLogger(__name__) LOCAL_FEED_TASK = 'local_feed_task' @@ -228,6 +232,35 @@ class PowerVMVolumeAdapter(object): if self.stg_ftsk.name == LOCAL_FEED_TASK: self.stg_ftsk.execute() + def extend_volume(self): + raise NotImplementedError() + + def _extend_volume(self, udid): + """Rescan virtual disk so client VM can see extended size""" + resized = False + error = False + for vios_uuid in self.vios_uuids: + try: + LOG.debug("Rescanning volume %(vol)s for vios uuid %(uuid)s", + dict(vol=self.volume_id, uuid=vios_uuid), + instance=self.instance) + tsk_stg.rescan_vstor(vios_uuid, udid, adapter=self.adapter) + resized = True + except pvm_exc.VstorNotFound: + LOG.info("Failed to find volume %(vol)s for VIOS " + "UUID %(uuid)s during extend operation.", + {'vol': self.volume_id, 'uuid': vios_uuid}, + instance=self.instance) + except pvm_exc.JobRequestFailed as e: + error = True + LOG.error("Failed to rescan volume %(vol)s for VIOS " + "UUID %(uuid)s. %(reason)s", + {'vol': self.volume_id, 'uuid': vios_uuid, + 'reason': six.text_type(e)}, instance=self.instance) + if not resized or error: + raise exc.VolumeExtendFailed(volume_id=self.volume_id, + instance_name=self.instance.name) + def disconnect_volume(self, slot_mgr): """Disconnect the volume. diff --git a/nova_powervm/virt/powervm/volume/fileio.py b/nova_powervm/virt/powervm/volume/fileio.py index 5cfa5145..3a422103 100644 --- a/nova_powervm/virt/powervm/volume/fileio.py +++ b/nova_powervm/virt/powervm/volume/fileio.py @@ -19,6 +19,7 @@ import os import six from taskflow import task +from nova import exception as nova_exc from nova_powervm import conf as cfg from nova_powervm.virt.powervm import exception as p_exc from nova_powervm.virt.powervm import vm @@ -136,6 +137,12 @@ class FileIOVolumeAdapter(v_driver.PowerVMVolumeAdapter): self.stg_ftsk.add_post_execute(task.FunctorTask( set_slot_info, name='file_io_slot_%s' % path)) + def extend_volume(self): + path = self._get_path() + if path is None: + raise nova_exc.InvalidBDM() + self._extend_volume(path) + def _disconnect_volume(self, slot_mgr): # Build the match function match_func = tsk_map.gen_match_func(pvm_stg.VDisk,