Skip to content

Commit c534be7

Browse files
committed
Disconnecting volume from the compute host
cmd nova-manage volume_attachment refresh vm-id vol-id connetor There were cases where the instance said to live in compute#1 but the connection_info in the BDM record was for compute#2, and when the script called `remove_volume_connection` then nova would call os-brick on compute#1 (the wrong node) and try to detach it. In some case os-brick would mistakenly think that the volume was attached (because the target and lun matched an existing volume on the host) and would try to disconnect, resulting in errors on the compute logs. - Added HostConflict exception - Fixes dedent in cmd/manange.py - Updates nova-manage doc Conflicts: stable/2023.2 - nova/exception.py changes: (I052441076c677c0fe76a8d9421af70b0ffa1d400) Removed 2 new EphemeralEncryption exceptions as feature is not backportable. Closes-Bug: #2012365 Change-Id: I21109752ff1c56d3cefa58fcd36c68bf468e0a73 (cherry picked from commit a8f81d5) (cherry picked from commit ac45029)
1 parent fc54bc9 commit c534be7

File tree

4 files changed

+70
-10
lines changed

4 files changed

+70
-10
lines changed

doc/source/cli/nova-manage.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1531,7 +1531,9 @@ command.
15311531
* - 5
15321532
- Instance state invalid (must be stopped and unlocked)
15331533
* - 6
1534-
- Instance is not attached to volume
1534+
- Volume is not attached to the instance
1535+
* - 7
1536+
- Connector host is not correct
15351537

15361538

15371539
Libvirt Commands

nova/cmd/manage.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -161,18 +161,14 @@ def locked_instance(cell_mapping, instance, reason):
161161
initial_state = 'locked' if instance.locked else 'unlocked'
162162
if not instance.locked:
163163
with context.target_cell(
164-
context.get_admin_context(),
165-
cell_mapping
166-
) as cctxt:
164+
context.get_admin_context(), cell_mapping) as cctxt:
167165
compute_api.lock(cctxt, instance, reason=reason)
168166
try:
169167
yield
170168
finally:
171169
if initial_state == 'unlocked':
172170
with context.target_cell(
173-
context.get_admin_context(),
174-
cell_mapping
175-
) as cctxt:
171+
context.get_admin_context(), cell_mapping) as cctxt:
176172
compute_api.unlock(cctxt, instance)
177173

178174

