Skip to content

Commit

Permalink
Handle port delete initiated by neutron
Browse files Browse the repository at this point in the history
Neutron can delete a port without Nova being aware of it.
https://review.openstack.org/#/c/187871/ adds an event to notify nova
when a port is deleted. This patch will process the event and detach
the interface.

Change-Id: I998b6bb80cc0a81d665b61b8c4a424d7219c666f
Related-Bug: #1448148
Related-Bug: #1333365
Related-Bug: #1462366
  • Loading branch information
Robert Li committed Aug 12, 2015
1 parent a8e51e0 commit d3e9ddf
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 5 deletions.
32 changes: 32 additions & 0 deletions nova/compute/manager.py
Expand Up @@ -76,6 +76,7 @@
from nova.image import glance
from nova import manager
from nova import network
from nova.network import base_api as base_net_api
from nova.network import model as network_model
from nova.network.security_group import openstack_driver
from nova import objects
Expand Down Expand Up @@ -6228,6 +6229,33 @@ def _process_instance_event(self, instance, event):
{'event': event.key}, instance=instance)
_event.send(event)

def _process_instance_vif_deleted_event(self, context, instance,
deleted_vif_id):
# If an attached port is deleted by neutron, it needs to
# be detached from the instance.
# And info cache needs to be updated.
network_info = instance.info_cache.network_info
for index, vif in enumerate(network_info):
if vif['id'] == deleted_vif_id:
LOG.info(_LI('Neutron deleted interface %(intf)s; '
'detaching it from the instance and '
'deleting it from the info cache'),
{'intf': vif['id']},
instance=instance)
del network_info[index]
base_net_api.update_instance_cache_with_nw_info(
self.network_api, context,
instance,
nw_info=network_info)
try:
self.driver.detach_interface(instance, vif)
except exception.NovaException as ex:
LOG.warning(_LW("Detach interface failed, "
"port_id=%(port_id)s, reason: %(msg)s"),
{'port_id': deleted_vif_id, 'msg': ex},
instance=instance)
break

@wrap_exception()
def external_instance_event(self, context, instances, events):
# NOTE(danms): Some event types are handled by the manager, such
Expand All @@ -6242,6 +6270,10 @@ def external_instance_event(self, context, instances, events):
instance=instance)
if event.name == 'network-changed':
self.network_api.get_instance_nw_info(context, instance)
elif event.name == 'network-vif-deleted':
self._process_instance_vif_deleted_event(context,
instance,
event.tag)
else:
self._process_instance_event(instance, event)

Expand Down
4 changes: 3 additions & 1 deletion nova/objects/external_event.py
Expand Up @@ -22,6 +22,7 @@
# VIF plugging notifications, tag is port_id
'network-vif-plugged',
'network-vif-unplugged',
'network-vif-deleted',

]

Expand All @@ -34,7 +35,8 @@ class InstanceExternalEvent(obj_base.NovaObject,
obj_base.NovaObjectDictCompat):
# Version 1.0: Initial version
# Supports network-changed and vif-plugged
VERSION = '1.0'
# Version 1.1: adds network-vif-deleted event
VERSION = '1.1'

fields = {
'instance_uuid': fields.UUIDField(),
Expand Down
87 changes: 84 additions & 3 deletions nova/tests/unit/compute/test_compute_mgr.py
Expand Up @@ -47,6 +47,7 @@
from nova.tests.unit.compute import fake_resource_tracker
from nova.tests.unit import fake_block_device
from nova.tests.unit import fake_instance
from nova.tests.unit import fake_network_cache_model
from nova.tests.unit import fake_server_actions
from nova.tests.unit.objects import test_instance_fault
from nova.tests.unit.objects import test_instance_info_cache
Expand Down Expand Up @@ -1744,27 +1745,107 @@ def test_process_instance_event(self):
self.assertEqual(event_obj, event.wait())
self.assertEqual({}, self.compute.instance_events._events)

def test_process_instance_vif_deleted_event(self):
vif1 = fake_network_cache_model.new_vif()
vif1['id'] = '1'
vif2 = fake_network_cache_model.new_vif()
vif2['id'] = '2'
nw_info = network_model.NetworkInfo([vif1, vif2])
info_cache = objects.InstanceInfoCache(network_info=nw_info,
instance_uuid='uuid')
inst_obj = objects.Instance(id=3, uuid='uuid', info_cache=info_cache)

@mock.patch.object(manager.base_net_api,
'update_instance_cache_with_nw_info')
@mock.patch.object(self.compute.driver, 'detach_interface')
def do_test(detach_interface, update_instance_cache_with_nw_info):
self.compute._process_instance_vif_deleted_event(self.context,
inst_obj,
vif2['id'])
update_instance_cache_with_nw_info.assert_called_once_with(
self.compute.network_api,
self.context,
inst_obj,
nw_info=[vif1])
detach_interface.assert_called_once_with(inst_obj, vif2)
do_test()

def test_external_instance_event(self):
instances = [
objects.Instance(id=1, uuid='uuid1'),
objects.Instance(id=2, uuid='uuid2')]
objects.Instance(id=2, uuid='uuid2'),
objects.Instance(id=3, uuid='uuid3')]
events = [
objects.InstanceExternalEvent(name='network-changed',
tag='tag1',
instance_uuid='uuid1'),
objects.InstanceExternalEvent(name='network-vif-plugged',
instance_uuid='uuid2',
tag='tag2')]
tag='tag2'),
objects.InstanceExternalEvent(name='network-vif-deleted',
instance_uuid='uuid3',
tag='tag3')]

