Merge "Add agent extension 'dhcp' for ovs agent"

This commit is contained in:
Zuul 2021-06-26 12:12:45 +00:00 committed by Gerrit Code Review
commit c90043f853
13 changed files with 426 additions and 7 deletions

View File

@ -0,0 +1,155 @@
# Copyright (c) 2021 China Unicom Cloud Data Co.,Ltd.
# Copyright (c) 2019 - 2020 China Telecom Corporation
# 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 netaddr
from neutron_lib.agent import l2_extension as l2_agent_extension
from neutron_lib import constants
from os_ken.base import app_manager
from oslo_config import cfg
from oslo_log import log as logging
from neutron.agent.l2.extensions.dhcp import ipv4
from neutron.agent.l2.extensions.dhcp import ipv6
from neutron.api.rpc.callbacks import resources
LOG = logging.getLogger(__name__)
class DHCPExtensionPortInfoAPI(object):
def __init__(self, cache_api):
self.cache_api = cache_api
def get_port_info(self, port_id):
port_obj = self.cache_api.get_resource_by_id(
resources.PORT, port_id)
if not port_obj or not port_obj.device_owner.startswith(
constants.DEVICE_OWNER_COMPUTE_PREFIX):
return
mac_addr = str(netaddr.EUI(str(port_obj.mac_address),
dialect=netaddr.mac_unix_expanded))
entry = {
'network_id': port_obj.network_id,
'port_id': port_obj.id,
'mac_address': mac_addr,
'admin_state_up': port_obj.admin_state_up,
'device_owner': port_obj.device_owner
}
# fixed_ips info for DHCP
fixed_ips = []
for ip in port_obj.fixed_ips:
subnet = self.cache_api.get_resource_by_id(
resources.SUBNET, ip.subnet_id)
if not subnet.enable_dhcp:
continue
info = {'subnet_id': ip.subnet_id,
'ip_address': str(ip.ip_address),
'version': subnet.ip_version,
'cidr': subnet.cidr,
'host_routes': subnet.host_routes,
'dns_nameservers': subnet.dns_nameservers,
'gateway_ip': subnet.gateway_ip}
fixed_ips.append(info)
net = self.cache_api.get_resource_by_id(
resources.NETWORK, port_obj.network_id)
extra_info = {'fixed_ips': fixed_ips,
'mtu': net.mtu}
entry.update(extra_info)
LOG.debug("DHCP extension API return port info: %s", entry)
return entry
class DHCPAgentExtension(l2_agent_extension.L2AgentExtension):
VIF_PORT_CACHE = {}
def consume_api(self, agent_api):
"""Allows an extension to gain access to resources internal to the
neutron agent and otherwise unavailable to the extension.
"""
self.agent_api = agent_api
self.rcache_api = agent_api.plugin_rpc.remote_resource_cache
def initialize(self, connection, driver_type):
"""Initialize agent extension."""
self.ext_api = DHCPExtensionPortInfoAPI(self.rcache_api)
self.int_br = self.agent_api.request_int_br()
self.app_mgr = app_manager.AppManager.get_instance()
self.start_dhcp()
if cfg.CONF.DHCP.enable_ipv6:
self.start_dhcp(version=constants.IP_VERSION_6)
def start_dhcp(self, version=constants.IP_VERSION_4):
responder = (
ipv4.DHCPIPv4Responder if version == constants.IP_VERSION_4 else (
ipv6.DHCPIPv6Responder))
app = self.app_mgr.instantiate(
responder,
self.agent_api,
self.ext_api)
app.start()
if version == constants.IP_VERSION_4:
self.dhcp4_app = app
else:
self.dhcp6_app = app
def handle_port(self, context, port_detail):
fixed_ips = port_detail.get('fixed_ips')
port = port_detail['vif_port']
# TODO(liuyulong): DHCP for baremetal
if (not port_detail['device_owner'].startswith(
constants.DEVICE_OWNER_COMPUTE_PREFIX) or (
not fixed_ips)):
return
LOG.info("DHCP extension add DHCP related flows for port %s",
port_detail['port_id'])
self.int_br.add_dhcp_ipv4_flow(port_detail['port_id'],
port.ofport,
port.vif_mac)
if cfg.CONF.DHCP.enable_ipv6:
self.int_br.add_dhcp_ipv6_flow(port_detail['port_id'],
port.ofport,
port.vif_mac)
self.VIF_PORT_CACHE[port_detail['port_id']] = port
def get_ofport(self, port_id):
vifs = self.int_br.get_vif_ports()
for vif in vifs:
if vif.vif_id == port_id:
return vif
def delete_port(self, context, port_detail):
port_id = port_detail['port_id']
port = port_detail.get('vif_port')
cached_port = self.VIF_PORT_CACHE.pop(port_id, None)
if not port or port.ofport <= 0:
port = cached_port
if not port:
port = self.get_ofport(port_id)
if not port:
LOG.warning("DHCP extension skipping delete DHCP related flow, "
"failed to get port %s ofport and MAC.",
port_id)
return
LOG.info("DHCP extension remove DHCP related flows for port %s",
port_id)
self.int_br.del_dhcp_flow(port.ofport, port.vif_mac)