@@ -3078,8 +3074,15 @@ def _do_refresh(self, cctxt, instance,
30783074
# TODO(lyarwood): Add delete_attachment as a kwarg to
30793075
# remove_volume_connection as is available in the private
30803076
# method within the manager.
3081-
compute_rpcapi.remove_volume_connection(
3082-
cctxt, instance, volume_id, instance.host)
3077+
if instance.host == connector['host']:
3078+
compute_rpcapi.remove_volume_connection(
3079+
cctxt, instance, volume_id, instance.host)
3080+
else:
3081+
msg = (
3082+
f"The compute host '{connector['host']}' in the "
3083+
f"connector does not match the instance host "
3084+
f"'{instance.host}'.")
3085+
raise exception.HostConflict(_(msg))
30833086

30843087
# Delete the existing volume attachment if present in the bdm.
30853088
# This isn't present when the original attachment was made
@@ -3161,6 +3164,7 @@ def refresh(self, instance_uuid=None, volume_id=None, connector_path=None):
31613164
* 4: Instance does not exist.
31623165
* 5: Instance state invalid.
31633166
* 6: Volume is not attached to instance.
3167+
* 7: Connector host is not correct.
31643168
"""
31653169
try:
31663170
# TODO(lyarwood): Make this optional and provide a rpcapi capable
@@ -3176,6 +3180,12 @@ def refresh(self, instance_uuid=None, volume_id=None, connector_path=None):
31763180
# Refresh the volume attachment
31773181
return self._refresh(instance_uuid, volume_id, connector)
31783182

3183+
except exception.HostConflict as e:
3184+
print(
3185+
f"The command 'nova-manage volume_attachment get_connector' "
3186+
f"may have been run on the wrong compute host. Or the "
3187+
f"instance host may be wrong and in need of repair.\n{e}")
3188+
return 7
31793189
except exception.VolumeBDMNotFound as e:
31803190
print(str(e))
31813191
return 6

nova/exception.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2530,3 +2530,7 @@ class NotSupportedComputeForEvacuateV295(NotSupported):
25302530
"instance on destination. To evacuate before upgrades are "
25312531
"complete please use an older microversion. Required version "
25322532
"for compute %(expected), current version %(currently)s")
2533+
2534+
2535+
class HostConflict(Exception):
2536+
pass

nova/tests/unit/cmd/test_manage.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3622,6 +3622,50 @@ def test_refresh_attachment_unknown_failure(
36223622
mock_action_start.assert_called_once()
36233623
mock_action.finish.assert_called_once()
36243624

3625+
@mock.patch('nova.compute.rpcapi.ComputeAPI', autospec=True)
3626+
@mock.patch('nova.volume.cinder.API', autospec=True)
3627+
@mock.patch('nova.compute.api.API', autospec=True)
3628+
@mock.patch.object(objects.BlockDeviceMapping, 'save')
3629+
@mock.patch.object(
3630+
objects.BlockDeviceMapping, 'get_by_volume_and_instance')
3631+
@mock.patch.object(objects.Instance, 'get_by_uuid')
3632+
@mock.patch.object(objects.InstanceAction, 'action_start')
3633+
def test_refresh_invalid_connector_host(
3634+
self, mock_action_start, mock_get_instance,
3635+
mock_get_bdm, mock_save_bdm, mock_compute_api, mock_volume_api,
3636+
mock_compute_rpcapi
3637+
):
3638+
"""Test refresh with a old host not disconnected properly
3639+
and connector host info is not correct, a fake-host is
3640+
passed.
3641+
"""
3642+
3643+
fake_volume_api = mock_volume_api.return_value
3644+
device_name = '/dev/vda'
3645+
3646+
mock_get_instance.return_value = objects.Instance(
3647+
uuid=uuidsentinel.instance,
3648+
vm_state=obj_fields.InstanceState.STOPPED,
3649+
host='old-host', locked=False)
3650+
mock_get_bdm.return_value = objects.BlockDeviceMapping(
3651+
uuid=uuidsentinel.bdm, volume_id=uuidsentinel.volume,
3652+
attachment_id=uuidsentinel.instance,
3653+
device_name=device_name)
3654+
mock_action = mock.Mock(spec=objects.InstanceAction)
3655+
mock_action_start.return_value = mock_action
3656+
3657+
fake_volume_api.attachment_create.return_value = {
3658+
'id': uuidsentinel.new_attachment,
3659+
}
3660+
# in instance we have host as 'old-host'
3661+
# but here 'fake-host' is passed in connector info.
3662+
fake_volume_api.attachment_update.return_value = {
3663+
'connection_info': self._get_fake_connector_info(),
3664+
}
3665+
3666+
ret = self._test_refresh()
3667+
self.assertEqual(7, ret)
3668+
36253669
@mock.patch('nova.compute.rpcapi.ComputeAPI', autospec=True)
36263670
@mock.patch('nova.volume.cinder.API', autospec=True)
36273671
@mock.patch('nova.compute.api.API', autospec=True)
@@ -3644,7 +3688,7 @@ def test_refresh(
36443688
mock_get_instance.return_value = objects.Instance(
36453689
uuid=uuidsentinel.instance,
36463690
vm_state=obj_fields.InstanceState.STOPPED,
3647-
host='foo', locked=False)
3691+
host='fake-host', locked=False)
36483692
mock_get_bdm.return_value = objects.BlockDeviceMapping(
36493693
uuid=uuidsentinel.bdm, volume_id=uuidsentinel.volume,
36503694
attachment_id=uuidsentinel.instance,

0 commit comments

Comments
 (0)