Merge "Initial LB VIF Type"
This commit is contained in:
@@ -15,7 +15,7 @@
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
import netifaces
|
||||
from mock import call
|
||||
|
||||
from nova import exception
|
||||
from nova import test
|
||||
@@ -188,6 +188,86 @@ class TestVifSeaDriver(test.TestCase):
|
||||
self.drv.post_live_migrate_at_source(mock.Mock())
|
||||
|
||||
|
||||
class TestVifLBDriver(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestVifLBDriver, self).setUp()
|
||||
|
||||
self.adpt = self.useFixture(pvm_fx.AdapterFx(
|
||||
traits=pvm_fx.LocalPVMTraits)).adpt
|
||||
self.inst = mock.MagicMock(uuid='inst_uuid')
|
||||
self.drv = vif.PvmLBVifDriver(self.adpt, 'host_uuid', self.inst)
|
||||
|
||||
@mock.patch('nova.network.linux_net.LinuxBridgeInterfaceDriver.'
|
||||
'ensure_bridge')
|
||||
@mock.patch('nova.utils.execute')
|
||||
@mock.patch('nova.network.linux_net.create_ovs_vif_port')
|
||||
@mock.patch('nova_powervm.virt.powervm.vif.PvmOvsVifDriver.'
|
||||
'get_trunk_dev_name')
|
||||
@mock.patch('pypowervm.tasks.cna.crt_p2p_cna')
|
||||
@mock.patch('pypowervm.tasks.partition.get_this_partition')
|
||||
@mock.patch('nova_powervm.virt.powervm.vm.get_pvm_uuid')
|
||||
def test_plug(
|
||||
self, mock_pvm_uuid, mock_mgmt_lpar, mock_p2p_cna,
|
||||
mock_trunk_dev_name, mock_crt_ovs_vif_port, mock_exec,
|
||||
mock_ensure_bridge):
|
||||
# Mock the data
|
||||
mock_pvm_uuid.return_value = 'lpar_uuid'
|
||||
mock_mgmt_lpar.return_value = mock.Mock(uuid='mgmt_uuid')
|
||||
mock_trunk_dev_name.return_value = 'device'
|
||||
|
||||
cna_w, trunk_wraps = mock.MagicMock(), [mock.MagicMock()]
|
||||
mock_p2p_cna.return_value = cna_w, trunk_wraps
|
||||
|
||||
# Run the plug
|
||||
vif = {'network': {'bridge': 'br0'}, 'address': 'aa:bb:cc:dd:ee:ff',
|
||||
'id': 'vif_id', 'devname': 'tap_dev'}
|
||||
self.drv.plug(vif, 6)
|
||||
|
||||
# Validate the calls
|
||||
mock_p2p_cna.assert_called_once_with(
|
||||
self.adpt, 'host_uuid', 'lpar_uuid', ['mgmt_uuid'], 'OpenStackOVS',
|
||||
crt_vswitch=True, mac_addr='aa:bb:cc:dd:ee:ff', dev_name='tap_dev',
|
||||
slot_num=6)
|
||||
mock_exec.assert_called_once_with('ip', 'link', 'set', 'tap_dev', 'up',
|
||||
run_as_root=True)
|
||||
mock_ensure_bridge.assert_called_once_with('br0', 'tap_dev')
|
||||
|
||||
@mock.patch('nova.utils.execute')
|
||||
@mock.patch('pypowervm.tasks.cna.find_trunks')
|
||||
@mock.patch('nova_powervm.virt.powervm.vif.PvmLBVifDriver.'
|
||||
'get_trunk_dev_name')
|
||||
@mock.patch('nova_powervm.virt.powervm.vif.PvmLBVifDriver.'
|
||||
'_find_cna_for_vif')
|
||||
@mock.patch('nova_powervm.virt.powervm.vm.get_cnas')
|
||||
def test_unplug(self, mock_get_cnas, mock_find_cna, mock_trunk_dev_name,
|
||||
mock_find_trunks, mock_exec):
|
||||
# Set up the mocks
|
||||
mock_cna = mock.Mock()
|
||||
mock_get_cnas.return_value = [mock_cna, mock.Mock()]
|
||||
mock_find_cna.return_value = mock_cna
|
||||
|
||||
t1 = mock.MagicMock()
|
||||
mock_find_trunks.return_value = [t1]
|
||||
|
||||
mock_trunk_dev_name.return_value = 'fake_dev'
|
||||
|
||||
# Call the unplug
|
||||
vif = {'address': 'aa:bb:cc:dd:ee:ff', 'network': {'bridge': 'br0'}}
|
||||
self.drv.unplug(vif)
|
||||
|
||||
# The trunks and the cna should have been deleted
|
||||
self.assertTrue(t1.delete.called)
|
||||
self.assertTrue(mock_cna.delete.called)
|
||||
|
||||
# Validate the execute
|
||||
call_ip = call('ip', 'link', 'set', 'fake_dev', 'down',
|
||||
run_as_root=True)
|
||||
call_delif = call('brctl', 'delif', 'br0', 'fake_dev',
|
||||
run_as_root=True)
|
||||
mock_exec.assert_has_calls([call_ip, call_delif])
|
||||
|
||||
|
||||
class TestVifOvsDriver(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
@@ -226,33 +306,21 @@ class TestVifOvsDriver(test.TestCase):
|
||||
'br0', 'device', 'vif_id', 'aa:bb:cc:dd:ee:ff', 'inst_uuid')
|
||||
mock_p2p_cna.assert_called_once_with(
|
||||
self.adpt, 'host_uuid', 'lpar_uuid', ['mgmt_uuid'], 'OpenStackOVS',
|
||||
crt_vswitch=True, mac_addr='aa:bb:cc:dd:ee:ff', slot_num=slot_num)
|
||||
mock_exec.assert_called_once_with('ip', 'link', 'set', 'device', 'up',
|
||||
run_as_root=True)
|
||||
crt_vswitch=True, mac_addr='aa:bb:cc:dd:ee:ff', slot_num=slot_num,
|
||||
dev_name='device')
|
||||
mock_exec.assert_called_with('ip', 'link', 'set', 'device', 'up',
|
||||
run_as_root=True)
|
||||
|
||||
@mock.patch('netifaces.ifaddresses')
|
||||
@mock.patch('netifaces.interfaces')
|
||||
def test_get_trunk_dev_name(self, mock_interfaces, mock_ifaddresses):
|
||||
trunk_w = mock.Mock(mac='01234567890A')
|
||||
def test_get_trunk_dev_name(self):
|
||||
mock_vif = {'devname': 'tap_test', 'id': '1234567890123456'}
|
||||
|
||||
mock_link_addrs1 = {
|
||||
netifaces.AF_LINK: [{'addr': '00:11:22:33:44:55'},
|
||||
{'addr': '00:11:22:33:44:66'}]}
|
||||
mock_link_addrs2 = {
|
||||
netifaces.AF_LINK: [{'addr': '00:11:22:33:44:77'},
|
||||
{'addr': '01:23:45:67:89:0a'}]}
|
||||
# Test when the dev name is available
|
||||
self.assertEqual('tap_test', self.drv.get_trunk_dev_name(mock_vif))
|
||||
|
||||
mock_interfaces.return_value = ['a', 'b']
|
||||
mock_ifaddresses.side_effect = [mock_link_addrs1, mock_link_addrs2]
|
||||
|
||||
# The mock_link_addrs2 (or interface b) should be the match
|
||||
self.assertEqual('b', self.drv.get_trunk_dev_name(trunk_w))
|
||||
|
||||
# If you take out the correct adapter, make sure it fails.
|
||||
mock_interfaces.return_value = ['a']
|
||||
mock_ifaddresses.side_effect = [mock_link_addrs1]
|
||||
self.assertRaises(exception.VirtualInterfacePlugException,
|
||||
self.drv.get_trunk_dev_name, trunk_w)
|
||||
# And when it isn't. Should also cut off a few characters from the id
|
||||
del mock_vif['devname']
|
||||
self.assertEqual('nic12345678901',
|
||||
self.drv.get_trunk_dev_name(mock_vif))
|
||||
|
||||
@mock.patch('pypowervm.tasks.cna.find_trunks')
|
||||
@mock.patch('nova.network.linux_net.delete_ovs_vif_port')
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
|
||||
import abc
|
||||
import logging
|
||||
import netifaces
|
||||
import six
|
||||
|
||||
from nova import exception
|
||||
from nova.network import linux_net
|
||||
from nova.network import model as network_model
|
||||
from nova import utils
|
||||
from oslo_concurrency import lockutils
|
||||
from oslo_config import cfg
|
||||
@@ -42,7 +42,8 @@ SECURE_RMC_VSWITCH = 'MGMTSWITCH'
|
||||
SECURE_RMC_VLAN = 4094
|
||||
|
||||
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'}
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
@@ -259,6 +260,14 @@ class PvmVifDriver(object):
|
||||
return cna_w
|
||||
|
||||
def _find_cna_for_vif(self, cna_w_list, vif):
|
||||
"""Finds the PowerVM CNA for a given Nova VIF.
|
||||
|
||||
:param cna_w_list: The list of Client Network Adapter wrappers from
|
||||
pypowervm.
|
||||
:param vif: The Nova Virtual Interface (virtual network interface).
|
||||
:return: The CNA that corresponds to the VIF. None if one is not
|
||||
part of the cna_w_list.
|
||||
"""
|
||||
for cna_w in cna_w_list:
|
||||
# If the MAC address matched, attempt the delete.
|
||||
if vm.norm_mac(cna_w.mac) == vif['address']:
|
||||
@@ -288,6 +297,13 @@ class PvmSeaVifDriver(PvmVifDriver):
|
||||
"""The PowerVM Shared Ethernet Adapter VIF Driver."""
|
||||
|
||||
def plug(self, vif, slot_num):
|
||||
"""Plugs a virtual interface (network) into a VM.
|
||||
|
||||
This method simply creates the client network adapter into the VM.
|
||||
|
||||
:param vif: The virtual interface to plug into the instance.
|
||||
:param slot_num: Which slot number to plug the VIF into. May be None.
|
||||
"""
|
||||
lpar_uuid = vm.get_pvm_uuid(self.instance)
|
||||
|
||||
# CNA's require a VLAN. If the network doesn't provide, default to 1
|
||||
@@ -298,53 +314,161 @@ class PvmSeaVifDriver(PvmVifDriver):
|
||||
return cna_w
|
||||
|
||||
|
||||
class PvmOvsVifDriver(PvmVifDriver):
|
||||
"""The Open vSwitch VIF driver for PowerVM."""
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class PvmLioVifDriver(PvmVifDriver):
|
||||
"""An abstract VIF driver that uses Linux I/O to host."""
|
||||
|
||||
def get_trunk_dev_name(self, vif):
|
||||
"""Returns the device name for the trunk adapter.
|
||||
|
||||
A given VIF in the Linux I/O model will have a trunk adapter and a
|
||||
client adapter. This will return the trunk adapter's name as it
|
||||
will appear on the management VM.
|
||||
|
||||
:param vif: The nova network interface
|
||||
:return: The device name.
|
||||
"""
|
||||
if 'devname' in vif:
|
||||
return vif['devname']
|
||||
return ("nic" + vif['id'])[:network_model.NIC_NAME_LEN]
|
||||
|
||||
def plug(self, vif, slot_num):
|
||||
"""Plugs a virtual interface (network) into a VM.
|
||||
|
||||
Creates a 'peer to peer' connection between the Management partition
|
||||
hosting the Linux I/O and the client VM. There will be one trunk
|
||||
adapter for a given client adapter.
|
||||
|
||||
The device will be 'up' on the mgmt partition.
|
||||
|
||||
:param vif: The virtual interface to plug into the instance.
|
||||
:param slot_num: Which slot number to plug the VIF into. May be None.
|
||||
"""
|
||||
# Create the trunk and client adapter.
|
||||
lpar_uuid = vm.get_pvm_uuid(self.instance)
|
||||
mgmt_uuid = pvm_par.get_this_partition(self.adapter).uuid
|
||||
|
||||
dev_name = self.get_trunk_dev_name(vif)
|
||||
cna_w, trunk_wraps = pvm_cna.crt_p2p_cna(
|
||||
self.adapter, self.host_uuid, lpar_uuid, [mgmt_uuid],
|
||||
CONF.powervm.pvm_vswitch_for_ovs, crt_vswitch=True,
|
||||
mac_addr=vif['address'], slot_num=slot_num)
|
||||
mac_addr=vif['address'], dev_name=dev_name, slot_num=slot_num)
|
||||
|
||||
utils.execute('ip', 'link', 'set', dev_name, 'up', run_as_root=True)
|
||||
|
||||
return cna_w
|
||||
|
||||
|
||||
class PvmLBVifDriver(PvmLioVifDriver):
|
||||
"""The Linux Bridge VIF driver for PowerVM."""
|
||||
|
||||
def plug(self, vif, slot_num):
|
||||
"""Plugs a virtual interface (network) into a VM.
|
||||
|
||||
Extends the base Lio implementation. Will make sure that the bridge
|
||||
supports the trunk adapter.
|
||||
|
||||
:param vif: The virtual interface to plug into the instance.
|
||||
:param slot_num: Which slot number to plug the VIF into. May be None.
|
||||
"""
|
||||
cna_w = super(PvmLBVifDriver, self).plug(vif, slot_num)
|
||||
|
||||
# Similar to libvirt's vif.py plug_bridge. Need to attach the
|
||||
# interface to the bridge.
|
||||
linux_net.LinuxBridgeInterfaceDriver.ensure_bridge(
|
||||
vif['network']['bridge'], self.get_trunk_dev_name(vif))
|
||||
|
||||
return cna_w
|
||||
|
||||
def unplug(self, vif, cna_w_list=None):
|
||||
"""Unplugs a virtual interface (network) from a VM.
|
||||
|
||||
Extends the base implementation, but before invoking it will remove
|
||||
itself from the bridge it is connected to and delete the corresponding
|
||||
trunk device on the mgmt partition.
|
||||
|
||||
:param vif: The virtual interface to plug into the instance.
|
||||
:param cna_w_list: (Optional, Default: None) The list of Client Network
|
||||
Adapters from pypowervm. Providing this input
|
||||
allows for an improvement in operation speed.
|
||||
:return cna_w: The deleted Client Network Adapter.
|
||||
"""
|
||||
# Need to find the adapters if they were not provided
|
||||
if not cna_w_list:
|
||||
cna_w_list = vm.get_cnas(self.adapter, self.instance,
|
||||
self.host_uuid)
|
||||
|
||||
# Find the CNA for this vif.
|
||||
cna_w = self._find_cna_for_vif(cna_w_list, vif)
|
||||
if not cna_w:
|
||||
LOG.warning(_LW('Unable to unplug VIF with mac %(mac)s for '
|
||||
'instance %(inst)s. The VIF was not found on '
|
||||
'the instance.'),
|
||||
{'mac': vif['address'], 'inst': self.instance.name})
|
||||
return None
|
||||
|
||||
# Find and delete the trunk adapters
|
||||
trunks = pvm_cna.find_trunks(self.adapter, cna_w)
|
||||
|
||||
for trunk in trunks:
|
||||
dev_name = self.get_trunk_dev_name(vif)
|
||||
utils.execute('ip', 'link', 'set', dev_name, 'down',
|
||||
run_as_root=True)
|
||||
utils.execute('brctl', 'delif', vif['network']['bridge'],
|
||||
dev_name, run_as_root=True)
|
||||
trunk.delete()
|
||||
|
||||
# Now delete the client CNA
|
||||
return super(PvmLBVifDriver, self).unplug(vif, cna_w_list=cna_w_list)
|
||||
|
||||
|
||||
class PvmOvsVifDriver(PvmLioVifDriver):
|
||||
"""The Open vSwitch VIF driver for PowerVM."""
|
||||
|
||||
def plug(self, vif, slot_num):
|
||||
"""Plugs a virtual interface (network) into a VM.
|
||||
|
||||
Extends the Lio implementation. Will make sure that the trunk device
|
||||
has the appropriate metadata (ex. port id) set on it so that the
|
||||
Open vSwitch agent picks it up properly.
|
||||
|
||||
:param vif: The virtual interface to plug into the instance.
|
||||
:param slot_num: Which slot number to plug the VIF into. May be None.
|
||||
"""
|
||||
cna_w = super(PvmOvsVifDriver, self).plug(vif, slot_num)
|
||||
|
||||
# There will only be one trunk wrap, as we have created with just the
|
||||
# mgmt lpar. Next step is to set the device up and connect to the OVS
|
||||
dev = self.get_trunk_dev_name(trunk_wraps[0])
|
||||
utils.execute('ip', 'link', 'set', dev, 'up', run_as_root=True)
|
||||
linux_net.create_ovs_vif_port(vif['network']['bridge'], dev,
|
||||
dev_name = self.get_trunk_dev_name(vif)
|
||||
utils.execute('ip', 'link', 'set', dev_name, 'up', run_as_root=True)
|
||||
linux_net.create_ovs_vif_port(vif['network']['bridge'], dev_name,
|
||||
self.get_ovs_interfaceid(vif),
|
||||
vif['address'], self.instance.uuid)
|
||||
|
||||
return cna_w
|
||||
|
||||
def get_ovs_interfaceid(self, vif):
|
||||
"""Returns the interface id to set for a given VIF.
|
||||
|
||||
When a VIF is plugged for an Open vSwitch, it needs to have the
|
||||
interface ID set in the OVS metadata. This returns what the
|
||||
appropriate interface id is.
|
||||
|
||||
:param vif: The Nova network interface.
|
||||
"""
|
||||
return vif.get('ovs_interfaceid') or vif['id']
|
||||
|
||||
def get_trunk_dev_name(self, trunk_w):
|
||||
# The mac address from the API is of format: 01234567890A
|
||||
# We need it in format: 01:23:45:67:89:0a
|
||||
# That means we need to add colons and lower case it
|
||||
mac_addr = ":".join(trunk_w.mac[i:i + 2]
|
||||
for i in range(0, len(trunk_w.mac), 2)).lower()
|
||||
|
||||
# Use netifaces to find the appropriate matching interface name
|
||||
# TODO(thorst) I don't like this logic. Seems gross.
|
||||
ifaces = netifaces.interfaces()
|
||||
for iface in ifaces:
|
||||
link_addrs = netifaces.ifaddresses(iface)[netifaces.AF_LINK]
|
||||
for link_addr in link_addrs:
|
||||
if link_addr.get('addr') == mac_addr:
|
||||
return iface
|
||||
|
||||
raise exception.VirtualInterfacePlugException(
|
||||
_("Unable to find appropriate Trunk Device for mac "
|
||||
"%(mac_addr)s.") % {'mac_addr': mac_addr})
|
||||
|
||||
def unplug(self, vif, cna_w_list=None):
|
||||
"""Unplugs a virtual interface (network) from a VM.
|
||||
|
||||
Extends the base implementation, but before calling it will remove
|
||||
the adapter from the Open vSwitch and delete the trunk.
|
||||
|
||||
:param vif: The virtual interface to plug into the instance.
|
||||
:param cna_w_list: (Optional, Default: None) The list of Client Network
|
||||
Adapters from pypowervm. Providing this input
|
||||
allows for an improvement in operation speed.
|
||||
:return cna_w: The deleted Client Network Adapter.
|
||||
"""
|
||||
# Need to find the adapters if they were not provided
|
||||
if not cna_w_list:
|
||||
cna_w_list = vm.get_cnas(self.adapter, self.instance,
|
||||
|
||||
Reference in New Issue
Block a user