macvtap: Macvtap L2 Agent

This agent is required by the macvtap ml2 driver to support
macvtap attachments for libvirt qemu/kvm instances. It introduces
a new configuration option MACVTAP.physical_interface_mappings.

The review is submitted in three parts:
 - Part 1
    Common functions that are used by the ml2 driver and the agent
 - Part 2
     The Mechanism Driver to support port binding for macvtap attachments
 - Part 3 (this part)
    The Macvtap L2 Agent.

DocImpact
New ML2 mech driver + l2 agent
New config option "macvtap.physical_interface_mappings"

Change-Id: I219d80b4c704ac2f41edd3501f4b2198925778d6
Closes-Bug: #1480979
This commit is contained in:
Andreas Scheuring 2016-02-02 16:34:59 +01:00
parent dbd0ec757a
commit 2e7eb09271
17 changed files with 631 additions and 0 deletions

View File

@ -176,6 +176,12 @@ class IPWrapper(SubProcessBase):
return (IPDevice(name1, namespace=self.namespace),
IPDevice(name2, namespace=namespace2))
def add_macvtap(self, name, src_dev, mode='bridge'):
args = ['add', 'link', src_dev, 'name', name, 'type', 'macvtap',
'mode', mode]
self._as_root([], 'link', tuple(args))
return IPDevice(name, namespace=self.namespace)
def del_veth(self, name):
"""Delete a virtual interface between two namespaces."""
self._as_root([], 'link', ('del', name))
@ -455,6 +461,9 @@ class IpLinkCommand(IpDeviceCommandBase):
def set_address(self, mac_address):
self._as_root([], ('set', self.name, 'address', mac_address))
def set_allmulticast_on(self):
self._as_root([], ('set', self.name, 'allmulticast', 'on'))
def set_mtu(self, mtu_size):
self._as_root([], ('set', self.name, 'mtu', mtu_size))

View File

@ -0,0 +1,20 @@
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from neutron.plugins.ml2.drivers.macvtap.agent import (
macvtap_neutron_agent as agent_main)
def main():
agent_main.main()

View File

@ -153,6 +153,8 @@ DEVICE_NAME_MAX_LEN = 15
# vhost-user device names start with "vhu"
VHOST_USER_DEVICE_PREFIX = 'vhu'
# Device names start with "macvtap"
MACVTAP_DEVICE_PREFIX = 'macvtap'
# The vswitch side of a veth pair for a nova iptables filter setup
VETH_DEVICE_PREFIX = 'qvo'
# prefix for SNAT interface in DVR

View File

