Merge "Disconnecting volume from the compute host"
This commit is contained in:
commit
6230018d65
@ -1572,7 +1572,9 @@ command.
|
|||||||
* - 5
|
* - 5
|
||||||
- Instance state invalid (must be stopped and unlocked)
|
- Instance state invalid (must be stopped and unlocked)
|
||||||
* - 6
|
* - 6
|
||||||
- Instance is not attached to volume
|
- Volume is not attached to the instance
|
||||||
|
* - 7
|
||||||
|
- Connector host is not correct
|
||||||
|
|
||||||
|
|
||||||
Libvirt Commands
|
Libvirt Commands
|
||||||
|
@ -164,18 +164,14 @@ def locked_instance(cell_mapping, instance, reason):
|
|||||||
initial_state = 'locked' if instance.locked else 'unlocked'
|
initial_state = 'locked' if instance.locked else 'unlocked'
|
||||||
if not instance.locked:
|
if not instance.locked:
|
||||||
with context.target_cell(
|
with context.target_cell(
|
||||||
context.get_admin_context(),
|
context.get_admin_context(), cell_mapping) as cctxt:
|
||||||
cell_mapping
|
|
||||||
) as cctxt:
|
|
||||||
compute_api.lock(cctxt, instance, reason=reason)
|
compute_api.lock(cctxt, instance, reason=reason)
|
||||||
try:
|
try:
|
||||||
yield
|
yield
|
||||||
finally:
|
finally:
|
||||||
if initial_state == 'unlocked':
|
if initial_state == 'unlocked':
|
||||||
with context.target_cell(
|
with context.target_cell(
|
||||||
context.get_admin_context(),
|
context.get_admin_context(), cell_mapping) as cctxt:
|
||||||
cell_mapping
|
|
||||||
) as cctxt:
|
|
||||||
compute_api.unlock(cctxt, instance)
|
compute_api.unlock(cctxt, instance)
|
||||||
|
|
||||||
|
|
||||||
@ -3139,8 +3135,15 @@ class VolumeAttachmentCommands(object):
|
|||||||
# TODO(lyarwood): Add delete_attachment as a kwarg to
|
# TODO(lyarwood): Add delete_attachment as a kwarg to
|
||||||
# remove_volume_connection as is available in the private
|
# remove_volume_connection as is available in the private
|
||||||
# method within the manager.
|
# method within the manager.
|
||||||
compute_rpcapi.remove_volume_connection(
|
if instance.host == connector['host']:
|
||||||
cctxt, instance, volume_id, instance.host)
|
compute_rpcapi.remove_volume_connection(
|
||||||
|
cctxt, instance, volume_id, instance.host)
|
||||||
|
else:
|
||||||
|
msg = (
|
||||||
|
f"The compute host '{connector['host']}' in the "
|
||||||
|
f"connector does not match the instance host "
|
||||||
|
f"'{instance.host}'.")
|
||||||
|
raise exception.HostConflict(_(msg))
|
||||||
|
|
||||||
# Delete the existing volume attachment if present in the bdm.
|
# Delete the existing volume attachment if present in the bdm.
|
||||||
# This isn't present when the original attachment was made
|
# This isn't present when the original attachment was made
|
||||||
@ -3222,6 +3225,7 @@ class VolumeAttachmentCommands(object):
|
|||||||
* 4: Instance does not exist.
|
* 4: Instance does not exist.
|
||||||
* 5: Instance state invalid.
|
* 5: Instance state invalid.
|
||||||
* 6: Volume is not attached to instance.
|
* 6: Volume is not attached to instance.
|
||||||
|
* 7: Connector host is not correct.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# TODO(lyarwood): Make this optional and provide a rpcapi capable
|
# TODO(lyarwood): Make this optional and provide a rpcapi capable
|
||||||
@ -3237,6 +3241,12 @@ class VolumeAttachmentCommands(object):
|
|||||||
# Refresh the volume attachment
|
# Refresh the volume attachment
|
||||||
return self._refresh(instance_uuid, volume_id, connector)
|
return self._refresh(instance_uuid, volume_id, connector)
|
||||||
|
|
||||||
|
except exception.HostConflict as e:
|
||||||
|
print(
|
||||||
|
f"The command 'nova-manage volume_attachment get_connector' "
|
||||||
|
f"may have been run on the wrong compute host. Or the "
|
||||||
|
f"instance host may be wrong and in need of repair.\n{e}")
|
||||||
|
return 7
|
||||||
except exception.VolumeBDMNotFound as e:
|
except exception.VolumeBDMNotFound as e:
|
||||||
print(str(e))
|
print(str(e))
|
||||||
return 6
|
return 6
|
||||||
|
@ -2556,3 +2556,7 @@ class EphemeralEncryptionSecretNotFound(Invalid):
|
|||||||
class EphemeralEncryptionCleanupFailed(NovaException):
|
class EphemeralEncryptionCleanupFailed(NovaException):
|
||||||
msg_fmt = _("Failed to clean up ephemeral encryption secrets: "
|
msg_fmt = _("Failed to clean up ephemeral encryption secrets: "
|
||||||
"%(error)s")
|
"%(error)s")
|
||||||
|
|
||||||
|
|
||||||
|
class HostConflict(Exception):
|
||||||
|
pass
|
||||||
|
@ -3642,6 +3642,50 @@ class VolumeAttachmentCommandsTestCase(test.NoDBTestCase):
|
|||||||
mock_action_start.assert_called_once()
|
mock_action_start.assert_called_once()
|
||||||
mock_action.finish.assert_called_once()
|
mock_action.finish.assert_called_once()
|
||||||
|
|
||||||
|
@mock.patch('nova.compute.rpcapi.ComputeAPI', autospec=True)
|
||||||
|
@mock.patch('nova.volume.cinder.API', autospec=True)
|
||||||
|
@mock.patch('nova.compute.api.API', autospec=True)
|
||||||
|
@mock.patch.object(objects.BlockDeviceMapping, 'save')
|
||||||
|
@mock.patch.object(
|
||||||
|
objects.BlockDeviceMapping, 'get_by_volume_and_instance')
|
||||||
|
@mock.patch.object(objects.Instance, 'get_by_uuid')
|
||||||
|
@mock.patch.object(objects.InstanceAction, 'action_start')
|
||||||
|
def test_refresh_invalid_connector_host(
|
||||||
|
self, mock_action_start, mock_get_instance,
|
||||||
|
mock_get_bdm, mock_save_bdm, mock_compute_api, mock_volume_api,
|
||||||
|
mock_compute_rpcapi
|
||||||
|
):
|
||||||
|
"""Test refresh with a old host not disconnected properly
|
||||||
|
and connector host info is not correct, a fake-host is
|
||||||
|
passed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
fake_volume_api = mock_volume_api.return_value
|
||||||
|
device_name = '/dev/vda'
|
||||||
|
|
||||||
|
mock_get_instance.return_value = objects.Instance(
|
||||||
|
uuid=uuidsentinel.instance,
|
||||||
|
vm_state=obj_fields.InstanceState.STOPPED,
|
||||||
|
host='old-host', locked=False)
|
||||||
|
mock_get_bdm.return_value = objects.BlockDeviceMapping(
|
||||||
|
uuid=uuidsentinel.bdm, volume_id=uuidsentinel.volume,
|
||||||
|
attachment_id=uuidsentinel.instance,
|
||||||
|
device_name=device_name)
|
||||||
|
mock_action = mock.Mock(spec=objects.InstanceAction)
|
||||||
|
mock_action_start.return_value = mock_action
|
||||||
|
|
||||||
|
fake_volume_api.attachment_create.return_value = {
|
||||||
|
'id': uuidsentinel.new_attachment,
|
||||||
|
}
|
||||||
|
# in instance we have host as 'old-host'
|
||||||
|
# but here 'fake-host' is passed in connector info.
|
||||||
|
fake_volume_api.attachment_update.return_value = {
|
||||||
|
'connection_info': self._get_fake_connector_info(),
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = self._test_refresh()
|
||||||
|
self.assertEqual(7, ret)
|
||||||
|
|
||||||
@mock.patch('nova.compute.rpcapi.ComputeAPI', autospec=True)
|
@mock.patch('nova.compute.rpcapi.ComputeAPI', autospec=True)
|
||||||
@mock.patch('nova.volume.cinder.API', autospec=True)
|
@mock.patch('nova.volume.cinder.API', autospec=True)
|
||||||
@mock.patch('nova.compute.api.API', autospec=True)
|
@mock.patch('nova.compute.api.API', autospec=True)
|
||||||
@ -3664,7 +3708,7 @@ class VolumeAttachmentCommandsTestCase(test.NoDBTestCase):
|
|||||||
mock_get_instance.return_value = objects.Instance(
|
mock_get_instance.return_value = objects.Instance(
|
||||||
uuid=uuidsentinel.instance,
|
uuid=uuidsentinel.instance,
|
||||||
vm_state=obj_fields.InstanceState.STOPPED,
|
vm_state=obj_fields.InstanceState.STOPPED,
|
||||||
host='foo', locked=False)
|
host='fake-host', locked=False)
|
||||||
mock_get_bdm.return_value = objects.BlockDeviceMapping(
|
mock_get_bdm.return_value = objects.BlockDeviceMapping(
|
||||||
uuid=uuidsentinel.bdm, volume_id=uuidsentinel.volume,
|
uuid=uuidsentinel.bdm, volume_id=uuidsentinel.volume,
|
||||||
attachment_id=uuidsentinel.instance,
|
attachment_id=uuidsentinel.instance,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user