diff --git a/neutron/agent/l2/extensions/dhcp/extension.py b/neutron/agent/l2/extensions/dhcp/extension.py new file mode 100644 index 00000000000..df57710dcbe --- /dev/null +++ b/neutron/agent/l2/extensions/dhcp/extension.py @@ -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) diff --git a/neutron/conf/plugins/ml2/drivers/ovs_conf.py b/neutron/conf/plugins/ml2/drivers/ovs_conf.py index 2f5f47706d2..ba7a3990512 100644 --- a/neutron/conf/plugins/ml2/drivers/ovs_conf.py +++ b/neutron/conf/plugins/ml2/drivers/ovs_conf.py @@ -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.")), diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/common/constants.py b/neutron/plugins/ml2/drivers/openvswitch/agent/common/constants.py index 3497e4286fd..ab1ad764267 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/common/constants.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/common/constants.py @@ -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, diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_int.py b/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_int.py index 415cfc1b2e9..149c5fb7db0 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_int.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_int.py @@ -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) diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_agent_extension_api.py b/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_agent_extension_api.py index 1564446df61..c6ec265a0b2 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_agent_extension_api.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_agent_extension_api.py @@ -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 diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py b/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py index 862b5aab01e..dafd2b99da8 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py @@ -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 = ( @@ -281,7 +283,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) @@ -1351,7 +1354,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.''' diff --git a/neutron/tests/unit/agent/l2/extensions/dhcp/test_base.py b/neutron/tests/unit/agent/l2/extensions/dhcp/test_base.py index eba171056c5..e414a9f1939 100644 --- a/neutron/tests/unit/agent/l2/extensions/dhcp/test_base.py +++ b/neutron/tests/unit/agent/l2/extensions/dhcp/test_base.py @@ -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) diff --git a/neutron/tests/unit/agent/l2/extensions/dhcp/test_extension.py b/neutron/tests/unit/agent/l2/extensions/dhcp/test_extension.py new file mode 100644 index 00000000000..c10d515ed31 --- /dev/null +++ b/neutron/tests/unit/agent/l2/extensions/dhcp/test_extension.py @@ -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) diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_int.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_int.py index 0c479dd656e..89e9daa0b0a 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_int.py +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_int.py @@ -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=[], diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py index c368d5971f2..0f612175a58 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py @@ -159,6 +159,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') @@ -237,6 +238,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) @@ -2904,6 +2906,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)) @@ -2944,6 +2947,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) @@ -3017,6 +3021,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') diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py index c4a685ce3d5..a452cf40374 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py @@ -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() diff --git a/releasenotes/notes/dhcp_ext_for_ovs_agent-ee895747687c7b58.yaml b/releasenotes/notes/dhcp_ext_for_ovs_agent-ee895747687c7b58.yaml new file mode 100644 index 00000000000..6c7f603b43a --- /dev/null +++ b/releasenotes/notes/dhcp_ext_for_ovs_agent-ee895747687c7b58.yaml @@ -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. diff --git a/setup.cfg b/setup.cfg index 9e78c5f29d7..dc272f0fb7a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -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