Merge "Reset state robustification for volume os-reset_status"

This commit is contained in:
Zuul 2022-03-01 14:39:48 +00:00 committed by Gerrit Code Review
commit ef08a0eb30
2 changed files with 119 additions and 4 deletions

View File

@ -132,10 +132,6 @@ class VolumeAdminController(AdminController):
message)
def _update(self, *args, **kwargs):
context = args[0]
volume_id = args[1]
volume = objects.Volume.get_by_id(context, volume_id)
self.authorize(context, 'reset_status', target_obj=volume)
db.volume_update(*args, **kwargs)
def _get(self, *args, **kwargs):
@ -165,6 +161,53 @@ class VolumeAdminController(AdminController):
return update
@wsgi.response(HTTPStatus.ACCEPTED)
@wsgi.action('os-reset_status')
def _reset_status(self, req, id, body):
"""Reset status on the volume."""
def _clean_volume_attachment(context, id):
attachments = (
db.volume_attachment_get_all_by_volume_id(context, id))
for attachment in attachments:
db.volume_detached(context.elevated(), id, attachment.id)
db.volume_admin_metadata_delete(context.elevated(), id,
'attached_mode')
# any exceptions raised will be handled at the wsgi level
update = self.validate_update(req, body=body)
context = req.environ['cinder.context']
volume = objects.Volume.get_by_id(context, id)
self.authorize(context, 'reset_status', target_obj=volume)
# at this point, we still don't know if we're going to
# reset the volume's state. Need to check what the caller
# is requesting first.
if update.get('status') in ('deleting', 'error_deleting'
'detaching'):
msg = _("Cannot reset-state to %s"
% update.get('status'))
raise webob.exc.HTTPBadRequest(explanation=msg)
if update.get('status') == 'in-use':
attachments = (
db.volume_attachment_get_all_by_volume_id(context, id))
if not attachments:
msg = _("Cannot reset-state to in-use "
"because volume does not have any attachments.")
raise webob.exc.HTTPBadRequest(explanation=msg)
msg = "Updating %(resource)s '%(id)s' with '%(update)r'"
LOG.debug(msg, {'resource': self.resource_name, 'id': id,
'update': update})
self._notify_reset_status(context, id, 'reset_status.start')
self._update(context, id, update)
self._remove_worker(context, id)
if update.get('attach_status') == 'detached':
_clean_volume_attachment(context, id)
self._notify_reset_status(context, id, 'reset_status.end')
@wsgi.response(HTTPStatus.ACCEPTED)
@wsgi.action('os-force_detach')
@validation.schema(admin_actions.force_detach)

View File

@ -39,6 +39,7 @@ from cinder.tests.unit.api.v3 import fakes as v3_fakes
from cinder.tests.unit import cast_as_call
from cinder.tests.unit import fake_constants as fake
from cinder.tests.unit import fake_snapshot
from cinder.tests.unit import fake_volume
from cinder.tests.unit import test
from cinder.tests.unit import utils as test_utils
from cinder.volume import api as volume_api
@ -217,6 +218,77 @@ class AdminActionsTest(BaseAdminTest):
self.assertEqual(fields.VolumeAttachStatus.DETACHED,
volume['attach_status'])
def test_reset_detached_status_to_attached(self):
volume = db.volume_create(self.ctx,
{'status': 'available',
'attach_status':
fields.VolumeAttachStatus.DETACHED,
'volume_type_id': fake.VOLUME_TYPE_ID})
resp = self._issue_volume_reset(self.ctx,
volume,
{'attach_status':
fields.VolumeAttachStatus.ATTACHED})
self.assertEqual(HTTPStatus.ACCEPTED, resp.status_int)
volume = db.volume_get(self.ctx, volume['id'])
self.assertEqual(fields.VolumeAttachStatus.ATTACHED,
volume['attach_status'])
def test_reset_attached_status_to_attached(self):
volume = db.volume_create(self.ctx,
{'status': 'available',
'attach_status':
fields.VolumeAttachStatus.ATTACHED,
'volume_type_id': fake.VOLUME_TYPE_ID})
resp = self._issue_volume_reset(self.ctx,
volume,
{'attach_status':
fields.VolumeAttachStatus.ATTACHED})
self.assertEqual(HTTPStatus.ACCEPTED, resp.status_int)
volume = db.volume_get(self.ctx, volume['id'])
self.assertEqual(fields.VolumeAttachStatus.ATTACHED,
volume['attach_status'])
def test_reset_in_use_to_in_use_fail(self):
volume = db.volume_create(self.ctx,
{'status': 'in-use',
'attach_status':
fields.VolumeAttachStatus.ATTACHED,
'volume_type_id': fake.VOLUME_TYPE_ID})
resp = self._issue_volume_reset(self.ctx,
volume,
{'status': 'in-use'})
self.assertEqual(HTTPStatus.BAD_REQUEST, resp.status_int)
def test_reset_available_to_in_use_on_nonattached_volume_fail(self):
volume = db.volume_create(self.ctx,
{'status': 'available',
'attach_status':
fields.VolumeAttachStatus.DETACHED,
'volume_type_id': fake.VOLUME_TYPE_ID})
resp = self._issue_volume_reset(self.ctx,
volume,
{'status': 'in-use'})
self.assertEqual(HTTPStatus.BAD_REQUEST, resp.status_int)
@mock.patch('cinder.db.volume_attachment_get_all_by_volume_id')
def test_reset_available_to_in_use_on_attached_volume(
self, get_attachment):
volume = db.volume_create(self.ctx,
{'status': 'available',
'attach_status':
fields.VolumeAttachStatus.ATTACHED,
'volume_type_id': fake.VOLUME_TYPE_ID})
resp = self._issue_volume_reset(self.ctx,
volume,
{'status': 'in-use'})
db_attachment = fake_volume.volume_attachment_db_obj()
get_attachment.return_value = [db_attachment]
self.assertEqual(HTTPStatus.ACCEPTED, resp.status_int)
volume = db.volume_get(self.ctx, volume['id'])
self.assertEqual(fields.VolumeAttachStatus.ATTACHED,
volume['attach_status'])
self.assertEqual('in-use', volume['status'])
def test_reset_migration_invalid_status(self):
volume = db.volume_create(self.ctx, {'migration_status': None,
'volume_type_id':