@mock.patch.object(self.compute, '_process_instance_vif_deleted_event')
@mock.patch.object(self.compute.network_api, 'get_instance_nw_info')
@mock.patch.object(self.compute, '_process_instance_event')
def do_test(_process_instance_event, get_instance_nw_info):
def do_test(_process_instance_event, get_instance_nw_info,
_process_instance_vif_deleted_event):
self.compute.external_instance_event(self.context,
instances, events)
get_instance_nw_info.assert_called_once_with(self.context,
instances[0])
_process_instance_event.assert_called_once_with(instances[1],
events[1])
_process_instance_vif_deleted_event.assert_called_once_with(
self.context, instances[2], events[2].tag)
do_test()

def test_external_instance_event_with_exception(self):
vif1 = fake_network_cache_model.new_vif()
vif1['id'] = '1'
vif2 = fake_network_cache_model.new_vif()
vif2['id'] = '2'
nw_info = network_model.NetworkInfo([vif1, vif2])
info_cache = objects.InstanceInfoCache(network_info=nw_info,
instance_uuid='uuid2')
instances = [
objects.Instance(id=1, uuid='uuid1'),
objects.Instance(id=2, uuid='uuid2', info_cache=info_cache),
objects.Instance(id=3, uuid='uuid3')]
events = [
objects.InstanceExternalEvent(name='network-changed',
tag='tag1',
instance_uuid='uuid1'),
objects.InstanceExternalEvent(name='network-vif-deleted',
instance_uuid='uuid2',
tag='2'),
objects.InstanceExternalEvent(name='network-vif-plugged',
instance_uuid='uuid3',
tag='tag3')]

# Make sure all the three events are handled despite the exception in
# processing event 2
@mock.patch.object(manager.base_net_api,
'update_instance_cache_with_nw_info')
@mock.patch.object(self.compute.driver, 'detach_interface',
side_effect=exception.NovaException)
@mock.patch.object(self.compute.network_api, 'get_instance_nw_info')
@mock.patch.object(self.compute, '_process_instance_event')
def do_test(_process_instance_event, get_instance_nw_info,
detach_interface, update_instance_cache_with_nw_info):
self.compute.external_instance_event(self.context,
instances, events)
get_instance_nw_info.assert_called_once_with(self.context,
instances[0])
update_instance_cache_with_nw_info.assert_called_once_with(
self.compute.network_api,
self.context,
instances[1],
nw_info=[vif1])
detach_interface.assert_called_once_with(instances[1], vif2)
_process_instance_event.assert_called_once_with(instances[2],
events[2])
do_test()

def test_cancel_all_events(self):
Expand Down
2 changes: 1 addition & 1 deletion nova/tests/unit/objects/test_objects.py
Expand Up @@ -1118,7 +1118,7 @@ def test_serialize_args(self):
'InstanceActionEvent': '1.1-e56a64fa4710e43ef7af2ad9d6028b33',
'InstanceActionEventList': '1.1-13d92fb953030cdbfee56481756e02be',
'InstanceActionList': '1.0-4a53826625cc280e15fae64a575e0879',
'InstanceExternalEvent': '1.0-33cc4a1bbd0655f68c0ee791b95da7e6',
'InstanceExternalEvent': '1.1-6e446ceaae5f475ead255946dd443417',
'InstanceFault': '1.2-7ef01f16f1084ad1304a513d6d410a38',
'InstanceFaultList': '1.1-f8ec07cbe3b60f5f07a8b7a06311ac0d',
'InstanceGroup': '1.9-a413a4ec0ff391e3ef0faa4e3e2a96d0',
Expand Down

0 comments on commit d3e9ddf

Please sign in to comment.