Skip to content

Commit 295d202

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Add a lock to prevent race during detach/attach of interface"
2 parents e91c755 + 39831c5 commit 295d202

File tree

4 files changed

+56
-7
lines changed

4 files changed

+56
-7
lines changed

nova/compute/manager.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7504,6 +7504,19 @@ def _deallocate_port_for_instance(self, context, instance, port_id,
75047504
def attach_interface(self, context, instance, network_id, port_id,
75057505
requested_ip, tag):
75067506
"""Use hotplug to add an network adapter to an instance."""
7507+
lockname = 'interface-%s-%s' % (instance.uuid, port_id)
7508+
7509+
@utils.synchronized(lockname)
7510+
def do_attach_interface(context, instance, network_id, port_id,
7511+
requested_ip, tag):
7512+
return self._attach_interface(context, instance, network_id,
7513+
port_id, requested_ip, tag)
7514+
7515+
return do_attach_interface(context, instance, network_id, port_id,
7516+
requested_ip, tag)
7517+
7518+
def _attach_interface(self, context, instance, network_id, port_id,
7519+
requested_ip, tag):
75077520
if not self.driver.capabilities.get('supports_attach_interface',
75087521
False):
75097522
raise exception.AttachInterfaceNotSupported(
@@ -7563,6 +7576,18 @@ def attach_interface(self, context, instance, network_id, port_id,
75637576
@wrap_instance_fault
75647577
def detach_interface(self, context, instance, port_id):
75657578
"""Detach a network adapter from an instance."""
7579+
lockname = 'interface-%s-%s' % (instance.uuid, port_id)
7580+
7581+
@utils.synchronized(lockname)
7582+
def do_detach_interface(context, instance, port_id):
7583+
self._detach_interface(context, instance, port_id)
7584+
7585+
do_detach_interface(context, instance, port_id)
7586+
7587+
def _detach_interface(self, context, instance, port_id):
7588+
# NOTE(aarents): we need to refresh info cache from DB here,
7589+
# as previous detach/attach lock holder just updated it.
7590+
compute_utils.refresh_info_cache_for_instance(context, instance)
75667591
network_info = instance.info_cache.network_info
75677592
condemned = None
75687593
for vif in network_info:

nova/tests/unit/compute/test_compute.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10284,11 +10284,15 @@ def test_attach_interface(self, mock_notify):
1028410284
network_id = nwinfo[0]['network']['id']
1028510285
port_id = nwinfo[0]['id']
1028610286
req_ip = '1.2.3.4'
10287+
lock_name = 'interface-%s-%s' % (instance.uuid, port_id)
1028710288
mock_allocate = mock.Mock(return_value=nwinfo)
1028810289
self.compute.network_api.allocate_port_for_instance = mock_allocate
1028910290

10290-
with mock.patch.dict(self.compute.driver.capabilities,
10291-
supports_attach_interface=True):
10291+
with test.nested(
10292+
mock.patch.dict(self.compute.driver.capabilities,
10293+
supports_attach_interface=True),
10294+
mock.patch('oslo_concurrency.lockutils.lock')
10295+
) as (cap, mock_lock):
1029210296
vif = self.compute.attach_interface(self.context,
1029310297
instance,
1029410298
network_id,
@@ -10303,6 +10307,9 @@ def test_attach_interface(self, mock_notify):
1030310307
action='interface_attach', phase='start'),
1030410308
mock.call(self.context, instance, self.compute.host,
1030510309
action='interface_attach', phase='end')])
10310+
mock_lock.assert_called_once_with(lock_name, mock.ANY, mock.ANY,
10311+
mock.ANY, delay=mock.ANY, do_log=mock.ANY, fair=mock.ANY,
10312+
semaphores=mock.ANY)
1030610313
return nwinfo, port_id
1030710314

1030810315
@mock.patch.object(compute_utils, 'notify_about_instance_action')
@@ -10400,6 +10407,7 @@ def test_detach_interface(self, mock_notify):
1040010407
self.context, uuids.info_cache_instance)
1040110408
instance.info_cache.network_info = network_model.NetworkInfo.hydrate(
1040210409
nwinfo)
10410+
lock_name = 'interface-%s-%s' % (instance.uuid, port_id)
1040310411

