Skip to content

Commit 2bee83b

Browse files
felixhuettnerlyarwood
authored andcommitted
compute: Avoid duplicate BDMs during reserve_block_device_name
When attaching a volume to a running instance the nova-api validates that the volume is not already attached to the instance. However nova-compute is responsible for actually creating the BDM entry in the database. If sending attach requests fast enough it can be possible that the same "attach_volume" request can be sent to nova-compute for the same volume/instance combination. To work around this we add a check in nova-compute to validate that the volume has not been attached in the mean time. Closes-Bug: #1937375 Change-Id: I92f35514efddcb071c7094370b79d91d34c5bc72 (cherry picked from commit 2209b00)
1 parent 7a9e3dc commit 2bee83b

File tree

3 files changed

+41
-6
lines changed

3 files changed

+41
-6
lines changed

nova/compute/manager.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6979,6 +6979,12 @@ def do_reserve():
69796979
objects.BlockDeviceMappingList.get_by_instance_uuid(
69806980
context, instance.uuid))
69816981

6982+
# Now that we have the lock check that we haven't raced another
6983+
# request and ensure there is no existing attachment
6984+
if any(b for b in bdms if b.volume_id == volume_id):
6985+
msg = _("volume %s already attached") % volume_id
6986+
raise exception.InvalidVolume(reason=msg)
6987+
69826988
# NOTE(ndipanov): We need to explicitly set all the fields on the
69836989
# object so that obj_load_attr does not fail
69846990
new_bdm = objects.BlockDeviceMapping(

nova/tests/functional/regressions/test_bug_1937375.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import time
1717

1818
from nova import context
19+
from nova import exception
1920
from nova import objects
2021
from nova.tests.functional import integrated_helpers
2122

@@ -68,7 +69,12 @@ def wrap_reserve_block_device_name(*args, **kwargs):
6869
# twice to mimic two callers racing each other after the checks on
6970
# the api.
7071
original_bdm = original_reserve_name(*args, **kwargs)
71-
original_reserve_name(*args, **kwargs)
72+
73+
# Assert that a repeat call fails as an attachment already exists
74+
self.assertRaises(
75+
exception.InvalidVolume,
76+
original_reserve_name, *args, **kwargs)
77+
7278
return original_bdm
7379

7480
with mock.patch.object(
@@ -86,10 +92,7 @@ def wrap_reserve_block_device_name(*args, **kwargs):
8692
bdms = objects.BlockDeviceMappingList.get_by_instance_uuid(
8793
ctxt, server_id)
8894

89-
# FIXME(lyarwood): This is bug #1937375, we now have 3 bdms for the
90-
# instance, the original root disk and two duplicate volume bdms for
91-
# the same volume attachment.
92-
self.assertEqual(3, len(bdms))
93-
self.assertEqual(volume_id, bdms[2].volume_id)
95+
# Assert that the correct bdms are present
96+
self.assertEqual(2, len(bdms))
9497
self.assertEqual(volume_id, bdms[1].volume_id)
9598
self.assertEqual('local', bdms[0].destination_type)

nova/tests/unit/compute/test_compute_mgr.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,32 @@ def test_reserve_block_device_name_multiattach_raises(self, _):
614614
'fake_device', 'fake_volume_id', 'fake_disk_bus',
615615
'fake_device_type', tag=None, multiattach=True)
616616

617+
@mock.patch.object(compute_utils, 'add_instance_fault_from_exc',
618+
new=mock.Mock())
619+
@mock.patch.object(objects.BlockDeviceMapping, 'create', new=mock.Mock())
620+
@mock.patch.object(objects.BlockDeviceMappingList, 'get_by_instance_uuid')
621+
def test_reserve_block_device_name_raises_on_duplicate(self, mock_get):
622+
instance = fake_instance.fake_instance_obj(self.context)
623+
vol_bdm = objects.BlockDeviceMapping(
624+
self.context,
625+
id=1,
626+
instance_uuid=instance.uuid,
627+
volume_id="myinstanceuuid",
628+
source_type='volume',
629+
destination_type='volume',
630+
delete_on_termination=True,
631+
connection_info=None,
632+
tag='fake-tag',
633+
device_name='/dev/fake0',
634+
attachment_id=uuids.attachment_id)
635+
mock_get.return_value = objects.BlockDeviceMappingList(
636+
objects=[vol_bdm])
637+
638+
self.assertRaises(exception.InvalidVolume,
639+
self.compute.reserve_block_device_name,
640+
self.context, instance, None, "myinstanceuuid",
641+
None, None, 'foo', False)
642+
617643
@mock.patch.object(objects.Instance, 'save')
618644
@mock.patch.object(time, 'sleep')
619645
def test_allocate_network_succeeds_after_retries(

0 commit comments

Comments
 (0)