Add agent extension 'dhcp' for ovs agent
Add a new ovs agent extension to support distributed DHCP for VMs in compute nodes directly. For large scale deployment, this can be used to reduce the number of neutron agents. Large scale cloud can benefit from it. From the perspective of virtual machine, this will reduce the probability of DHCP request failure. The VMs will get a higher level availability for DHCP R/R, no single point of failure permanently. If one host goes down, VMs in other hosts will not be influnced by it. For the perspective of network performance, after using this extension, the DHCP broadcasting packages will be limited to the host locally. Partially-Implements: bp/distributed-dhcp-for-ml2-ovs Closes-Bug: #1900934 Change-Id: Id8a4c501daad7c2185e6d69441182666ef987e61
This commit is contained in:
parent
ad2bc847ab
commit
56e8498a4d
155
neutron/agent/l2/extensions/dhcp/extension.py
Normal file
155
neutron/agent/l2/extensions/dhcp/extension.py
Normal 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)
|
@ -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.")),
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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.'''
|
||||
|
@ -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)
|
||||
|
@ -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)
|
@ -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=[],
|
||||
|
@ -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')
|
||||
|
@ -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()
|
||||
|
@ -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.
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user