Adds support for the Indigo Virtual Switch (IVS)
Implements: blueprint ivs-interface-driver Adds the IVS VIF type to portbindings.py. Adds the VIF interface class to allow agents to bind to VIF switches. Adds support to the BigSwitch plugin to request the IVS VIF type on nova compute nodes. Adds unit tests for new interface class and changes to BigSwitch plugin. Change-Id: I2fe104000523c60097c22946b0a80dc404dfe273
This commit is contained in:
parent
b87981d556
commit
e02c746a94
|
@ -40,3 +40,9 @@ servers=localhost:8080
|
|||
#server_ssl=True
|
||||
#sync_data=True
|
||||
#server_timeout=10
|
||||
|
||||
[NOVA]
|
||||
# Specify the VIF_TYPE that will be controlled on the Nova compute instances
|
||||
# options: ivs or ovs
|
||||
# default: ovs
|
||||
# vif_type = ovs
|
||||
|
|
|
@ -21,6 +21,7 @@ kill_dnsmasq_usr: KillFilter, root, /usr/sbin/dnsmasq, -9, -HUP
|
|||
# dhcp-agent uses cat
|
||||
cat: RegExpFilter, cat, root, cat, /proc/\d+/cmdline
|
||||
ovs-vsctl: CommandFilter, ovs-vsctl, root
|
||||
ivs-ctl: CommandFilter, ivs-ctl, root
|
||||
|
||||
# metadata proxy
|
||||
metadata_proxy: CommandFilter, quantum-ns-metadata-proxy, root
|
||||
|
|
|
@ -219,6 +219,69 @@ class OVSInterfaceDriver(LinuxInterfaceDriver):
|
|||
device_name)
|
||||
|
||||
|
||||
class IVSInterfaceDriver(LinuxInterfaceDriver):
|
||||
"""Driver for creating an internal interface on an IVS bridge."""
|
||||
|
||||
DEV_NAME_PREFIX = 'tap'
|
||||
|
||||
def __init__(self, conf):
|
||||
super(IVSInterfaceDriver, self).__init__(conf)
|
||||
self.DEV_NAME_PREFIX = 'ns-'
|
||||
|
||||
def _get_tap_name(self, dev_name, prefix=None):
|
||||
dev_name = dev_name.replace(prefix or self.DEV_NAME_PREFIX, 'tap')
|
||||
return dev_name
|
||||
|
||||
def _ivs_add_port(self, device_name, port_id, mac_address):
|
||||
cmd = ['ivs-ctl', 'add-port', device_name]
|
||||
utils.execute(cmd, self.root_helper)
|
||||
|
||||
def plug(self, network_id, port_id, device_name, mac_address,
|
||||
bridge=None, namespace=None, prefix=None):
|
||||
"""Plug in the interface."""
|
||||
if not ip_lib.device_exists(device_name,
|
||||
self.root_helper,
|
||||
namespace=namespace):
|
||||
|
||||
ip = ip_lib.IPWrapper(self.root_helper)
|
||||
tap_name = self._get_tap_name(device_name, prefix)
|
||||
|
||||
root_dev, ns_dev = ip.add_veth(tap_name, device_name)
|
||||
|
||||
self._ivs_add_port(tap_name, port_id, mac_address)
|
||||
|
||||
ns_dev = ip.device(device_name)
|
||||
ns_dev.link.set_address(mac_address)
|
||||
|
||||
if self.conf.network_device_mtu:
|
||||
ns_dev.link.set_mtu(self.conf.network_device_mtu)
|
||||
root_dev.link.set_mtu(self.conf.network_device_mtu)
|
||||
|
||||
if namespace:
|
||||
namespace_obj = ip.ensure_namespace(namespace)
|
||||
namespace_obj.add_device_to_namespace(ns_dev)
|
||||
|
||||
ns_dev.link.set_up()
|
||||
root_dev.link.set_up()
|
||||
else:
|
||||
LOG.warn(_("Device %s already exists"), device_name)
|
||||
|
||||
def unplug(self, device_name, bridge=None, namespace=None, prefix=None):
|
||||
"""Unplug the interface."""
|
||||
tap_name = self._get_tap_name(device_name, prefix)
|
||||
try:
|
||||
cmd = ['ivs-ctl', 'del-port', tap_name]
|
||||
utils.execute(cmd, self.root_helper)
|
||||
device = ip_lib.IPDevice(device_name,
|
||||
self.root_helper,
|
||||
namespace)
|
||||
device.link.delete()
|
||||
LOG.debug(_("Unplugged interface '%s'"), device_name)
|
||||
except RuntimeError:
|
||||
LOG.error(_("Failed unplugging interface '%s'"),
|
||||
device_name)
|
||||
|
||||
|
||||
class BridgeInterfaceDriver(LinuxInterfaceDriver):
|
||||
"""Driver for creating bridge interfaces."""
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ CAP_PORT_FILTER = 'port_filter'
|
|||
VIF_TYPE_UNBOUND = 'unbound'
|
||||
VIF_TYPE_BINDING_FAILED = 'binding_failed'
|
||||
VIF_TYPE_OVS = 'ovs'
|
||||
VIF_TYPE_IVS = 'ivs'
|
||||
VIF_TYPE_BRIDGE = 'bridge'
|
||||
VIF_TYPE_802_QBG = '802.1qbg'
|
||||
VIF_TYPE_802_QBH = '802.1qbh'
|
||||
|
|
|
@ -104,6 +104,16 @@ restproxy_opts = [
|
|||
cfg.CONF.register_opts(restproxy_opts, "RESTPROXY")
|
||||
|
||||
|
||||
nova_opts = [
|
||||
cfg.StrOpt('vif_type', default='ovs',
|
||||
help=_("Virtual interface type to configure on "
|
||||
"Nova compute nodes")),
|
||||
]
|
||||
|
||||
|
||||
cfg.CONF.register_opts(nova_opts, "NOVA")
|
||||
|
||||
|
||||
# The following are used to invoke the API on the external controller
|
||||
NET_RESOURCE_PATH = "/tenants/%s/networks"
|
||||
PORT_RESOURCE_PATH = "/tenants/%s/networks/%s/ports"
|
||||
|
@ -1267,7 +1277,16 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2,
|
|||
return data
|
||||
|
||||
def _extend_port_dict_binding(self, context, port):
|
||||
port[portbindings.VIF_TYPE] = portbindings.VIF_TYPE_OVS
|
||||
cfg_vif_type = cfg.CONF.NOVA.vif_type.lower()
|
||||
if not cfg_vif_type in (portbindings.VIF_TYPE_OVS,
|
||||
portbindings.VIF_TYPE_IVS):
|
||||
LOG.warning(_("Unrecognized vif_type in configuration "
|
||||
"[%s]. Defaulting to ovs. "),
|
||||
cfg_vif_type)
|
||||
cfg_vif_type = portbindings.VIF_TYPE_OVS
|
||||
|
||||
port[portbindings.VIF_TYPE] = cfg_vif_type
|
||||
|
||||
port[portbindings.CAPABILITIES] = {
|
||||
portbindings.CAP_PORT_FILTER:
|
||||
'security-group' in self.supported_extension_aliases}
|
||||
|
|
|
@ -24,3 +24,10 @@ reconnect_interval = 2
|
|||
servers=localhost:8899
|
||||
serverssl=False
|
||||
#serverauth=username:password
|
||||
|
||||
[NOVA]
|
||||
# Specify the VIF_TYPE that will be controlled on the Nova compute instances
|
||||
# options: ivs or ovs
|
||||
# default: ovs
|
||||
vif_type = ovs
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
import os
|
||||
|
||||
from mock import patch
|
||||
from oslo.config import cfg
|
||||
|
||||
import quantum.common.test_lib as test_lib
|
||||
from quantum.extensions import portbindings
|
||||
|
@ -93,6 +94,18 @@ class TestBigSwitchProxyPortsV2(test_plugin.TestPortsV2,
|
|||
HAS_PORT_FILTER = False
|
||||
|
||||
|
||||
class TestBigSwitchProxyPortsV2IVS(test_plugin.TestPortsV2,
|
||||
BigSwitchProxyPluginV2TestCase,
|
||||
test_bindings.PortBindingsTestCase):
|
||||
VIF_TYPE = portbindings.VIF_TYPE_IVS
|
||||
HAS_PORT_FILTER = False
|
||||
|
||||
def setUp(self):
|
||||
super(TestBigSwitchProxyPortsV2IVS,
|
||||
self).setUp()
|
||||
cfg.CONF.set_override('vif_type', 'ivs', 'NOVA')
|
||||
|
||||
|
||||
class TestBigSwitchProxyNetworksV2(test_plugin.TestNetworksV2,
|
||||
BigSwitchProxyPluginV2TestCase):
|
||||
|
||||
|
|
|
@ -394,3 +394,79 @@ class TestMetaInterfaceDriver(TestBase):
|
|||
namespace=namespace)
|
||||
self.ip_dev.assert_has_calls(expected)
|
||||
self.assertEquals('fake1', plugin_tag1)
|
||||
|
||||
|
||||
class TestIVSInterfaceDriver(TestBase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestIVSInterfaceDriver, self).setUp()
|
||||
|
||||
def test_get_device_name(self):
|
||||
br = interface.IVSInterfaceDriver(self.conf)
|
||||
device_name = br.get_device_name(FakePort())
|
||||
self.assertEqual('ns-abcdef01-12', device_name)
|
||||
|
||||
def test_plug_with_prefix(self):
|
||||
self._test_plug(devname='qr-0', prefix='qr-')
|
||||
|
||||
def _test_plug(self, devname=None, namespace=None,
|
||||
prefix=None, mtu=None):
|
||||
|
||||
if not devname:
|
||||
devname = 'ns-0'
|
||||
|
||||
def device_exists(dev, root_helper=None, namespace=None):
|
||||
return dev == 'indigo'
|
||||
|
||||
ivs = interface.IVSInterfaceDriver(self.conf)
|
||||
self.device_exists.side_effect = device_exists
|
||||
|
||||
root_dev = mock.Mock()
|
||||
_ns_dev = mock.Mock()
|
||||
ns_dev = mock.Mock()
|
||||
self.ip().add_veth = mock.Mock(return_value=(root_dev, _ns_dev))
|
||||
self.ip().device = mock.Mock(return_value=(ns_dev))
|
||||
expected = [mock.call('sudo'), mock.call().add_veth('tap0', devname),
|
||||
mock.call().device(devname)]
|
||||
|
||||
ivsctl_cmd = ['ivs-ctl', 'add-port', 'tap0']
|
||||
|
||||
with mock.patch.object(utils, 'execute') as execute:
|
||||
ivs.plug('01234567-1234-1234-99',
|
||||
'port-1234',
|
||||
devname,
|
||||
'aa:bb:cc:dd:ee:ff',
|
||||
namespace=namespace,
|
||||
prefix=prefix)
|
||||
execute.assert_called_once_with(ivsctl_cmd, 'sudo')
|
||||
|
||||
ns_dev.assert_has_calls(
|
||||
[mock.call.link.set_address('aa:bb:cc:dd:ee:ff')])
|
||||
if mtu:
|
||||
ns_dev.assert_has_calls([mock.call.link.set_mtu(mtu)])
|
||||
root_dev.assert_has_calls([mock.call.link.set_mtu(mtu)])
|
||||
if namespace:
|
||||
expected.extend(
|
||||
[mock.call().ensure_namespace(namespace),
|
||||
mock.call().ensure_namespace().add_device_to_namespace(
|
||||
mock.ANY)])
|
||||
|
||||
self.ip.assert_has_calls(expected)
|
||||
root_dev.assert_has_calls([mock.call.link.set_up()])
|
||||
ns_dev.assert_has_calls([mock.call.link.set_up()])
|
||||
|
||||
def test_plug_mtu(self):
|
||||
self.conf.set_override('network_device_mtu', 9000)
|
||||
self._test_plug(mtu=9000)
|
||||
|
||||
def test_plug_namespace(self):
|
||||
self._test_plug(namespace='mynamespace')
|
||||
|
||||
def test_unplug(self):
|
||||
ivs = interface.IVSInterfaceDriver(self.conf)
|
||||
ivsctl_cmd = ['ivs-ctl', 'del-port', 'tap0']
|
||||
with mock.patch.object(utils, 'execute') as execute:
|
||||
ivs.unplug('ns-0')
|
||||
execute.assert_called_once_with(ivsctl_cmd, 'sudo')
|
||||
self.ip_dev.assert_has_calls([mock.call('ns-0', 'sudo', None),
|
||||
mock.call().link.delete()])
|
||||
|
|
Loading…
Reference in New Issue