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:
parent
576729f500
commit
4fcf6cfb1e
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue