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
This commit is contained in:
Takashi Kajinami 2024-09-26 14:35:54 +09:00
parent 0b84e5be0d
commit e26f779d92
5 changed files with 63 additions and 6 deletions
heat
engine/resources/openstack/cinder
tests
releasenotes/notes

View File

@ -27,6 +27,8 @@ from heat.engine import translation
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
CINDER_MICROVERSIONS = (MICROVERSION_EXTEND_INUSE,) = ('3.42',)
class CinderVolume(vb.BaseVolume, sh.SchedulerHintsMixin): class CinderVolume(vb.BaseVolume, sh.SchedulerHintsMixin):
"""A resource that implements Cinder volumes. """A resource that implements Cinder volumes.
@ -369,6 +371,9 @@ class CinderVolume(vb.BaseVolume, sh.SchedulerHintsMixin):
def _ready_to_extend_volume(self): def _ready_to_extend_volume(self):
vol = self.client().volumes.get(self.resource_id) vol = self.client().volumes.get(self.resource_id)
expected_status = ('available',) expected_status = ('available',)
if self.client_plugin().is_version_supported(
MICROVERSION_EXTEND_INUSE):
expected_status = ('available', 'in-use')
if vol.status in expected_status: if vol.status in expected_status:
LOG.debug("Volume %s is ready to extend.", vol.id) LOG.debug("Volume %s is ready to extend.", vol.id)
return True return True
@ -381,6 +386,9 @@ class CinderVolume(vb.BaseVolume, sh.SchedulerHintsMixin):
return False return False
expected_status = ('available',) 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: if vol.status not in expected_status:
LOG.info("Resize failed: Volume %(vol)s " LOG.info("Resize failed: Volume %(vol)s "
"is in %(status)s state.", "is in %(status)s state.",
@ -490,7 +498,9 @@ class CinderVolume(vb.BaseVolume, sh.SchedulerHintsMixin):
elif new_size > vol.size: elif new_size > vol.size:
prg_resize = progress.VolumeResizeProgress(size=new_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 return prg_restore, prg_detach, prg_resize, prg_access, prg_attach

View File

@ -14,7 +14,6 @@
import copy import copy
from unittest import mock from unittest import mock
from cinderclient import api_versions
from cinderclient import exceptions as cinder_exp from cinderclient import exceptions as cinder_exp
from oslo_config import cfg from oslo_config import cfg
@ -625,7 +624,7 @@ class VolumeTest(vt_base.VolumeTestCase):
self.create_volume(self.t, stack, 'DataVolume', no_create=True) self.create_volume(self.t, stack, 'DataVolume', no_create=True)
cinder.CinderClientPlugin._create.assert_called_once_with( 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.m_restore.assert_called_once_with('backup-123')
self.cinder_fc.volumes.get.assert_called_with('vol-123') self.cinder_fc.volumes.get.assert_called_with('vol-123')
self.cinder_fc.volumes.update.assert_called_once_with( self.cinder_fc.volumes.update.assert_called_once_with(
@ -654,7 +653,7 @@ class VolumeTest(vt_base.VolumeTestCase):
str(ex)) str(ex))
cinder.CinderClientPlugin._create.assert_called_once_with( 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.m_restore.assert_called_once_with('backup-123')
self.cinder_fc.volumes.update.assert_called_once_with( self.cinder_fc.volumes.update.assert_called_once_with(
fv.id, description=vol_name, name=vol_name) fv.id, description=vol_name, name=vol_name)

View File

@ -449,6 +449,36 @@ class CinderVolumeTest(vt_base.VolumeTestCase):
self.stack_name = 'test_cvolume_extend_att_stack' self.stack_name = 'test_cvolume_extend_att_stack'
self._update_if_attached(self.stack_name) 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): def test_cinder_volume_extend_created_from_backup_with_same_size(self):
self.stack_name = 'test_cvolume_extend_snapsht_stack' 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._mock_create_volume(vt_base.FakeVolume('creating'),
self.stack_name, self.stack_name,
extra_get_mocks=[ 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('extending'),
vt_base.FakeVolume('reserved'), vt_base.FakeVolume('reserved'),
vt_base.FakeVolume('in-use'), vt_base.FakeVolume('in-use'),
@ -1401,6 +1436,14 @@ class CinderVolumeTest(vt_base.VolumeTestCase):
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())
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): def test_try_detach_volume_if_server_was_temporarily_in_error(self):
self.stack_name = 'test_cvolume_detach_server_in_error_stack' self.stack_name = 'test_cvolume_detach_server_in_error_stack'
fva = vt_base.FakeVolume('in-use') fva = vt_base.FakeVolume('in-use')

View File

@ -13,7 +13,6 @@
from unittest import mock from unittest import mock
from cinderclient import api_versions
from cinderclient.v3 import client as cinderclient from cinderclient.v3 import client as cinderclient
from heat.engine.clients.os import cinder 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 = cinderclient.Client('username', 'password')
self.cinder_fc.volume_api_version = 3 self.cinder_fc.volume_api_version = 3
self.patchobject(cinder.CinderClientPlugin, 'get_max_microversion', self.patchobject(cinder.CinderClientPlugin, 'get_max_microversion',
return_value=api_versions.MAX_VERSION) return_value='3.0')
self.patchobject(cinder.CinderClientPlugin, '_create', self.patchobject(cinder.CinderClientPlugin, '_create',
return_value=self.cinder_fc) return_value=self.cinder_fc)
self.patchobject(nova.NovaClientPlugin, 'client', self.patchobject(nova.NovaClientPlugin, 'client',

View File

@ -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.