From e26f779d92d205b303ed49e1372b1ba9ece8e945 Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Thu, 26 Sep 2024 14:35:54 +0900 Subject: [PATCH] Support online volume extension Cinder supports extending volumes in use since API microversion 3.42. Check the supported microversion and use this feature if supported, instead of detaching the volume. Story: 2011229 Task: 51086 Change-Id: If67bdd2bc7a055d9b0c22772528f6ab08697f486 --- .../resources/openstack/cinder/volume.py | 12 +++++- heat/tests/aws/test_volume.py | 5 +-- heat/tests/openstack/cinder/test_volume.py | 43 +++++++++++++++++++ .../openstack/cinder/test_volume_utils.py | 3 +- ...ine-volume-extension-bb9d6554bda5b682.yaml | 6 +++ 5 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/online-volume-extension-bb9d6554bda5b682.yaml diff --git a/heat/engine/resources/openstack/cinder/volume.py b/heat/engine/resources/openstack/cinder/volume.py index ef41355824..fa4bc9fdd0 100644 --- a/heat/engine/resources/openstack/cinder/volume.py +++ b/heat/engine/resources/openstack/cinder/volume.py @@ -27,6 +27,8 @@ from heat.engine import translation LOG = logging.getLogger(__name__) +CINDER_MICROVERSIONS = (MICROVERSION_EXTEND_INUSE,) = ('3.42',) + class CinderVolume(vb.BaseVolume, sh.SchedulerHintsMixin): """A resource that implements Cinder volumes. @@ -369,6 +371,9 @@ class CinderVolume(vb.BaseVolume, sh.SchedulerHintsMixin): def _ready_to_extend_volume(self): vol = self.client().volumes.get(self.resource_id) expected_status = ('available',) + if self.client_plugin().is_version_supported( + MICROVERSION_EXTEND_INUSE): + expected_status = ('available', 'in-use') if vol.status in expected_status: LOG.debug("Volume %s is ready to extend.", vol.id) return True @@ -381,6 +386,9 @@ class CinderVolume(vb.BaseVolume, sh.SchedulerHintsMixin): return False expected_status = ('available',) + if self.client_plugin().is_version_supported( + MICROVERSION_EXTEND_INUSE): + expected_status = ('available', 'in-use') if vol.status not in expected_status: LOG.info("Resize failed: Volume %(vol)s " "is in %(status)s state.", @@ -490,7 +498,9 @@ class CinderVolume(vb.BaseVolume, sh.SchedulerHintsMixin): elif new_size > vol.size: prg_resize = progress.VolumeResizeProgress(size=new_size) - prg_detach, prg_attach = self._detach_attach_progress(vol) + if not self.client_plugin().is_version_supported( + MICROVERSION_EXTEND_INUSE): + prg_detach, prg_attach = self._detach_attach_progress(vol) return prg_restore, prg_detach, prg_resize, prg_access, prg_attach diff --git a/heat/tests/aws/test_volume.py b/heat/tests/aws/test_volume.py index fe2cae7e1e..44406b77e1 100644 --- a/heat/tests/aws/test_volume.py +++ b/heat/tests/aws/test_volume.py @@ -14,7 +14,6 @@ import copy from unittest import mock -from cinderclient import api_versions from cinderclient import exceptions as cinder_exp from oslo_config import cfg @@ -625,7 +624,7 @@ class VolumeTest(vt_base.VolumeTestCase): self.create_volume(self.t, stack, 'DataVolume', no_create=True) cinder.CinderClientPlugin._create.assert_called_once_with( - version=api_versions.MAX_VERSION) + version='3.0') self.m_restore.assert_called_once_with('backup-123') self.cinder_fc.volumes.get.assert_called_with('vol-123') self.cinder_fc.volumes.update.assert_called_once_with( @@ -654,7 +653,7 @@ class VolumeTest(vt_base.VolumeTestCase): str(ex)) cinder.CinderClientPlugin._create.assert_called_once_with( - version=api_versions.MAX_VERSION) + version='3.0') self.m_restore.assert_called_once_with('backup-123') self.cinder_fc.volumes.update.assert_called_once_with( fv.id, description=vol_name, name=vol_name) diff --git a/heat/tests/openstack/cinder/test_volume.py b/heat/tests/openstack/cinder/test_volume.py index 3d658e86c7..ef5f6c4cd7 100644 --- a/heat/tests/openstack/cinder/test_volume.py +++ b/heat/tests/openstack/cinder/test_volume.py @@ -449,6 +449,36 @@ class CinderVolumeTest(vt_base.VolumeTestCase): self.stack_name = 'test_cvolume_extend_att_stack' self._update_if_attached(self.stack_name) + def test_cinder_volume_extend_attached_live(self): + self.patchobject(cinder.CinderClientPlugin, 'get_max_microversion', + return_value='3.42') + self.stack_name = 'test_cvolume_extend_live_att_stack' + + fv = vt_base.FakeVolume('available', + size=1, attachments=[]) + self._mock_create_volume(vt_base.FakeVolume('creating'), + self.stack_name, + extra_get_mocks=[ + fv, vt_base.FakeVolume('extending'), + vt_base.FakeVolume('extending'), + vt_base.FakeVolume('in-use')]) + + stack = utils.parse_stack(self.t, stack_name=self.stack_name) + + rsrc = self.create_volume(self.t, stack, 'volume') + + props = copy.deepcopy(rsrc.properties.data) + props['size'] = 2 + after = rsrc_defn.ResourceDefinition(rsrc.name, rsrc.type(), props) + + update_task = scheduler.TaskRunner(rsrc.update, after) + self.assertIsNone(update_task()) + + self.assertEqual((rsrc.UPDATE, rsrc.COMPLETE), rsrc.state) + self.cinder_fc.volumes.extend.assert_called_once_with(fv.id, 2) + self.fc.volumes.get_server_volume.assert_not_called() + self.fc.volumes.delete_server_volume.assert_not_called() + def test_cinder_volume_extend_created_from_backup_with_same_size(self): self.stack_name = 'test_cvolume_extend_snapsht_stack' @@ -1386,6 +1416,11 @@ class CinderVolumeTest(vt_base.VolumeTestCase): self._mock_create_volume(vt_base.FakeVolume('creating'), self.stack_name, extra_get_mocks=[ + vt_base.FakeVolume('extending'), + vt_base.FakeVolume('reserved'), + vt_base.FakeVolume('in-use'), + vt_base.FakeVolume('available'), + vt_base.FakeVolume('creating'), vt_base.FakeVolume('extending'), vt_base.FakeVolume('reserved'), vt_base.FakeVolume('in-use'), @@ -1401,6 +1436,14 @@ class CinderVolumeTest(vt_base.VolumeTestCase): self.assertEqual(False, rsrc._ready_to_extend_volume()) self.assertEqual(True, rsrc._ready_to_extend_volume()) + self.patchobject(cinder.CinderClientPlugin, 'get_max_microversion', + return_value='3.42') + self.assertEqual(False, rsrc._ready_to_extend_volume()) + self.assertEqual(False, rsrc._ready_to_extend_volume()) + self.assertEqual(False, rsrc._ready_to_extend_volume()) + self.assertEqual(True, rsrc._ready_to_extend_volume()) + self.assertEqual(True, rsrc._ready_to_extend_volume()) + def test_try_detach_volume_if_server_was_temporarily_in_error(self): self.stack_name = 'test_cvolume_detach_server_in_error_stack' fva = vt_base.FakeVolume('in-use') diff --git a/heat/tests/openstack/cinder/test_volume_utils.py b/heat/tests/openstack/cinder/test_volume_utils.py index 51e2966b24..af884920a8 100644 --- a/heat/tests/openstack/cinder/test_volume_utils.py +++ b/heat/tests/openstack/cinder/test_volume_utils.py @@ -13,7 +13,6 @@ from unittest import mock -from cinderclient import api_versions from cinderclient.v3 import client as cinderclient from heat.engine.clients.os import cinder @@ -34,7 +33,7 @@ class VolumeTestCase(common.HeatTestCase): self.cinder_fc = cinderclient.Client('username', 'password') self.cinder_fc.volume_api_version = 3 self.patchobject(cinder.CinderClientPlugin, 'get_max_microversion', - return_value=api_versions.MAX_VERSION) + return_value='3.0') self.patchobject(cinder.CinderClientPlugin, '_create', return_value=self.cinder_fc) self.patchobject(nova.NovaClientPlugin, 'client', diff --git a/releasenotes/notes/online-volume-extension-bb9d6554bda5b682.yaml b/releasenotes/notes/online-volume-extension-bb9d6554bda5b682.yaml new file mode 100644 index 0000000000..11dd347bc6 --- /dev/null +++ b/releasenotes/notes/online-volume-extension-bb9d6554bda5b682.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + The ``OS::Cinder::Volume`` resource type now supports extending volumes + in use. Note that this requires that Cinder supports API microversion + 3.42 or later.