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
|
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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Loading…
Reference in New Issue