@ -45,6 +45,7 @@ import neutron.openstack.common.cache.cache
import neutron.plugins.ml2.config
import neutron.plugins.ml2.drivers.agent.config
import neutron.plugins.ml2.drivers.linuxbridge.agent.common.config
import neutron.plugins.ml2.drivers.macvtap.agent.config
import neutron.plugins.ml2.drivers.mech_sriov.agent.common.config
import neutron.plugins.ml2.drivers.mech_sriov.mech_driver.mech_driver
import neutron.plugins.ml2.drivers.openvswitch.agent.common.config
@ -199,6 +200,17 @@ def list_l3_agent_opts():
]
def list_macvtap_opts():
return [
('macvtap',
neutron.plugins.ml2.drivers.macvtap.agent.config.macvtap_opts),
('agent',
neutron.plugins.ml2.drivers.agent.config.agent_opts),
('securitygroup',
neutron.agent.securitygroups_rpc.security_group_opts)
]
def list_metadata_agent_opts():
return [
('DEFAULT',

View File

@ -0,0 +1,35 @@
# Copyright (c) 2016 IBM Corp.
#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_config import cfg
DEFAULT_INTERFACE_MAPPINGS = []
macvtap_opts = [
cfg.ListOpt('physical_interface_mappings',
default=DEFAULT_INTERFACE_MAPPINGS,
help=_("Comma-separated list of "
"<physical_network>:<physical_interface> tuples "
"mapping physical network names to the agent's "
"node-specific physical network interfaces to be used "
"for flat and VLAN networks. All physical networks "
"listed in network_vlan_ranges on the server should "
"have mappings to appropriate interfaces on each "
"agent.")),
]
cfg.CONF.register_opts(macvtap_opts, "macvtap")

View File

@ -0,0 +1,211 @@
# Copyright (c) 2016 IBM Corp.
#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import sys
from oslo_config import cfg
from oslo_log import log as logging
import oslo_messaging
from oslo_service import service
from neutron._i18n import _LE, _LI
from neutron.agent.linux import ip_lib
from neutron.agent.linux import utils
from neutron.agent import securitygroups_rpc as sg_rpc
from neutron.common import config as common_config
from neutron.common import constants
from neutron.common import topics
from neutron.common import utils as n_utils
from neutron.plugins.common import constants as p_constants
from neutron.plugins.ml2.drivers.agent import _agent_manager_base as amb
from neutron.plugins.ml2.drivers.agent import _common_agent as ca
from neutron.plugins.ml2.drivers.macvtap.agent import config # noqa
from neutron.plugins.ml2.drivers.macvtap import macvtap_common
LOG = logging.getLogger(__name__)
MACVTAP_AGENT_BINARY = "neutron-macvtap-agent"
MACVTAP_FS = "/sys/class/net/"
EXTENSION_DRIVER_TYPE = 'macvtap'
class MacvtapRPCCallBack(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
amb.CommonAgentManagerRpcCallBackBase):
# Set RPC API version to 1.0 by default.
# history
# 1.1 Support Security Group RPC
# 1.3 Added param devices_to_update to security_groups_provider_updated
# 1.4 Added support for network_update
target = oslo_messaging.Target(version='1.4')
def network_delete(self, context, **kwargs):
LOG.debug("network_delete received")
network_id = kwargs.get('network_id')
if network_id not in self.network_map:
LOG.error(_LE("Network %s is not available."), network_id)
return
segment = self.network_map.get(network_id)
if segment and segment.network_type == p_constants.TYPE_VLAN:
if_mappings = self.agent.mgr.interface_mappings
vlan_device_name = macvtap_common.get_vlan_device_name(
if_mappings[segment.physical_network],
str(segment.segmentation_id))
ip_dev = ip_lib.IPDevice(vlan_device_name)
if ip_dev.exists():
LOG.debug("Delete %s", ip_dev.name)
ip_dev.link.delete()
else:
LOG.debug("Cannot delete vlan device %s; it does not exist",
vlan_device_name)
def port_update(self, context, **kwargs):
port = kwargs['port']
LOG.debug("port_update received for port %s ", port)
mac = port['mac_address']
# Put the device name in the updated_devices set.
# Do not store port details, as if they're used for processing
# notifications there is no guarantee the notifications are
# processed in the same order as the relevant API requests.
self.updated_devices.add(mac)
class MacvtapManager(amb.CommonAgentManagerBase):
def __init__(self, interface_mappings):
self.interface_mappings = interface_mappings
self.validate_interface_mappings()
self.mac_device_name_mappings = dict()
def validate_interface_mappings(self):
for physnet, interface in self.interface_mappings.items():
if not ip_lib.device_exists(interface):
LOG.error(_LE("Interface %(intf)s for physical network "
"%(net)s does not exist. Agent terminated!"),
{'intf': interface, 'net': physnet})
sys.exit(1)
def ensure_port_admin_state(self, device, admin_state_up):
LOG.debug("Setting admin_state_up to %s for device %s",
admin_state_up, device)
dev = ip_lib.IPDevice(self.mac_device_name_mappings[device])
if admin_state_up:
dev.link.set_up()
else:
dev.link.set_down()
def get_agent_configurations(self):
return {'interface_mappings': self.interface_mappings}
def get_agent_id(self):
devices = ip_lib.IPWrapper().get_devices(True)
if devices:
mac = utils.get_interface_mac(devices[0].name)
return 'macvtap%s' % mac.replace(":", "")
else:
LOG.error(_LE("Unable to obtain MAC address for unique ID. "
"Agent terminated!"))
sys.exit(1)
def get_all_devices(self):
devices = set()
all_device_names = os.listdir(MACVTAP_FS)
# Refresh the mac_device_name mapping
self.mac_device_name_mappings = dict()
for device_name in all_device_names:
if device_name.startswith(constants.MACVTAP_DEVICE_PREFIX):
mac = utils.get_interface_mac(device_name)
self.mac_device_name_mappings[mac] = device_name
devices.add(mac)
return devices
def get_extension_driver_type(self):
return EXTENSION_DRIVER_TYPE
def get_rpc_callbacks(self, context, agent, sg_agent):
return MacvtapRPCCallBack(context, agent, sg_agent)
def get_rpc_consumers(self):
consumers = [[topics.PORT, topics.UPDATE],
[topics.NETWORK, topics.DELETE],
[topics.SECURITY_GROUP, topics.UPDATE]]
return consumers
def plug_interface(self, network_id, network_segment, device,
device_owner):
# Setting ALLMULTICAST Flag on macvtap device to allow the guest
# receiving traffic for arbitrary multicast addresses.
# The alternative would be to let libvirt instantiate the macvtap
# device with the 'trustGuestRxFilters' option. But doing so, the guest
# would be able to change its mac address and therefore the mac
# address of the macvtap device.
dev = ip_lib.IPDevice(self.mac_device_name_mappings[device])
dev.link.set_allmulticast_on()
return True
def setup_arp_spoofing_protection(self, device, device_details):
pass
def delete_arp_spoofing_protection(self, devices):
pass
def delete_unreferenced_arp_protection(self, current_devices):
pass
def parse_interface_mappings():
try:
interface_mappings = n_utils.parse_mappings(
cfg.CONF.macvtap.physical_interface_mappings)
LOG.info(_LI("Interface mappings: %s"), interface_mappings)
return interface_mappings
except ValueError as e:
LOG.error(_LE("Parsing physical_interface_mappings failed: %s. "
"Agent terminated!"), e)
sys.exit(1)
def validate_firewall_driver():
fw_driver = cfg.CONF.SECURITYGROUP.firewall_driver
if fw_driver != 'neutron.agent.firewall.NoopFirewallDriver':
LOG.error(_LE('Unsupported configuration option for "SECURITYGROUP.'
'firewall_driver"! Only "neutron.agent.firewall.'
'NoopFirewallDriver" is supported by macvtap agent, but'
'"%s" is configured. Agent terminated!'),
fw_driver)
sys.exit(1)
def main():
common_config.init(sys.argv[1:])
common_config.setup_logging()
validate_firewall_driver()
interface_mappings = parse_interface_mappings()
manager = MacvtapManager(interface_mappings)
polling_interval = cfg.CONF.AGENT.polling_interval
quitting_rpc_timeout = cfg.CONF.AGENT.quitting_rpc_timeout
agent = ca.CommonAgentLoop(manager, polling_interval,
quitting_rpc_timeout,
constants.AGENT_TYPE_MACVTAP,
MACVTAP_AGENT_BINARY)
LOG.info(_LI("Agent initialized successfully, now running... "))
launcher = service.launch(cfg.CONF, agent)
launcher.wait()

View File

@ -53,6 +53,7 @@ PORT_PREFIX = 'port'
VETH0_PREFIX = 'test-veth0'
VETH1_PREFIX = 'test-veth1'
PATCH_PREFIX = 'patch'
MACVTAP_PREFIX = 'macvtap'
# port name should be shorter than DEVICE_NAME_MAX_LEN because if this
# port is used to provide vlan connection between two linuxbridge
@ -543,6 +544,40 @@ class NamedVethFixture(VethFixture):
return name
class MacvtapFixture(fixtures.Fixture):
"""Create a macvtap.
:param src_dev: source device for macvtap
:type src_dev: IPDevice
:param mode: mode of macvtap
:type mode: string
:ivar ip_dev: created macvtap
:type ip_dev: IPDevice
"""
def __init__(self, src_dev=None, mode=None, prefix=MACVTAP_PREFIX):
super(MacvtapFixture, self).__init__()
self.src_dev = src_dev
self.mode = mode
self.prefix = prefix
def _setUp(self):
ip_wrapper = ip_lib.IPWrapper()
self.ip_dev = common_base.create_resource(
self.prefix,
ip_wrapper.add_macvtap,
self.src_dev, mode=self.mode)
self.addCleanup(self.destroy)
def destroy(self):
ip_wrapper = ip_lib.IPWrapper(self.ip_dev.namespace)
if (ip_wrapper.netns.exists(self.ip_dev.namespace) or
self.ip_dev.namespace is None):
try:
self.ip_dev.link.delete()
except RuntimeError:
pass
@six.add_metaclass(abc.ABCMeta)
class PortFixture(fixtures.Fixture):
"""Create a port.

View File

@ -0,0 +1,36 @@
# Copyright (c) 2016 IBM Corp.
#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from neutron.common import constants
from neutron.plugins.ml2.drivers.macvtap.agent import macvtap_neutron_agent
from neutron.tests.common import net_helpers
from neutron.tests.functional import base as functional_base
class MacvtapAgentTestCase(functional_base.BaseSudoTestCase):
def setUp(self):
super(MacvtapAgentTestCase, self).setUp()
self.mgr = macvtap_neutron_agent.MacvtapManager({})
def test_get_all_devices(self):
# Veth is simulating the hosts eth device. In this test it is used as
# src_dev for the macvtap
veth1, veth2 = self.useFixture(net_helpers.VethFixture()).ports
macvtap = self.useFixture(net_helpers.MacvtapFixture(
src_dev=veth1.name, mode='bridge',
prefix=constants.MACVTAP_DEVICE_PREFIX)).ip_dev
self.assertEqual(set([macvtap.link.address]),
self.mgr.get_all_devices())

View File

@ -331,6 +331,15 @@ class TestIpWrapper(base.BaseTestCase):
run_as_root=True, namespace=None,
log_fail_as_error=True)
def test_add_macvtap(self):
ip_lib.IPWrapper().add_macvtap('macvtap0', 'eth0', 'bridge')
self.execute.assert_called_once_with([], 'link',
('add', 'link', 'eth0', 'name',
'macvtap0', 'type', 'macvtap',
'mode', 'bridge'),
run_as_root=True, namespace=None,
log_fail_as_error=True)
def test_del_veth(self):
ip_lib.IPWrapper().del_veth('fpr-1234')
self.execute.assert_called_once_with([], 'link',
@ -697,6 +706,10 @@ class TestIpLinkCommand(TestIPCmdBase):
self.link_cmd.set_address('aa:bb:cc:dd:ee:ff')
self._assert_sudo([], ('set', 'eth0', 'address', 'aa:bb:cc:dd:ee:ff'))
def test_set_allmulticast_on(self):
self.link_cmd.set_allmulticast_on()
self._assert_sudo([], ('set', 'eth0', 'allmulticast', 'on'))
def test_set_mtu(self):
self.link_cmd.set_mtu(1500)
self._assert_sudo([], ('set', 'eth0', 'mtu', 1500))

View File

@ -0,0 +1,237 @@
# Copyright (c) 2016 IBM Corp.
#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import sys
import mock
from oslo_config import cfg
from oslo_service import service
from neutron.agent.linux import ip_lib
from neutron.agent.linux import utils
from neutron.common import config as common_config
from neutron.common import topics
from neutron.common import utils as n_utils
from neutron.plugins.ml2.drivers.agent import _agent_manager_base as amb
from neutron.plugins.ml2.drivers.macvtap.agent import macvtap_neutron_agent
from neutron.plugins.ml2.drivers.macvtap import macvtap_common
from neutron.tests import base
INTERFACE_MAPPINGS = {'physnet1': 'eth1'}
NETWORK_ID = 'net-id123'
NETWORK_SEGMENT_VLAN = amb.NetworkSegment('vlan', 'physnet1', 1)
NETWORK_SEGMENT_FLAT = amb.NetworkSegment('flat', 'physnet1', None)
class TestMacvtapRPCCallbacks(base.BaseTestCase):
def setUp(self):
super(TestMacvtapRPCCallbacks, self).setUp()
agent = mock.Mock()
agent.mgr = mock.Mock()
agent.mgr.interface_mappings = INTERFACE_MAPPINGS
self.rpc = macvtap_neutron_agent.MacvtapRPCCallBack(mock.Mock(), agent,
mock.Mock())
def test_network_delete_vlan(self):
self.rpc.network_map = {NETWORK_ID: NETWORK_SEGMENT_VLAN}
with mock.patch.object(ip_lib.IpLinkCommand, 'delete') as mock_del,\
mock.patch.object(macvtap_common, 'get_vlan_device_name',
return_value='vlan1'),\
mock.patch.object(ip_lib.IPDevice, 'exists', return_value=True):
self.rpc.network_delete("anycontext", network_id=NETWORK_ID)
self.assertTrue(mock_del.called)
def test_network_delete_flat(self):
self.rpc.network_map = {NETWORK_ID: NETWORK_SEGMENT_FLAT}
with mock.patch.object(ip_lib.IpLinkCommand, 'delete') as mock_del:
self.rpc.network_delete(
"anycontext", network_id=NETWORK_SEGMENT_FLAT.segmentation_id)
self.assertFalse(mock_del.called)
def test_port_update(self):
port = {'id': 'port-id123', 'mac_address': 'mac1'}
self.rpc.port_update(context=None, port=port)
self.assertEqual(set(['mac1']), self.rpc.updated_devices)
class TestMacvtapManager(base.BaseTestCase):
def setUp(self):
super(TestMacvtapManager, self).setUp()
with mock.patch.object(ip_lib, 'device_exists', return_value=True):
self.mgr = macvtap_neutron_agent.MacvtapManager(INTERFACE_MAPPINGS)
def test_validate_interface_mappings_dev_exists(self):
good_mapping = {'physnet1': 'eth1', 'physnet2': 'eth2'}
self.mgr.interface_mappings = good_mapping
with mock.patch.object(ip_lib, 'device_exists', return_value=True)\
as mock_de:
self.mgr.validate_interface_mappings()
mock_de.assert_any_call('eth1')
mock_de.assert_any_call('eth2')
self.assertEqual(2, mock_de.call_count)
def test_validate_interface_mappings_dev_not_exists(self):
bad_mapping = {'physnet1': 'foo'}
self.mgr.interface_mappings = bad_mapping
with mock.patch.object(ip_lib, 'device_exists', return_value=False)\
as mock_de, mock.patch.object(sys, 'exit') as mock_exit:
self.mgr.validate_interface_mappings()
mock_de.assert_called_with('foo')
mock_exit.assert_called_once_with(1)
def _test_ensure_port_admin_state(self, admin_state):
dev = 'macvtap1'
mac = 'mac1'
self.mgr.mac_device_name_mappings = {mac: dev}
with mock.patch.object(ip_lib, 'IPDevice') as mock_ip_dev:
self.mgr.ensure_port_admin_state(mac, admin_state)
self.assertEqual(admin_state, mock_ip_dev(dev).link.set_up.called)
self.assertNotEqual(admin_state,
mock_ip_dev(dev).link.set_down.called)
def test_ensure_port_admin_state_up(self):
self._test_ensure_port_admin_state(True)
def test_ensure_port_admin_state_down(self):
self._test_ensure_port_admin_state(False)
def test_get_all_devices(self):
listing = ['foo', 'macvtap0', 'macvtap1', 'bar']
# set some mac mappings to make sure they are cleaned up
self.mgr.mac_device_name_mappings = {'foo': 'bar'}
with mock.patch.object(os, 'listdir', return_value=listing)\
as mock_ld,\
mock.patch.object(utils, 'get_interface_mac') as mock_gdn:
mock_gdn.side_effect = ['mac0', 'mac1']
result = self.mgr.get_all_devices()
mock_ld.assert_called_once_with(macvtap_neutron_agent.MACVTAP_FS)
self.assertEqual(set(['mac0', 'mac1']), result)
self.assertEqual({'mac0': 'macvtap0', 'mac1': 'macvtap1'},
self.mgr.mac_device_name_mappings)
def test_get_agent_configurations(self):
expected = {'interface_mappings': INTERFACE_MAPPINGS}
self.assertEqual(expected, self.mgr.get_agent_configurations())
def test_get_agent_id_ok(self):
mock_devices = [ip_lib.IPDevice('macvtap1')]
with mock.patch.object(ip_lib.IPWrapper, 'get_devices',
return_value=mock_devices),\
mock.patch.object(utils, 'get_interface_mac',
return_value='foo:bar'):
self.assertEqual('macvtapfoobar', self.mgr.get_agent_id())
def test_get_agent_id_fail(self):
mock_devices = []
with mock.patch.object(ip_lib.IPWrapper, 'get_devices',
return_value=mock_devices),\
mock.patch.object(sys, 'exit') as mock_exit:
self.mgr.get_agent_id()
mock_exit.assert_called_once_with(1)
def test_get_extension_driver_type(self):
self.assertEqual('macvtap', self.mgr.get_extension_driver_type())
def test_get_rpc_callbacks(self):
context = mock.Mock()
agent = mock.Mock()
sg_agent = mock.Mock()
obj = self.mgr.get_rpc_callbacks(context, agent, sg_agent)
self.assertIsInstance(obj, macvtap_neutron_agent.MacvtapRPCCallBack)
def test_get_rpc_consumers(self):
consumers = [[topics.PORT, topics.UPDATE],
[topics.NETWORK, topics.DELETE],
[topics.SECURITY_GROUP, topics.UPDATE]]
self.assertEqual(consumers, self.mgr.get_rpc_consumers())
def test_plug_interface(self):
self.mgr.mac_device_name_mappings['mac1'] = 'macvtap0'
with mock.patch.object(ip_lib.IpLinkCommand, 'set_allmulticast_on')\
as mock_sao:
self.mgr.plug_interface('network_id', 'network_segment', 'mac1',
'device_owner')
self.assertTrue(mock_sao.called)
class TestMacvtapMain(base.BaseTestCase):
def test_parse_interface_mappings_good(self):
cfg.CONF.set_override('physical_interface_mappings', 'good_mapping',
'macvtap')
with mock.patch.object(n_utils, 'parse_mappings',
return_value=INTERFACE_MAPPINGS):
mappings = macvtap_neutron_agent.parse_interface_mappings()
self.assertEqual(INTERFACE_MAPPINGS, mappings)
def test_parse_interface_mappings_bad(self):
cfg.CONF.set_override('physical_interface_mappings', 'bad_mapping',
'macvtap')
with mock.patch.object(n_utils, 'parse_mappings',
side_effect=ValueError('bad mapping')),\
mock.patch.object(sys, 'exit') as mock_exit:
macvtap_neutron_agent.parse_interface_mappings()
mock_exit.assert_called_with(1)
def test_validate_firewall_driver_noop(self):
cfg.CONF.set_override('firewall_driver',
'neutron.agent.firewall.NoopFirewallDriver',
'SECURITYGROUP')
macvtap_neutron_agent.validate_firewall_driver()
def test_validate_firewall_driver_other(self):
cfg.CONF.set_override('firewall_driver',
'foo',
'SECURITYGROUP')
with mock.patch.object(sys, 'exit')as mock_exit:
macvtap_neutron_agent.validate_firewall_driver()
mock_exit.assert_called_with(1)
def test_main(self):
cfg.CONF.set_override('quitting_rpc_timeout', 1, 'AGENT')
cfg.CONF.set_override('polling_interval', 2, 'AGENT')
mock_manager_return = mock.Mock(spec=amb.CommonAgentManagerBase)
mock_launch_return = mock.Mock()
with mock.patch.object(common_config, 'init'),\
mock.patch.object(common_config, 'setup_logging'),\
mock.patch.object(service, 'launch',
return_value=mock_launch_return) as mock_launch,\
mock.patch.object(macvtap_neutron_agent,
'parse_interface_mappings',
return_value=INTERFACE_MAPPINGS) as mock_pim,\
mock.patch.object(macvtap_neutron_agent,
'validate_firewall_driver') as mock_vfd,\
mock.patch('neutron.plugins.ml2.drivers.agent._common_agent.'
'CommonAgentLoop') as mock_loop,\
mock.patch('neutron.plugins.ml2.drivers.macvtap.agent.'
'macvtap_neutron_agent.MacvtapManager',
return_value=mock_manager_return) as mock_manager:
macvtap_neutron_agent.main()
self.assertTrue(mock_vfd.called)
self.assertTrue(mock_pim.called)
mock_manager.assert_called_with(INTERFACE_MAPPINGS)
mock_loop.assert_called_with(mock_manager_return, 2, 1,
'Macvtap agent',
'neutron-macvtap-agent')
self.assertTrue(mock_launch.called)
self.assertTrue(mock_launch_return.wait.called)

View File

@ -0,0 +1,19 @@
---
prelude: >
Adding MacVtap ML2 driver and L2 Agent as new vswitch choice
features:
- Libvirt qemu/kvm instances can now be attached via MacVtap in
bridge mode to a network. VLAN and FLAT attachments are
supported. Other attachmentes than compute are not supported.
issues:
- To ensure any kind of migration works between all compute nodes,
make sure that the same physical_interface_mappings is
configured on each MacVtap compute node. Having different
mappings could cause live migration to fail (if the configured
physical network interface does not exist on the target host), or
even worse, result in an instance placed on the wrong physical
network (if the physical network interface exists on the target
host, but is used by another physical network or not used at all
by OpenStack). Such an instance does not have access to its
configured networks anymore. It then has layer 2 connectivity to
either another OpenStack network, or one of the hosts networks.

View File

@ -49,6 +49,7 @@ console_scripts =
neutron-l3-agent = neutron.cmd.eventlet.agents.l3:main
neutron-linuxbridge-agent = neutron.cmd.eventlet.plugins.linuxbridge_neutron_agent:main
neutron-linuxbridge-cleanup = neutron.cmd.linuxbridge_cleanup:main
neutron-macvtap-agent = neutron.cmd.eventlet.plugins.macvtap_neutron_agent:main
neutron-metadata-agent = neutron.cmd.eventlet.agents.metadata:main
neutron-netns-cleanup = neutron.cmd.netns_cleanup:main
neutron-ns-metadata-proxy = neutron.cmd.eventlet.agents.metadata_proxy:main
@ -137,6 +138,7 @@ oslo.config.opts =
neutron.metering.agent = neutron.opts:list_metering_agent_opts
neutron.ml2 = neutron.opts:list_ml2_conf_opts
neutron.ml2.linuxbridge.agent = neutron.opts:list_linux_bridge_opts
neutron.ml2.macvtap.agent = neutron.opts:list_macvtap_opts
neutron.ml2.ovs.agent = neutron.opts:list_ovs_opts
neutron.ml2.sriov = neutron.opts:list_ml2_conf_sriov_opts
neutron.ml2.sriov.agent = neutron.opts:list_sriov_agent_opts