Send custom vif plug/unplug event

The main vif plug/unplug flows now push a custom Event through PowerVM
including the MAC address of the vif.  This Event is intended to be
consumed by the PowerVM neutron agents as a lightweight means of knowing
when they should update the corresponding neutron port's status.

The agents must still account for missed events.

Change-Id: I4ba159071f633b2817939de036bca378ead110a2
This commit is contained in:
Eric Fried 2016-09-20 13:57:03 -05:00
parent 576729f500
commit 4fcf6cfb1e
2 changed files with 85 additions and 14 deletions

View File

@ -45,8 +45,34 @@ class TestVifFunctions(test.TestCase):
traits=pvm_fx.LocalPVMTraits)).adpt
self.slot_mgr = mock.Mock()
@mock.patch('oslo_serialization.jsonutils.dumps')
@mock.patch('pypowervm.wrappers.event.Event')
def test_push_vif_event(self, mock_event, mock_dumps):
mock_vif = mock.Mock(mac='MAC', href='HREF')
vif._push_vif_event(self.adpt, 'action', mock_vif)
mock_dumps.assert_called_once_with(
{'provider': 'NOVA_PVM_VIF', 'action': 'action', 'mac': 'MAC'})
mock_event.bld.assert_called_once_with(self.adpt, 'HREF',
mock_dumps.return_value)
mock_event.bld.return_value.create.assert_called_once_with()
mock_dumps.reset_mock()
mock_event.bld.reset_mock()
mock_event.bld.return_value.create.reset_mock()
# Exception reraises
mock_event.bld.return_value.create.side_effect = IndexError
self.assertRaises(IndexError, vif._push_vif_event, self.adpt, 'action',
mock_vif)
mock_dumps.assert_called_once_with(
{'provider': 'NOVA_PVM_VIF', 'action': 'action', 'mac': 'MAC'})
mock_event.bld.assert_called_once_with(self.adpt, 'HREF',
mock_dumps.return_value)
mock_event.bld.return_value.create.assert_called_once_with()
@mock.patch('nova_powervm.virt.powervm.vif._build_vif_driver')
def test_plug(self, mock_bld_drv):
@mock.patch('nova_powervm.virt.powervm.vif._push_vif_event')
def test_plug(self, mock_event, mock_bld_drv):
"""Test the top-level plug method."""
mock_vif = {'address': 'MAC'}
slot_mgr = mock.Mock()
@ -62,6 +88,7 @@ class TestVifFunctions(test.TestCase):
new_vif=True)
slot_mgr.register_vnet.assert_called_once_with(
mock_bld_drv.return_value.plug.return_value)
mock_event.assert_called_once_with(self.adpt, 'plug', vnet)
self.assertEqual(mock_bld_drv.return_value.plug.return_value, vnet)
# Clean up
@ -69,9 +96,12 @@ class TestVifFunctions(test.TestCase):
slot_mgr.build_map.get_vnet_slot.reset_mock()
mock_bld_drv.return_value.plug.reset_mock()
slot_mgr.register_vnet.reset_mock()
mock_event.reset_mock()
# 2) Without slot registration
# 2) Without slot registration; and plug returns None (which it should
# IRL whenever new_vif=False).
slot_mgr.build_map.get_vnet_slot.return_value = 123
mock_bld_drv.return_value.plug.return_value = None
vnet = vif.plug(self.adpt, 'host_uuid', 'instance', mock_vif, slot_mgr,
new_vif=False)
@ -81,36 +111,43 @@ class TestVifFunctions(test.TestCase):
mock_bld_drv.return_value.plug.assert_called_once_with(mock_vif, 123,
new_vif=False)
slot_mgr.register_vnet.assert_not_called()
self.assertEqual(mock_bld_drv.return_value.plug.return_value, vnet)
mock_event.assert_not_called()
self.assertIsNone(vnet)
@mock.patch('nova_powervm.virt.powervm.vif._build_vif_driver')
def test_unplug(self, mock_bld_drv):
@mock.patch('nova_powervm.virt.powervm.vif._push_vif_event')
def test_unplug(self, mock_event, mock_bld_drv):
"""Test the top-level unplug method."""
mock_vif = {'address': 'MAC'}
slot_mgr = mock.Mock()
# 1) With slot deregistration, default cna_w_list
mock_bld_drv.return_value.unplug.return_value = 'vnet_w'
vif.unplug(self.adpt, 'host_uuid', 'instance', 'vif', slot_mgr)
vif.unplug(self.adpt, 'host_uuid', 'instance', mock_vif, slot_mgr)
mock_bld_drv.assert_called_once_with(self.adpt, 'host_uuid',
'instance', 'vif')
'instance', mock_vif)
mock_bld_drv.return_value.unplug.assert_called_once_with(
'vif', cna_w_list=None)
mock_vif, cna_w_list=None)
slot_mgr.drop_vnet.assert_called_once_with('vnet_w')
mock_event.assert_called_once_with(self.adpt, 'unplug', 'vnet_w')
# Clean up
mock_bld_drv.reset_mock()
mock_bld_drv.return_value.unplug.reset_mock()
slot_mgr.drop_vnet.reset_mock()
mock_event.reset_mock()
# 2) Without slot deregistration, specified cna_w_list
mock_bld_drv.return_value.unplug.return_value = None
vif.unplug(self.adpt, 'host_uuid', 'instance', 'vif', slot_mgr,
vif.unplug(self.adpt, 'host_uuid', 'instance', mock_vif, slot_mgr,
cna_w_list='cnalist')
mock_bld_drv.assert_called_once_with(self.adpt, 'host_uuid',
'instance', 'vif')
'instance', mock_vif)
mock_bld_drv.return_value.unplug.assert_called_once_with(
'vif', cna_w_list='cnalist')
mock_vif, cna_w_list='cnalist')
slot_mgr.drop_vnet.assert_not_called()
# When unplug doesn't find a vif, we don't push an event
mock_event.assert_not_called()
@mock.patch('nova_powervm.virt.powervm.vif._build_vif_driver')
def test_plug_raises(self, mock_vif_drv):

