Handle port delete initiated by neutron
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
This commit is contained in:
parent
a8e51e0e74
commit
d3e9ddfb7f
|
@ -76,6 +76,7 @@ from nova import image
|
|||
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
|
||||
|
@ -6228,6 +6229,33 @@ class ComputeManager(manager.Manager):
|
|||
{'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
|
||||
|
@ -6242,6 +6270,10 @@ class ComputeManager(manager.Manager):
|
|||
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)
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ EVENT_NAMES = [
|
|||
# VIF plugging notifications, tag is port_id
|
||||
'network-vif-plugged',
|
||||
'network-vif-unplugged',
|
||||
'network-vif-deleted',
|
||||
|
||||
]
|
||||
|
||||
|
@ -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(),
|
||||
|
|
|
@ -47,6 +47,7 @@ from nova import test
|
|||
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
|
||||
|
@ -1744,27 +1745,107 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase):
|
|||
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):
|
||||
|
|
|
@ -1118,7 +1118,7 @@ object_data = {
|
|||
'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',
|
||||
|
|
Loading…
Reference in New Issue