View File

@ -180,6 +180,10 @@ agent_opts = [
]
dhcp_opts = [
cfg.BoolOpt('enable_ipv6', default=True,
help=_("When set to True, the OVS agent DHCP "
"extension will add related flows for "
"DHCPv6 packets.")),
cfg.IntOpt('renewal_time', default=0,
help=_("DHCP renewal time T1 (in seconds). If set to 0, it "
"will default to half of the lease time.")),

View File

@ -62,6 +62,10 @@ TRANSIENT_TABLE = 60
LOCAL_MAC_DIRECT = 61
TRANSIENT_EGRESS_TABLE = 62
# Table for DHCP
DHCP_IPV4_TABLE = 77
DHCP_IPV6_TABLE = 78
# Tables used for ovs firewall
BASE_EGRESS_TABLE = 71
RULES_EGRESS_TABLE = 72
@ -96,6 +100,8 @@ INT_BR_ALL_TABLES = (
BASE_EGRESS_TABLE,
RULES_EGRESS_TABLE,
ACCEPT_OR_INGRESS_TABLE,
DHCP_IPV4_TABLE,
DHCP_IPV6_TABLE,
BASE_INGRESS_TABLE,
RULES_INGRESS_TABLE,
ACCEPTED_EGRESS_TRAFFIC_TABLE,

View File

@ -21,6 +21,7 @@
import netaddr
from neutron_lib import constants as lib_consts
from os_ken.lib.packet import ether_types
from os_ken.lib.packet import icmpv6
from os_ken.lib.packet import in_proto
@ -35,6 +36,12 @@ from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.native \
LOG = logging.getLogger(__name__)
# TODO(liuyulong): move to neutron-lib.
IPV4_NETWORK_BROADCAST = "255.255.255.255"
# All_DHCP_Relay_Agents_and_Servers
# [RFC8415] https://datatracker.ietf.org/doc/html/rfc8415
IPV6_All_DHCP_RELAY_AGENYS_AND_SERVERS = "ff02::1:2"
class OVSIntegrationBridge(ovs_bridge.OVSAgentBridge,
br_dvr_process.OVSDVRInterfaceMixin):
@ -42,10 +49,13 @@ class OVSIntegrationBridge(ovs_bridge.OVSAgentBridge,
of_tables = constants.INT_BR_ALL_TABLES
def setup_default_table(self):
def setup_default_table(self, enable_openflow_dhcp=False,
enable_dhcpv6=False):
self.setup_canary_table()
self.install_goto(dest_table_id=constants.TRANSIENT_TABLE)
self.install_normal(table_id=constants.TRANSIENT_TABLE, priority=3)
self.init_dhcp(enable_openflow_dhcp=enable_openflow_dhcp,
enable_dhcpv6=enable_dhcpv6)
self.install_drop(table_id=constants.ARP_SPOOF_TABLE)
self.install_drop(table_id=constants.LOCAL_SWITCHING,
priority=constants.OPENFLOW_MAX_PRIORITY,
@ -55,6 +65,87 @@ class OVSIntegrationBridge(ovs_bridge.OVSAgentBridge,
self.install_normal(table_id=constants.TRANSIENT_EGRESS_TABLE,
priority=3)
def init_dhcp(self, enable_openflow_dhcp=False, enable_dhcpv6=False):
if not enable_openflow_dhcp:
return
# DHCP IPv4
self.install_goto(dest_table_id=constants.DHCP_IPV4_TABLE,
table_id=constants.TRANSIENT_TABLE,
priority=101,
eth_type=ether_types.ETH_TYPE_IP,
ip_proto=in_proto.IPPROTO_UDP,
ipv4_dst=IPV4_NETWORK_BROADCAST,
udp_src=lib_consts.DHCP_CLIENT_PORT,
udp_dst=lib_consts.DHCP_RESPONSE_PORT)
self.install_drop(table_id=constants.DHCP_IPV4_TABLE)
if not enable_dhcpv6:
return
# DHCP IPv6
self.install_goto(dest_table_id=constants.DHCP_IPV6_TABLE,
table_id=constants.TRANSIENT_TABLE,
priority=101,
eth_type=ether_types.ETH_TYPE_IPV6,
ip_proto=in_proto.IPPROTO_UDP,
ipv6_dst=IPV6_All_DHCP_RELAY_AGENYS_AND_SERVERS,
udp_src=lib_consts.DHCPV6_CLIENT_PORT,
udp_dst=lib_consts.DHCPV6_RESPONSE_PORT)
self.install_drop(table_id=constants.DHCP_IPV6_TABLE)
def add_dhcp_ipv4_flow(self, port_id, ofport, port_mac):
(_dp, ofp, ofpp) = self._get_dp()
match = ofpp.OFPMatch(eth_type=ether_types.ETH_TYPE_IP,
ip_proto=in_proto.IPPROTO_UDP,
in_port=ofport,
eth_src=port_mac,
udp_src=68,
udp_dst=67)
actions = [
ofpp.OFPActionOutput(ofp.OFPP_CONTROLLER, 0),
]
instructions = [
ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, actions),
]
self.install_instructions(table_id=constants.DHCP_IPV4_TABLE,
priority=100,
instructions=instructions,
match=match)
def add_dhcp_ipv6_flow(self, port_id, ofport, port_mac):
(_dp, ofp, ofpp) = self._get_dp()
match = ofpp.OFPMatch(eth_type=ether_types.ETH_TYPE_IPV6,
ip_proto=in_proto.IPPROTO_UDP,
in_port=ofport,
eth_src=port_mac,
udp_src=546,
udp_dst=547)
actions = [
ofpp.OFPActionOutput(ofp.OFPP_CONTROLLER, 0),
]
instructions = [
ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, actions),
]
self.install_instructions(table_id=constants.DHCP_IPV6_TABLE,
priority=100,
instructions=instructions,
match=match)
def del_dhcp_flow(self, ofport, port_mac):
self.uninstall_flows(table_id=constants.DHCP_IPV4_TABLE,
eth_type=ether_types.ETH_TYPE_IP,
ip_proto=in_proto.IPPROTO_UDP,
in_port=ofport,
eth_src=port_mac,
udp_src=68,
udp_dst=67)
self.uninstall_flows(table_id=constants.DHCP_IPV6_TABLE,
eth_type=ether_types.ETH_TYPE_IPV6,
ip_proto=in_proto.IPPROTO_UDP,
in_port=ofport,
eth_src=port_mac,
udp_src=546,
udp_dst=547)
def setup_canary_table(self):
self.install_drop(constants.CANARY_TABLE)

View File

@ -40,11 +40,13 @@ class OVSAgentExtensionAPI(object):
method which has been added to the AgentExtension class.
'''
def __init__(self, int_br, tun_br, phys_brs=None):
def __init__(self, int_br, tun_br, phys_brs=None,
plugin_rpc=None):
super(OVSAgentExtensionAPI, self).__init__()
self.br_int = int_br
self.br_tun = tun_br
self.br_phys = phys_brs or {}
self.plugin_rpc = plugin_rpc
def request_int_br(self):
"""Allows extensions to request an integration bridge to use for

View File

@ -158,6 +158,8 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
agent_conf = self.conf.AGENT
ovs_conf = self.conf.OVS
self.enable_openflow_dhcp = 'dhcp' in self.ext_manager.names()
self.fullsync = False
# init bridge classes with configured datapath type.
self.br_int_cls, self.br_phys_cls, self.br_tun_cls = (
@ -284,7 +286,8 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
agent_api = ovs_ext_api.OVSAgentExtensionAPI(self.int_br,
self.tun_br,
self.phys_brs)
self.phys_brs,
self.plugin_rpc)
self.ext_manager.initialize(
self.connection, constants.EXTENSION_DRIVER_TYPE, agent_api)
@ -1354,7 +1357,10 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
# while flows are missing.
self.int_br.delete_port(self.conf.OVS.int_peer_patch_port)
self.int_br.uninstall_flows(cookie=ovs_lib.COOKIE_ANY)
self.int_br.setup_default_table()
self.int_br.setup_default_table(
enable_openflow_dhcp=self.enable_openflow_dhcp,
enable_dhcpv6=self.conf.DHCP.enable_ipv6)
def setup_ancillary_bridges(self, integ_br, tun_br):
'''Setup ancillary bridges - for example br-ex.'''

View File

@ -109,10 +109,14 @@ class DHCPResponderBaseTestCase(base.BaseTestCase):
super(DHCPResponderBaseTestCase, self).setUp()
self.int_br = mock.Mock()
self.tun_br = mock.Mock()
self.plugin_rpc = mock.Mock()
self.remote_resource_cache = mock.Mock()
self.plugin_rpc.remote_resource_cache = self.remote_resource_cache
self.agent_api = ovs_ext_api.OVSAgentExtensionAPI(
self.int_br,
self.tun_br,
phys_brs=None)
phys_brs=None,
plugin_rpc=self.plugin_rpc)
self.ext_api = mock.Mock()
self.base_responer = dhcp_resp_base.DHCPResponderBase(self.agent_api,
self.ext_api)

View File

@ -0,0 +1,96 @@
# Copyright (c) 2021 China Unicom Cloud Data Co.,Ltd.
# 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 unittest import mock
from neutron_lib import context
from oslo_config import cfg
from neutron.agent.common import ovs_lib
from neutron.agent.l2.extensions.dhcp import extension as ovs_dhcp
from neutron.plugins.ml2.drivers.openvswitch.agent \
import ovs_agent_extension_api as ovs_ext_api
from neutron.tests import base
class DHCPAgentExtensionTestCase(base.BaseTestCase):
def setUp(self):
super(DHCPAgentExtensionTestCase, self).setUp()
cfg.CONF.set_override('enable_ipv6', True, group='DHCP')
self.context = context.get_admin_context()
self.int_br = mock.Mock()
self.tun_br = mock.Mock()
self.plugin_rpc = mock.Mock()
self.remote_resource_cache = mock.Mock()
self.plugin_rpc.remote_resource_cache = self.remote_resource_cache
self.ovs_dhcp = ovs_dhcp.DHCPAgentExtension()
self.agent_api = ovs_ext_api.OVSAgentExtensionAPI(
self.int_br,
self.tun_br,
phys_brs=None,
plugin_rpc=self.plugin_rpc)
self.ovs_dhcp.consume_api(self.agent_api)
self.ovs_dhcp.initialize(None, None)
def tearDown(self):
self.ovs_dhcp.app_mgr.uninstantiate(self.ovs_dhcp.dhcp4_app.name)
self.ovs_dhcp.app_mgr.uninstantiate(self.ovs_dhcp.dhcp6_app.name)
super(DHCPAgentExtensionTestCase, self).tearDown()
def test_handle_port(self):
port = {"port_id": "p1",
"fixed_ips": [{"ip_address": "1.1.1.1"}],
"vif_port": ovs_lib.VifPort("tap-p1", "1", "p1",
"aa:aa:aa:aa:aa:aa", "br-int"),
"device_owner": "compute:test"}
self.ovs_dhcp.handle_port(self.context, port)
self.ovs_dhcp.int_br.add_dhcp_ipv4_flow.assert_called_once_with(
port['port_id'],
port["vif_port"].ofport,
port["vif_port"].vif_mac)
self.ovs_dhcp.int_br.add_dhcp_ipv6_flow.assert_called_once_with(
port['port_id'],
port["vif_port"].ofport,
port["vif_port"].vif_mac)
self.assertIsNotNone(self.ovs_dhcp.VIF_PORT_CACHE.get(port['port_id']))
def _test_delete_port(self, with_vif_port=False):
vif_port = ovs_lib.VifPort("tap-p1", 1, "p1",
"aa:aa:aa:aa:aa:aa", "br-int")
port1 = {"port_id": "p1",
"fixed_ips": [{"ip_address": "1.1.1.1"}],
"vif_port": vif_port,
"device_owner": "compute:test"}
self.ovs_dhcp.handle_port(self.context, port1)
with mock.patch.object(self.ovs_dhcp.int_br, "get_vif_ports",
return_value=[]):
if with_vif_port:
port2 = {"port_id": "p1",
"vif_port": vif_port}
else:
port2 = {"port_id": "p1"}
self.ovs_dhcp.delete_port(self.context, port2)
self.ovs_dhcp.int_br.del_dhcp_flow.assert_called_once_with(
port1["vif_port"].ofport,
port1["vif_port"].vif_mac)
# verify the cache
self.assertNotIn("p1", self.ovs_dhcp.VIF_PORT_CACHE.keys())
def test_delete_port_without_vif_port(self):
self._test_delete_port()
def test_delete_port_with_vif_port(self):
self._test_delete_port(with_vif_port=True)

View File

@ -33,7 +33,8 @@ class OVSIntegrationBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase):
self.stamp = self.br.default_cookie
def test_setup_default_table(self):
self.br.setup_default_table()
self.br.setup_default_table(enable_openflow_dhcp=True,
enable_dhcpv6=True)
(dp, ofp, ofpp) = self._get_dp()
expected = [
call._send_msg(ofpp.OFPFlowMod(dp,
@ -64,6 +65,42 @@ class OVSIntegrationBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase):
priority=3,
table_id=60),
active_bundle=None),
call._send_msg(ofpp.OFPFlowMod(dp,
cookie=self.stamp,
instructions=[ofpp.OFPInstructionGotoTable(table_id=77)],
match=ofpp.OFPMatch(eth_type=self.ether_types.ETH_TYPE_IP,
ip_proto=self.in_proto.IPPROTO_UDP,
ipv4_dst="255.255.255.255",
udp_dst=67,
udp_src=68),
priority=101,
table_id=60),
active_bundle=None),
call._send_msg(ofpp.OFPFlowMod(dp,
cookie=self.stamp,
instructions=[],
match=ofpp.OFPMatch(),
priority=0,
table_id=77),
active_bundle=None),
call._send_msg(ofpp.OFPFlowMod(dp,
cookie=self.stamp,
instructions=[ofpp.OFPInstructionGotoTable(table_id=78)],
match=ofpp.OFPMatch(eth_type=self.ether_types.ETH_TYPE_IPV6,
ip_proto=self.in_proto.IPPROTO_UDP,
ipv6_dst="ff02::1:2",
udp_dst=547,
udp_src=546),
priority=101,
table_id=60),
active_bundle=None),
call._send_msg(ofpp.OFPFlowMod(dp,
cookie=self.stamp,
instructions=[],
match=ofpp.OFPMatch(),
priority=0,
table_id=78),
active_bundle=None),
call._send_msg(ofpp.OFPFlowMod(dp,
cookie=self.stamp,
instructions=[],

View File

@ -160,6 +160,7 @@ class TestOvsNeutronAgent(object):
mock.patch('neutron.agent.rpc.PluginReportStateAPI.'
'has_alive_neutron_server'):
ext_manager = mock.Mock()
ext_manager.names = mock.Mock(return_value=[])
agent = self.mod_agent.OVSNeutronAgent(self._bridge_classes(),
ext_manager, cfg.CONF)
agent.tun_br = self.br_tun_cls(br_name='br-tun')
@ -238,6 +239,7 @@ class TestOvsNeutronAgent(object):
expected,
group='OVS')
ext_manager = mock.Mock()
ext_manager.names = mock.Mock(return_value=[])
self.agent = self.mod_agent.OVSNeutronAgent(self._bridge_classes(),
ext_manager, cfg.CONF)
self.assertEqual(expected, self.agent.int_br.datapath_type)
@ -2922,6 +2924,7 @@ class AncillaryBridgesTest(object):
mock.patch('neutron.agent.rpc.PluginReportStateAPI.'
'has_alive_neutron_server'):
ext_manager = mock.Mock()
ext_manager.names = mock.Mock(return_value=[])
self.agent = self.mod_agent.OVSNeutronAgent(self._bridge_classes(),
ext_manager, cfg.CONF)
self.assertEqual(len(ancillary), len(self.agent.ancillary_brs))
@ -2962,6 +2965,7 @@ class AncillaryBridgesTest(object):
mock.patch('neutron.agent.rpc.PluginReportStateAPI.'
'has_alive_neutron_server'):
ext_manager = mock.Mock()
ext_manager.names = mock.Mock(return_value=[])
self.agent = self.mod_agent.OVSNeutronAgent(self._bridge_classes(),
ext_manager, cfg.CONF)
return self.agent.scan_ancillary_ports(registered_ports, sync)
@ -3035,6 +3039,7 @@ class TestOvsDvrNeutronAgent(object):
mock.patch('neutron.agent.rpc.PluginReportStateAPI.'
'has_alive_neutron_server'):
ext_manager = mock.Mock()
ext_manager.names = mock.Mock(return_value=[])
self.agent = self.mod_agent.OVSNeutronAgent(self._bridge_classes(),
ext_manager, cfg.CONF)
self.agent.tun_br = self.br_tun_cls(br_name='br-tun')

View File

@ -210,7 +210,8 @@ class TunnelTest(object):
mock.call.set_secure_mode(),
mock.call.setup_controllers(mock.ANY),
mock.call.set_igmp_snooping_state(igmp_snooping),
mock.call.setup_default_table(),
mock.call.setup_default_table(enable_openflow_dhcp=False,
enable_dhcpv6=False),
]
self.mock_map_tun_bridge_expected = [
@ -314,11 +315,13 @@ class TunnelTest(object):
cfg.CONF.set_override('tunnel_types', ['gre'], 'AGENT')
cfg.CONF.set_override('veth_mtu', self.VETH_MTU, 'AGENT')
cfg.CONF.set_override('minimize_polling', False, 'AGENT')
cfg.CONF.set_override('enable_ipv6', False, 'DHCP')
for k, v in config_opts_agent.items():
cfg.CONF.set_override(k, v, 'AGENT')
ext_mgr = mock.Mock()
ext_mgr.names = mock.Mock(return_value=[])
agent = self.mod_agent.OVSNeutronAgent(
bridge_classes, ext_mgr, cfg.CONF)
mock.patch.object(agent.ovs.ovsdb, 'idl_monitor').start()

View File

@ -0,0 +1,9 @@
---
features:
- |
Added a new OVS agent extension ``dhcp`` to support distributed
DHCP for VMs in compute nodes directly. To enable this just
set ``extensions=dhcp`` to OVS agent config file under ``[agent]``
section. We also add a new config section ``[dhcp]`` which
has options ``enable_ipv6 = True/False`` for indicating whether
enable the DHCPv6 for VM ports.

View File

@ -126,6 +126,7 @@ neutron.agent.l2.extensions =
qos = neutron.agent.l2.extensions.qos:QosAgentExtension
fdb = neutron.agent.l2.extensions.fdb_population:FdbPopulationAgentExtension
log = neutron.services.logapi.agent.log_extension:LoggingExtension
dhcp = neutron.agent.l2.extensions.dhcp.extension:DHCPAgentExtension
neutron.agent.l3.extensions =
fip_qos = neutron.agent.l3.extensions.qos.fip:FipQosAgentExtension
gateway_ip_qos = neutron.agent.l3.extensions.qos.gateway_ip:RouterGatewayIPQosAgentExtension