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 traits=pvm_fx.LocalPVMTraits)).adpt
self.slot_mgr = mock.Mock() 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') @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.""" """Test the top-level plug method."""
mock_vif = {'address': 'MAC'} mock_vif = {'address': 'MAC'}
slot_mgr = mock.Mock() slot_mgr = mock.Mock()
@ -62,6 +88,7 @@ class TestVifFunctions(test.TestCase):
new_vif=True) new_vif=True)
slot_mgr.register_vnet.assert_called_once_with( slot_mgr.register_vnet.assert_called_once_with(
mock_bld_drv.return_value.plug.return_value) 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) self.assertEqual(mock_bld_drv.return_value.plug.return_value, vnet)
# Clean up # Clean up
@ -69,9 +96,12 @@ class TestVifFunctions(test.TestCase):
slot_mgr.build_map.get_vnet_slot.reset_mock() slot_mgr.build_map.get_vnet_slot.reset_mock()
mock_bld_drv.return_value.plug.reset_mock() mock_bld_drv.return_value.plug.reset_mock()
slot_mgr.register_vnet.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 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, vnet = vif.plug(self.adpt, 'host_uuid', 'instance', mock_vif, slot_mgr,
new_vif=False) new_vif=False)
@ -81,36 +111,43 @@ class TestVifFunctions(test.TestCase):
mock_bld_drv.return_value.plug.assert_called_once_with(mock_vif, 123, mock_bld_drv.return_value.plug.assert_called_once_with(mock_vif, 123,
new_vif=False) new_vif=False)
slot_mgr.register_vnet.assert_not_called() 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') @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.""" """Test the top-level unplug method."""
mock_vif = {'address': 'MAC'}
slot_mgr = mock.Mock() slot_mgr = mock.Mock()
# 1) With slot deregistration, default cna_w_list # 1) With slot deregistration, default cna_w_list
mock_bld_drv.return_value.unplug.return_value = 'vnet_w' 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', 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( 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') slot_mgr.drop_vnet.assert_called_once_with('vnet_w')
mock_event.assert_called_once_with(self.adpt, 'unplug', 'vnet_w')
# Clean up # Clean up
mock_bld_drv.reset_mock() mock_bld_drv.reset_mock()
mock_bld_drv.return_value.unplug.reset_mock() mock_bld_drv.return_value.unplug.reset_mock()
slot_mgr.drop_vnet.reset_mock() slot_mgr.drop_vnet.reset_mock()
mock_event.reset_mock()
# 2) Without slot deregistration, specified cna_w_list # 2) Without slot deregistration, specified cna_w_list
mock_bld_drv.return_value.unplug.return_value = None 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') cna_w_list='cnalist')
mock_bld_drv.assert_called_once_with(self.adpt, 'host_uuid', 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( 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() 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') @mock.patch('nova_powervm.virt.powervm.vif._build_vif_driver')
def test_plug_raises(self, mock_vif_drv): 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 nova import utils
from oslo_concurrency import lockutils from oslo_concurrency import lockutils
from oslo_config import cfg from oslo_config import cfg
from oslo_serialization import jsonutils
from oslo_utils import importutils from oslo_utils import importutils
from pypowervm import exceptions as pvm_ex from pypowervm import exceptions as pvm_ex
from pypowervm.tasks import cna as pvm_cna from pypowervm.tasks import cna as pvm_cna
from pypowervm.tasks import partition as pvm_par from pypowervm.tasks import partition as pvm_par
from pypowervm.tasks import sriov as sriovtask from pypowervm.tasks import sriov as sriovtask
from pypowervm import util as pvm_util 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 iocard as pvm_card
from pypowervm.wrappers import logical_partition as pvm_lpar from pypowervm.wrappers import logical_partition as pvm_lpar
from pypowervm.wrappers import managed_system as pvm_ms from pypowervm.wrappers import managed_system as pvm_ms
@ -46,6 +48,9 @@ LOG = logging.getLogger(__name__)
SECURE_RMC_VSWITCH = 'MGMTSWITCH' SECURE_RMC_VSWITCH = 'MGMTSWITCH'
SECURE_RMC_VLAN = 4094 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', VIF_MAPPING = {'pvm_sea': 'nova_powervm.virt.powervm.vif.PvmSeaVifDriver',
'ovs': 'nova_powervm.virt.powervm.vif.PvmOvsVifDriver', 'ovs': 'nova_powervm.virt.powervm.vif.PvmOvsVifDriver',
'bridge': 'nova_powervm.virt.powervm.vif.PvmLBVifDriver', '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}) {'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): def plug(adapter, host_uuid, instance, vif, slot_mgr, new_vif=True):
"""Plugs a virtual interface (network) into a VM. """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: if not slot_num and new_vif:
slot_mgr.register_vnet(vnet_w) 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 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) vif_drv = _build_vif_driver(adapter, host_uuid, instance, vif)
try: try:
vnet_w = vif_drv.unplug(vif, cna_w_list=cna_w_list) 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: except pvm_ex.HttpError as he:
# Log the message constructed by HttpError # Log the message constructed by HttpError
LOG.exception(he.args[0]) LOG.exception(he.args[0])
@ -278,10 +311,11 @@ class PvmVifDriver(object):
try: try:
cna_w.delete() cna_w.delete()
except Exception as e: except Exception as e:
LOG.error(_LE('Unable to unplug VIF with mac %(mac)s for instance ' LOG.exception(e)
'%(inst)s.'), {'mac': vif['address'], raise exception.VirtualInterfaceUnplugException(
'inst': self.instance.name}) _LE('Unable to unplug VIF with mac %(mac)s for instance '
raise exception.VirtualInterfaceUnplugException(reason=e.args[0]) '%(inst)s.') % {'mac': vif['address'],
'inst': self.instance.name})
return cna_w return cna_w
def _find_cna_for_vif(self, cna_w_list, vif): def _find_cna_for_vif(self, cna_w_list, vif):