1040410412
port_allocation = {uuids.rp1: {'NET_BW_EGR_KILOBIT_PER_SEC': 10000}}
1040510413
with test.nested(
@@ -10408,8 +10416,9 @@ def test_detach_interface(self, mock_notify):
1040810416
'remove_resources_from_instance_allocation'),
1040910417
mock.patch.object(self.compute.network_api,
1041010418
'deallocate_port_for_instance',
10411-
return_value=([], port_allocation))) as (
10412-
mock_remove_alloc, mock_deallocate):
10419+
return_value=([], port_allocation)),
10420+
mock.patch('oslo_concurrency.lockutils.lock')) as (
10421+
mock_remove_alloc, mock_deallocate, mock_lock):
1041310422
self.compute.detach_interface(self.context, instance, port_id)
1041410423

1041510424
mock_deallocate.assert_called_once_with(
@@ -10423,6 +10432,9 @@ def test_detach_interface(self, mock_notify):
1042310432
action='interface_detach', phase='start'),
1042410433
mock.call(self.context, instance, self.compute.host,
1042510434
action='interface_detach', phase='end')])
10435+
mock_lock.assert_called_once_with(lock_name, mock.ANY, mock.ANY,
10436+
mock.ANY, delay=mock.ANY, do_log=mock.ANY, fair=mock.ANY,
10437+
semaphores=mock.ANY)
1042610438

1042710439
@mock.patch('nova.compute.manager.LOG.log')
1042810440
def test_detach_interface_failed(self, mock_log):

nova/tests/unit/compute/test_compute_mgr.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2586,8 +2586,9 @@ def test_detach_interface_failure(self):
25862586

25872587
@mock.patch.object(compute_utils, 'EventReporter')
25882588
@mock.patch.object(compute_utils, 'add_instance_fault_from_exc')
2589+
@mock.patch.object(compute_utils, 'refresh_info_cache_for_instance')
25892590
@mock.patch.object(self.compute, '_set_instance_obj_error_state')
2590-
def do_test(meth, add_fault, event):
2591+
def do_test(meth, refresh, add_fault, event):
25912592
self.assertRaises(exception.PortNotFound,
25922593
self.compute.detach_interface,
25932594
self.context, f_instance, 'port_id')
@@ -2596,15 +2597,17 @@ def do_test(meth, add_fault, event):
25962597
event.assert_called_once_with(
25972598
self.context, 'compute_detach_interface', CONF.host,
25982599
f_instance.uuid, graceful_exit=False)
2600+
refresh.assert_called_once_with(self.context, f_instance)
25992601

26002602
do_test()
26012603

26022604
@mock.patch('nova.compute.manager.LOG.log')
26032605
@mock.patch.object(compute_utils, 'EventReporter')
26042606
@mock.patch.object(compute_utils, 'add_instance_fault_from_exc')
26052607
@mock.patch.object(compute_utils, 'notify_about_instance_action')
2606-
def test_detach_interface_instance_not_found(self, mock_notify, mock_fault,
2607-
mock_event, mock_log):
2608+
@mock.patch.object(compute_utils, 'refresh_info_cache_for_instance')
2609+
def test_detach_interface_instance_not_found(self, mock_refresh,
2610+
mock_notify, mock_fault, mock_event, mock_log):
26082611
nw_info = network_model.NetworkInfo([
26092612
network_model.VIF(uuids.port_id)])
26102613
info_cache = objects.InstanceInfoCache(network_info=nw_info,
@@ -2617,6 +2620,7 @@ def test_detach_interface_instance_not_found(self, mock_notify, mock_fault,
26172620
self.assertRaises(exception.InterfaceDetachFailed,
26182621
self.compute.detach_interface,
26192622
self.context, instance, uuids.port_id)
2623+
mock_refresh.assert_called_once_with(self.context, instance)
26202624
self.assertEqual(1, mock_log.call_count)
26212625
self.assertEqual(logging.DEBUG, mock_log.call_args[0][0])
26222626

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
fixes:
3+
- |
4+
Resolve a race condition that may occur during concurrent
5+
``interface detach/attach``, resulting in an interface accidentally unbind
6+
after attached. See `bug 1892870`_ for more details.
7+
8+
.. _bug 1892870: https://bugs.launchpad.net/nova/+bug/1892870

0 commit comments

Comments
 (0)