From ec374ded96aeb514ed14956742e72a1c3b7703ca Mon Sep 17 00:00:00 2001 From: Taylor Jakobson Date: Thu, 5 Oct 2017 16:59:37 -0500 Subject: [PATCH] Add extend volume support for fileio When cinder resizes a volume, it sends an event to nova. This event calls into the compute driver's "extend_volume" method. Calling the pypowervm rescan_vstor is all that is needed for our driver. Only kernel backstores are currently supported by the rescan_vstor method. Not all vios are guaranteed to have the storage mapped. Log an error if the virtual disk was not found, but throw an error if no virtual disks were found on any of the vios. Switching the backstore from USER_QCOW to FILE_IO enables this change. This adds support to extend size of a volume while it is attached to a client VM. Change-Id: I0f6f80df6ad0d87dcccc560509c4e31992e4c0ec --- doc/source/support-matrix.ini | 15 +++++++++ .../tests/virt/powervm/volume/test_fileio.py | 17 ++++++++++ nova_powervm/virt/powervm/driver.py | 9 ++++- nova_powervm/virt/powervm/exception.py | 5 +++ nova_powervm/virt/powervm/volume/driver.py | 33 +++++++++++++++++++ nova_powervm/virt/powervm/volume/fileio.py | 7 ++++ 6 files changed, 85 insertions(+), 1 deletion(-) 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 83220a2d..5e34a7c7 100644 --- a/nova_powervm/virt/powervm/driver.py +++ b/nova_powervm/virt/powervm/driver.py @@ -94,7 +94,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): @@ -743,6 +744,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,