View File

@ -24,12 +24,14 @@ from nova.network import model as network_model
from nova import utils
from oslo_concurrency import lockutils
from oslo_config import cfg
from oslo_serialization import jsonutils
from oslo_utils import importutils
from pypowervm import exceptions as pvm_ex
from pypowervm.tasks import cna as pvm_cna
from pypowervm.tasks import partition as pvm_par
from pypowervm.tasks import sriov as sriovtask
from pypowervm import util as pvm_util
from pypowervm.wrappers import event as pvm_evt
from pypowervm.wrappers import iocard as pvm_card
from pypowervm.wrappers import logical_partition as pvm_lpar
from pypowervm.wrappers import managed_system as pvm_ms
@ -46,6 +48,9 @@ LOG = logging.getLogger(__name__)
SECURE_RMC_VSWITCH = 'MGMTSWITCH'
SECURE_RMC_VLAN = 4094
# Provider tag for custom events from this module
EVENT_PROVIDER_ID = 'NOVA_PVM_VIF'
VIF_MAPPING = {'pvm_sea': 'nova_powervm.virt.powervm.vif.PvmSeaVifDriver',
'ovs': 'nova_powervm.virt.powervm.vif.PvmOvsVifDriver',
'bridge': 'nova_powervm.virt.powervm.vif.PvmLBVifDriver',
@ -81,6 +86,27 @@ def _build_vif_driver(adapter, host_uuid, instance, vif):
{'vif_type': vif['type'], 'instance': instance.name})
def _push_vif_event(adapter, action, vif_w):
"""Push a custom event to the REST server for a vif action (plug/unplug).
This event prompts the neutron agent to mark the port up or down.
:param adapter: The pypowervm adapter.
:param action: The action taken on the vif - either 'plug' or 'unplug'
:param vif_w: The pypowervm wrapper of the affected vif (CNA, VNIC, etc.)
"""
data = vif_w.href
detail = jsonutils.dumps(dict(provider=EVENT_PROVIDER_ID, action=action,
mac=vif_w.mac))
event = pvm_evt.Event.bld(adapter, data, detail)
try:
event = event.create()
LOG.info(_LI('Custom event push: %s'), str(event))
except Exception:
LOG.error(_LE('Custom VIF event push failed. %s'), str(event))
raise
def plug(adapter, host_uuid, instance, vif, slot_mgr, new_vif=True):
"""Plugs a virtual interface (network) into a VM.
@ -118,6 +144,10 @@ def plug(adapter, host_uuid, instance, vif, slot_mgr, new_vif=True):
if not slot_num and new_vif:
slot_mgr.register_vnet(vnet_w)
# Push a custom event if we really plugged the vif
if vnet_w is not None:
_push_vif_event(adapter, 'plug', vnet_w)
return vnet_w
@ -137,6 +167,9 @@ def unplug(adapter, host_uuid, instance, vif, slot_mgr, cna_w_list=None):
vif_drv = _build_vif_driver(adapter, host_uuid, instance, vif)
try:
vnet_w = vif_drv.unplug(vif, cna_w_list=cna_w_list)
# Push a custom event, but only if the vif existed in the first place
if vnet_w:
_push_vif_event(adapter, 'unplug', vnet_w)
except pvm_ex.HttpError as he:
# Log the message constructed by HttpError
LOG.exception(he.args[0])
@ -278,10 +311,11 @@ class PvmVifDriver(object):
try:
cna_w.delete()
except Exception as e:
LOG.error(_LE('Unable to unplug VIF with mac %(mac)s for instance '
'%(inst)s.'), {'mac': vif['address'],
'inst': self.instance.name})
raise exception.VirtualInterfaceUnplugException(reason=e.args[0])
LOG.exception(e)
raise exception.VirtualInterfaceUnplugException(
_LE('Unable to unplug VIF with mac %(mac)s for instance '
'%(inst)s.') % {'mac': vif['address'],
'inst': self.instance.name})
return cna_w
def _find_cna_for_vif(self, cna_w_list, vif):