From eab71473c3a1d40ec3f1d8a84b839b06b140e4fe Mon Sep 17 00:00:00 2001 From: YAMAMOTO Takashi Date: Mon, 2 Mar 2015 13:14:48 +0900 Subject: [PATCH] OVS-agent: Separate ovs-ofctl using code as a driver This is a preparation to introduce another Ryu-based implementation. The aim is to replace this with the new Ryu-based implementation eventually. Add a config option for OVS-agent which selects the implementation. Currently, the only available choice is 'ovs-ofctl'. Also, this commit simplifies DVR logics by reducing duplications and makes some of DVR UTs actually check the flows rather than just "add_flow is called". Partially-Implements: blueprint ovs-ofctl-to-python Change-Id: Ie1224f8a1c17268cd7d1c474ed82fdfb8852eaa8 --- .../openvswitch/ovs_neutron_plugin.ini | 4 + .../cmd/eventlet/plugins/ovs_neutron_agent.py | 4 +- neutron/plugins/openvswitch/agent/main.py | 45 + .../openvswitch/agent/openflow/__init__.py | 0 .../agent/openflow/ovs_ofctl/__init__.py | 0 .../openflow/ovs_ofctl/br_dvr_process.py | 89 + .../agent/openflow/ovs_ofctl/br_int.py | 133 ++ .../agent/openflow/ovs_ofctl/br_phys.py | 59 + .../agent/openflow/ovs_ofctl/br_tun.py | 246 +++ .../agent/openflow/ovs_ofctl/main.py | 33 + .../agent/openflow/ovs_ofctl/ofswitch.py | 74 + .../agent/openflow/ovs_ofctl/ovs_bridge.py | 30 + .../agent/ovs_dvr_neutron_agent.py | 405 ++--- .../openvswitch/agent/ovs_neutron_agent.py | 386 ++--- neutron/plugins/openvswitch/common/config.py | 2 + .../tests/functional/agent/test_ovs_flows.py | 85 +- .../openvswitch/agent/openflow/__init__.py | 0 .../agent/openflow/ovs_ofctl/__init__.py | 0 .../ovs_ofctl/ovs_bridge_test_base.py | 160 ++ .../agent/openflow/ovs_ofctl/test_br_int.py | 213 +++ .../agent/openflow/ovs_ofctl/test_br_phys.py | 97 ++ .../agent/openflow/ovs_ofctl/test_br_tun.py | 259 +++ .../openvswitch/agent/ovs_test_base.py | 54 + .../agent/test_ovs_neutron_agent.py | 1459 ++++++++--------- .../plugins/openvswitch/test_ovs_tunnel.py | 363 ++-- 25 files changed, 2717 insertions(+), 1483 deletions(-) create mode 100644 neutron/plugins/openvswitch/agent/main.py create mode 100644 neutron/plugins/openvswitch/agent/openflow/__init__.py create mode 100644 neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/__init__.py create mode 100644 neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/br_dvr_process.py create mode 100644 neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/br_int.py create mode 100644 neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/br_phys.py create mode 100644 neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/br_tun.py create mode 100644 neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/main.py create mode 100644 neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/ofswitch.py create mode 100644 neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/ovs_bridge.py create mode 100644 neutron/tests/unit/plugins/openvswitch/agent/openflow/__init__.py create mode 100644 neutron/tests/unit/plugins/openvswitch/agent/openflow/ovs_ofctl/__init__.py create mode 100644 neutron/tests/unit/plugins/openvswitch/agent/openflow/ovs_ofctl/ovs_bridge_test_base.py create mode 100644 neutron/tests/unit/plugins/openvswitch/agent/openflow/ovs_ofctl/test_br_int.py create mode 100644 neutron/tests/unit/plugins/openvswitch/agent/openflow/ovs_ofctl/test_br_phys.py create mode 100644 neutron/tests/unit/plugins/openvswitch/agent/openflow/ovs_ofctl/test_br_tun.py create mode 100644 neutron/tests/unit/plugins/openvswitch/agent/ovs_test_base.py diff --git a/etc/neutron/plugins/openvswitch/ovs_neutron_plugin.ini b/etc/neutron/plugins/openvswitch/ovs_neutron_plugin.ini index 42673cf1b..85586c596 100644 --- a/etc/neutron/plugins/openvswitch/ovs_neutron_plugin.ini +++ b/etc/neutron/plugins/openvswitch/ovs_neutron_plugin.ini @@ -53,6 +53,10 @@ # ovs-vsctl set-manager ptcp:6640:127.0.0.1 # ovsdb_connection = tcp:127.0.0.1:6640 +# (StrOpt) OpenFlow interface to use. +# 'ovs-ofctl' is currently the only available choice. +# of_interface = ovs-ofctl + [agent] # Agent's polling interval in seconds # polling_interval = 2 diff --git a/neutron/cmd/eventlet/plugins/ovs_neutron_agent.py b/neutron/cmd/eventlet/plugins/ovs_neutron_agent.py index ef686f86c..2d545bc53 100644 --- a/neutron/cmd/eventlet/plugins/ovs_neutron_agent.py +++ b/neutron/cmd/eventlet/plugins/ovs_neutron_agent.py @@ -13,8 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. -from neutron.plugins.openvswitch.agent import ovs_neutron_agent +import neutron.plugins.openvswitch.agent.main as agent_main def main(): - ovs_neutron_agent.main() + agent_main.main() diff --git a/neutron/plugins/openvswitch/agent/main.py b/neutron/plugins/openvswitch/agent/main.py new file mode 100644 index 000000000..7dbac9fbe --- /dev/null +++ b/neutron/plugins/openvswitch/agent/main.py @@ -0,0 +1,45 @@ +# Copyright (C) 2014,2015 VA Linux Systems Japan K.K. +# Copyright (C) 2014 Fumihiko Kakuma +# Copyright (C) 2014,2015 YAMAMOTO Takashi +# 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 sys + +from oslo_config import cfg +from oslo_log import log as logging +from oslo_utils import importutils + +from neutron.common import config as common_config +from neutron.common import utils as n_utils + + +LOG = logging.getLogger(__name__) +cfg.CONF.import_group('OVS', 'neutron.plugins.openvswitch.common.config') + + +_main_modules = { + 'ovs-ofctl': 'neutron.plugins.openvswitch.agent.openflow.ovs_ofctl.main', +} + + +def main(): + common_config.init(sys.argv[1:]) + driver_name = cfg.CONF.OVS.of_interface + mod_name = _main_modules[driver_name] + mod = importutils.import_module(mod_name) + mod.init_config() + common_config.setup_logging() + n_utils.log_opt_values(LOG) + mod.main() diff --git a/neutron/plugins/openvswitch/agent/openflow/__init__.py b/neutron/plugins/openvswitch/agent/openflow/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/__init__.py b/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/br_dvr_process.py b/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/br_dvr_process.py new file mode 100644 index 000000000..46db4ec69 --- /dev/null +++ b/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/br_dvr_process.py @@ -0,0 +1,89 @@ +# Copyright (C) 2014,2015 VA Linux Systems Japan K.K. +# Copyright (C) 2014,2015 YAMAMOTO Takashi +# 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. + +# Copyright 2011 VMware, Inc. +# 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. + + +class OVSDVRProcessMixin(object): + """Common logic for br-tun and br-phys' DVR_PROCESS tables. + + Inheriters should provide self.dvr_process_table_id and + self.dvr_process_next_table_id. + """ + + def install_dvr_process_ipv4(self, vlan_tag, gateway_ip): + # block ARP + self.add_flow(table=self.dvr_process_table_id, + priority=3, + dl_vlan=vlan_tag, + proto='arp', + nw_dst=gateway_ip, + actions='drop') + + def delete_dvr_process_ipv4(self, vlan_tag, gateway_ip): + self.delete_flows(table=self.dvr_process_table_id, + dl_vlan=vlan_tag, + proto='arp', + nw_dst=gateway_ip) + + def install_dvr_process_ipv6(self, vlan_tag, gateway_mac): + # block RA + self.add_flow(table=self.dvr_process_table_id, + priority=3, + dl_vlan=vlan_tag, + proto='icmp6', + dl_src=gateway_mac, + actions='drop') + + def delete_dvr_process_ipv6(self, vlan_tag, gateway_mac): + self.delete_flows(table=self.dvr_process_table_id, + dl_vlan=vlan_tag, + proto='icmp6', + dl_src=gateway_mac) + + def install_dvr_process(self, vlan_tag, vif_mac, dvr_mac_address): + self.add_flow(table=self.dvr_process_table_id, + priority=2, + dl_vlan=vlan_tag, + dl_dst=vif_mac, + actions="drop") + self.add_flow(table=self.dvr_process_table_id, + priority=1, + dl_vlan=vlan_tag, + dl_src=vif_mac, + actions="mod_dl_src:%s,resubmit(,%s)" % + (dvr_mac_address, self.dvr_process_next_table_id)) + + def delete_dvr_process(self, vlan_tag, vif_mac): + self.delete_flows(table=self.dvr_process_table_id, + dl_vlan=vlan_tag, + dl_dst=vif_mac) + self.delete_flows(table=self.dvr_process_table_id, + dl_vlan=vlan_tag, + dl_src=vif_mac) diff --git a/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/br_int.py b/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/br_int.py new file mode 100644 index 000000000..34e0c2f41 --- /dev/null +++ b/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/br_int.py @@ -0,0 +1,133 @@ +# Copyright (C) 2014,2015 VA Linux Systems Japan K.K. +# Copyright (C) 2014,2015 YAMAMOTO Takashi +# 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. + +""" +* references +** OVS agent https://wiki.openstack.org/wiki/Ovs-flow-logic +""" + +from neutron.plugins.common import constants as p_const +from neutron.plugins.openvswitch.agent.openflow.ovs_ofctl import ovs_bridge +from neutron.plugins.openvswitch.common import constants + + +class OVSIntegrationBridge(ovs_bridge.OVSAgentBridge): + """openvswitch agent br-int specific logic.""" + + def setup_default_table(self): + self.delete_flows() + self.install_normal() + self.setup_canary_table() + self.install_drop(table_id=constants.ARP_SPOOF_TABLE) + + def setup_canary_table(self): + self.install_drop(constants.CANARY_TABLE) + + def check_canary_table(self): + canary_flows = self.dump_flows(constants.CANARY_TABLE) + if canary_flows == '': + return constants.OVS_RESTARTED + elif canary_flows is None: + return constants.OVS_DEAD + else: + return constants.OVS_NORMAL + + def provision_local_vlan(self, port, lvid, segmentation_id): + if segmentation_id is None: + dl_vlan = 0xffff + else: + dl_vlan = segmentation_id + self.add_flow(priority=3, + in_port=port, + dl_vlan=dl_vlan, + actions="mod_vlan_vid:%s,normal" % lvid) + + def reclaim_local_vlan(self, port, segmentation_id): + if segmentation_id is None: + dl_vlan = 0xffff + else: + dl_vlan = segmentation_id + self.delete_flows(in_port=port, dl_vlan=dl_vlan) + + @staticmethod + def _dvr_to_src_mac_table_id(network_type): + if network_type == p_const.TYPE_VLAN: + return constants.DVR_TO_SRC_MAC_VLAN + else: + return constants.DVR_TO_SRC_MAC + + def install_dvr_to_src_mac(self, network_type, + vlan_tag, gateway_mac, dst_mac, dst_port): + table_id = self._dvr_to_src_mac_table_id(network_type) + self.add_flow(table=table_id, + priority=4, + dl_vlan=vlan_tag, + dl_dst=dst_mac, + actions="strip_vlan,mod_dl_src:%s," + "output:%s" % (gateway_mac, dst_port)) + + def delete_dvr_to_src_mac(self, network_type, vlan_tag, dst_mac): + table_id = self._dvr_to_src_mac_table_id(network_type) + self.delete_flows(table=table_id, + dl_vlan=vlan_tag, + dl_dst=dst_mac) + + def add_dvr_mac_vlan(self, mac, port): + self.install_goto(table_id=constants.LOCAL_SWITCHING, + priority=4, + in_port=port, + eth_src=mac, + dest_table_id=constants.DVR_TO_SRC_MAC_VLAN) + + def remove_dvr_mac_vlan(self, mac): + # REVISIT(yamamoto): match in_port as well? + self.delete_flows(table_id=constants.LOCAL_SWITCHING, + eth_src=mac) + + def add_dvr_mac_tun(self, mac, port): + # Table LOCAL_SWITCHING will now sort DVR traffic from other + # traffic depending on in_port + self.install_goto(table_id=constants.LOCAL_SWITCHING, + priority=2, + in_port=port, + eth_src=mac, + dest_table_id=constants.DVR_TO_SRC_MAC) + + def remove_dvr_mac_tun(self, mac, port): + self.delete_flows(table_id=constants.LOCAL_SWITCHING, + in_port=port, eth_src=mac) + + def install_arp_spoofing_protection(self, port, ip_addresses): + # allow ARPs as long as they match addresses that actually + # belong to the port. + for ip in ip_addresses: + self.install_normal( + table_id=constants.ARP_SPOOF_TABLE, priority=2, + proto='arp', arp_spa=ip, in_port=port) + + # Now that the rules are ready, direct ARP traffic from the port into + # the anti-spoof table. + # This strategy fails gracefully because OVS versions that can't match + # on ARP headers will just process traffic normally. + self.add_flow(table=constants.LOCAL_SWITCHING, + priority=10, proto='arp', in_port=port, + actions=("resubmit(,%s)" % constants.ARP_SPOOF_TABLE)) + + def delete_arp_spoofing_protection(self, port): + self.delete_flows(table_id=constants.LOCAL_SWITCHING, + in_port=port, proto='arp') + self.delete_flows(table_id=constants.ARP_SPOOF_TABLE, + in_port=port) diff --git a/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/br_phys.py b/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/br_phys.py new file mode 100644 index 000000000..3498bb86f --- /dev/null +++ b/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/br_phys.py @@ -0,0 +1,59 @@ +# Copyright (C) 2014,2015 VA Linux Systems Japan K.K. +# Copyright (C) 2014,2015 YAMAMOTO Takashi +# 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.openvswitch.agent.openflow.ovs_ofctl import br_dvr_process +from neutron.plugins.openvswitch.agent.openflow.ovs_ofctl import ovs_bridge +from neutron.plugins.openvswitch.common import constants + + +class OVSPhysicalBridge(ovs_bridge.OVSAgentBridge, + br_dvr_process.OVSDVRProcessMixin): + """openvswitch agent physical bridge specific logic.""" + + # Used by OVSDVRProcessMixin + dvr_process_table_id = constants.DVR_PROCESS_VLAN + dvr_process_next_table_id = constants.LOCAL_VLAN_TRANSLATION + + def setup_default_table(self): + self.delete_flows() + self.install_normal() + + def provision_local_vlan(self, port, lvid, segmentation_id, distributed): + table_id = constants.LOCAL_VLAN_TRANSLATION if distributed else 0 + if segmentation_id is None: + self.add_flow(table=table_id, + priority=4, + in_port=port, + dl_vlan=lvid, + actions="strip_vlan,normal") + else: + self.add_flow(table=table_id, + priority=4, + in_port=port, + dl_vlan=lvid, + actions="mod_vlan_vid:%s,normal" % segmentation_id) + + def reclaim_local_vlan(self, port, lvid): + self.delete_flows(in_port=port, dl_vlan=lvid) + + def add_dvr_mac_vlan(self, mac, port): + self.install_output(table_id=constants.DVR_NOT_LEARN_VLAN, + priority=2, eth_src=mac, port=port) + + def remove_dvr_mac_vlan(self, mac): + # REVISIT(yamamoto): match in_port as well? + self.delete_flows(table_id=constants.DVR_NOT_LEARN_VLAN, + eth_src=mac) diff --git a/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/br_tun.py b/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/br_tun.py new file mode 100644 index 000000000..4407e2fe0 --- /dev/null +++ b/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/br_tun.py @@ -0,0 +1,246 @@ +# Copyright (C) 2014,2015 VA Linux Systems Japan K.K. +# Copyright (C) 2014,2015 YAMAMOTO Takashi +# 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. + +# Copyright 2011 VMware, Inc. +# 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 functools + +import netaddr + +from neutron.agent.common import ovs_lib +from neutron.plugins.openvswitch.agent.openflow.ovs_ofctl import br_dvr_process +from neutron.plugins.openvswitch.agent.openflow.ovs_ofctl import ovs_bridge +from neutron.plugins.openvswitch.common import constants + + +class OVSTunnelBridge(ovs_bridge.OVSAgentBridge, + br_dvr_process.OVSDVRProcessMixin): + """openvswitch agent tunnel bridge specific logic.""" + + # Used by OVSDVRProcessMixin + dvr_process_table_id = constants.DVR_PROCESS + dvr_process_next_table_id = constants.PATCH_LV_TO_TUN + + def setup_default_table(self, patch_int_ofport, arp_responder_enabled): + # Table 0 (default) will sort incoming traffic depending on in_port + self.add_flow(priority=1, + in_port=patch_int_ofport, + actions="resubmit(,%s)" % + constants.PATCH_LV_TO_TUN) + self.add_flow(priority=0, actions="drop") + + if arp_responder_enabled: + # ARP broadcast-ed request go to the local ARP_RESPONDER table to + # be locally resolved + # REVISIT(yamamoto): arp_op=arp.ARP_REQUEST + self.add_flow(table=constants.PATCH_LV_TO_TUN, + priority=1, + proto='arp', + dl_dst="ff:ff:ff:ff:ff:ff", + actions=("resubmit(,%s)" % + constants.ARP_RESPONDER)) + + # PATCH_LV_TO_TUN table will handle packets coming from patch_int + # unicasts go to table UCAST_TO_TUN where remote addresses are learnt + self.add_flow(table=constants.PATCH_LV_TO_TUN, + priority=0, + dl_dst="00:00:00:00:00:00/01:00:00:00:00:00", + actions="resubmit(,%s)" % constants.UCAST_TO_TUN) + + # Broadcasts/multicasts go to table FLOOD_TO_TUN that handles flooding + self.add_flow(table=constants.PATCH_LV_TO_TUN, + priority=0, + dl_dst="01:00:00:00:00:00/01:00:00:00:00:00", + actions="resubmit(,%s)" % constants.FLOOD_TO_TUN) + + # Tables [tunnel_type]_TUN_TO_LV will set lvid depending on tun_id + # for each tunnel type, and resubmit to table LEARN_FROM_TUN where + # remote mac addresses will be learnt + for tunnel_type in constants.TUNNEL_NETWORK_TYPES: + self.add_flow(table=constants.TUN_TABLE[tunnel_type], + priority=0, + actions="drop") + + # LEARN_FROM_TUN table will have a single flow using a learn action to + # dynamically set-up flows in UCAST_TO_TUN corresponding to remote mac + # addresses (assumes that lvid has already been set by a previous flow) + learned_flow = ("table=%s," + "priority=1," + "hard_timeout=300," + "NXM_OF_VLAN_TCI[0..11]," + "NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[]," + "load:0->NXM_OF_VLAN_TCI[]," + "load:NXM_NX_TUN_ID[]->NXM_NX_TUN_ID[]," + "output:NXM_OF_IN_PORT[]" % + constants.UCAST_TO_TUN) + # Once remote mac addresses are learnt, output packet to patch_int + self.add_flow(table=constants.LEARN_FROM_TUN, + priority=1, + actions="learn(%s),output:%s" % + (learned_flow, patch_int_ofport)) + + # Egress unicast will be handled in table UCAST_TO_TUN, where remote + # mac addresses will be learned. For now, just add a default flow that + # will resubmit unknown unicasts to table FLOOD_TO_TUN to treat them + # as broadcasts/multicasts + self.add_flow(table=constants.UCAST_TO_TUN, + priority=0, + actions="resubmit(,%s)" % + constants.FLOOD_TO_TUN) + + if arp_responder_enabled: + # If none of the ARP entries correspond to the requested IP, the + # broadcast-ed packet is resubmitted to the flooding table + self.add_flow(table=constants.ARP_RESPONDER, + priority=0, + actions="resubmit(,%s)" % + constants.FLOOD_TO_TUN) + + # FLOOD_TO_TUN will handle flooding in tunnels based on lvid, + # for now, add a default drop action + self.install_drop(table_id=constants.FLOOD_TO_TUN) + + def provision_local_vlan(self, network_type, lvid, segmentation_id, + distributed=False): + if distributed: + table_id = constants.DVR_NOT_LEARN + else: + table_id = constants.LEARN_FROM_TUN + self.add_flow(table=constants.TUN_TABLE[network_type], + priority=1, + tun_id=segmentation_id, + actions="mod_vlan_vid:%s," + "resubmit(,%s)" % + (lvid, table_id)) + + def reclaim_local_vlan(self, network_type, segmentation_id): + self.delete_flows(table=constants.TUN_TABLE[network_type], + tun_id=segmentation_id) + + @staticmethod + def _ofport_set_to_str(ports_set): + return ",".join(map(str, ports_set)) + + def install_flood_to_tun(self, vlan, tun_id, ports, deferred_br=None): + br = deferred_br if deferred_br else self + br.mod_flow(table=constants.FLOOD_TO_TUN, + dl_vlan=vlan, + actions="strip_vlan,set_tunnel:%s,output:%s" % + (tun_id, self._ofport_set_to_str(ports))) + + def delete_flood_to_tun(self, vlan, deferred_br=None): + br = deferred_br if deferred_br else self + br.delete_flows(table=constants.FLOOD_TO_TUN, dl_vlan=vlan) + + def install_unicast_to_tun(self, vlan, tun_id, port, mac, + deferred_br=None): + br = deferred_br if deferred_br else self + br.add_flow(table=constants.UCAST_TO_TUN, + priority=2, + dl_vlan=vlan, + dl_dst=mac, + actions="strip_vlan,set_tunnel:%s,output:%s" % + (tun_id, port)) + + def delete_unicast_to_tun(self, vlan, mac, deferred_br=None): + br = deferred_br if deferred_br else self + if mac is None: + br.delete_flows(table=constants.UCAST_TO_TUN, + dl_vlan=vlan) + else: + br.delete_flows(table=constants.UCAST_TO_TUN, + dl_vlan=vlan, + dl_dst=mac) + + def install_arp_responder(self, vlan, ip, mac, deferred_br=None): + br = deferred_br if deferred_br else self + actions = constants.ARP_RESPONDER_ACTIONS % { + 'mac': netaddr.EUI(mac, dialect=netaddr.mac_unix), + 'ip': netaddr.IPAddress(ip), + } + br.add_flow(table=constants.ARP_RESPONDER, + priority=1, + proto='arp', + dl_vlan=vlan, + nw_dst='%s' % ip, + actions=actions) + + def delete_arp_responder(self, vlan, ip, deferred_br=None): + br = deferred_br if deferred_br else self + if ip is None: + br.delete_flows(table=constants.ARP_RESPONDER, + proto='arp', + dl_vlan=vlan) + else: + br.delete_flows(table=constants.ARP_RESPONDER, + proto='arp', + dl_vlan=vlan, + nw_dst='%s' % ip) + + def setup_tunnel_port(self, network_type, port): + self.add_flow(priority=1, + in_port=port, + actions="resubmit(,%s)" % + constants.TUN_TABLE[network_type]) + + def cleanup_tunnel_port(self, port): + self.delete_flows(in_port=port) + + def add_dvr_mac_tun(self, mac, port): + # Table DVR_NOT_LEARN ensures unique dvr macs in the cloud + # are not learnt, as they may result in flow explosions + self.install_output(table_id=constants.DVR_NOT_LEARN, + priority=1, + eth_src=mac, + port=port) + + def remove_dvr_mac_tun(self, mac): + # REVISIT(yamamoto): match in_port as well? + self.delete_flows(table_id=constants.DVR_NOT_LEARN, + eth_src=mac) + + def deferred(self, **kwargs): + return DeferredOVSTunnelBridge(self, **kwargs) + + +class DeferredOVSTunnelBridge(ovs_lib.DeferredOVSBridge): + _METHODS = [ + 'install_unicast_to_tun', + 'delete_unicast_to_tun', + 'install_flood_to_tun', + 'delete_flood_to_tun', + 'install_arp_responder', + 'delete_arp_responder', + ] + + def __getattr__(self, name): + if name in self._METHODS: + m = getattr(self.br, name) + return functools.partial(m, deferred_br=self) + raise AttributeError(name) diff --git a/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/main.py b/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/main.py new file mode 100644 index 000000000..537c324b1 --- /dev/null +++ b/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/main.py @@ -0,0 +1,33 @@ +# Copyright (C) 2015 VA Linux Systems Japan K.K. +# Copyright (C) 2015 YAMAMOTO Takashi +# 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.openvswitch.agent.openflow.ovs_ofctl import br_int +from neutron.plugins.openvswitch.agent.openflow.ovs_ofctl import br_phys +from neutron.plugins.openvswitch.agent.openflow.ovs_ofctl import br_tun +from neutron.plugins.openvswitch.agent import ovs_neutron_agent + + +def init_config(): + pass + + +def main(): + bridge_classes = { + 'br_int': br_int.OVSIntegrationBridge, + 'br_phys': br_phys.OVSPhysicalBridge, + 'br_tun': br_tun.OVSTunnelBridge, + } + ovs_neutron_agent.main(bridge_classes) diff --git a/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/ofswitch.py b/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/ofswitch.py new file mode 100644 index 000000000..578e3e219 --- /dev/null +++ b/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/ofswitch.py @@ -0,0 +1,74 @@ +# Copyright (C) 2014,2015 VA Linux Systems Japan K.K. +# Copyright (C) 2014,2015 YAMAMOTO Takashi +# 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. + +# Field name mappings (from Ryu to ovs-ofctl) +_keywords = { + 'eth_src': 'dl_src', + 'eth_dst': 'dl_dst', + 'ipv4_src': 'nw_src', + 'ipv4_dst': 'nw_dst', + 'table_id': 'table', +} + + +class OpenFlowSwitchMixin(object): + """Mixin to provide common convenient routines for an openflow switch.""" + + @staticmethod + def _conv_args(kwargs): + for our_name, ovs_ofctl_name in _keywords.items(): + if our_name in kwargs: + kwargs[ovs_ofctl_name] = kwargs.pop(our_name) + return kwargs + + def dump_flows(self, table_id): + return self.dump_flows_for_table(table_id) + + def install_goto_next(self, table_id): + self.install_goto(table_id=table_id, dest_table_id=table_id + 1) + + def install_output(self, port, table_id=0, priority=0, **kwargs): + self.add_flow(table=table_id, + priority=priority, + actions="output:%s" % port, + **self._conv_args(kwargs)) + + def install_normal(self, table_id=0, priority=0, **kwargs): + self.add_flow(table=table_id, + priority=priority, + actions="normal", + **self._conv_args(kwargs)) + + def install_goto(self, dest_table_id, table_id=0, priority=0, **kwargs): + self.add_flow(table=table_id, + priority=priority, + actions="resubmit(,%s)" % dest_table_id, + **self._conv_args(kwargs)) + + def install_drop(self, table_id=0, priority=0, **kwargs): + self.add_flow(table=table_id, + priority=priority, + actions="drop", + **self._conv_args(kwargs)) + + def delete_flows(self, **kwargs): + # NOTE(yamamoto): super() points to ovs_lib.OVSBridge. + # See ovs_bridge.py how this class is actually used. + if kwargs: + super(OpenFlowSwitchMixin, self).delete_flows( + **self._conv_args(kwargs)) + else: + super(OpenFlowSwitchMixin, self).remove_all_flows() diff --git a/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/ovs_bridge.py b/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/ovs_bridge.py new file mode 100644 index 000000000..179994d18 --- /dev/null +++ b/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/ovs_bridge.py @@ -0,0 +1,30 @@ +# Copyright (C) 2014,2015 VA Linux Systems Japan K.K. +# Copyright (C) 2014,2015 YAMAMOTO Takashi +# 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.agent.common import ovs_lib +from neutron.plugins.openvswitch.agent.openflow.ovs_ofctl import ofswitch + + +class OVSAgentBridge(ofswitch.OpenFlowSwitchMixin, ovs_lib.OVSBridge): + """Common code for bridges used by OVS agent""" + + def setup_controllers(self, conf): + self.set_protocols("[OpenFlow10]") + self.del_controller() + + def drop_port(self, in_port): + self.install_drop(priority=2, in_port=in_port) diff --git a/neutron/plugins/openvswitch/agent/ovs_dvr_neutron_agent.py b/neutron/plugins/openvswitch/agent/ovs_dvr_neutron_agent.py index 43f4ac5e7..0243a555b 100644 --- a/neutron/plugins/openvswitch/agent/ovs_dvr_neutron_agent.py +++ b/neutron/plugins/openvswitch/agent/ovs_dvr_neutron_agent.py @@ -171,8 +171,9 @@ class OVSDVRNeutronAgent(object): if not self.in_distributed_mode(): # switch all traffic using L2 learning - self.int_br.add_flow(table=constants.LOCAL_SWITCHING, - priority=1, actions="normal") + # REVISIT(yamamoto): why to install the same flow as + # setup_integration_br? + self.int_br.install_normal() def get_dvr_mac_address_with_retry(self): # Get the local DVR MAC Address from the Neutron Server. @@ -204,52 +205,42 @@ class OVSDVRNeutronAgent(object): LOG.info(_LI("L2 Agent operating in DVR Mode with MAC %s"), self.dvr_mac_address) # Remove existing flows in integration bridge - self.int_br.remove_all_flows() + self.int_br.delete_flows() # Add a canary flow to int_br to track OVS restarts - self.int_br.add_flow(table=constants.CANARY_TABLE, priority=0, - actions="drop") + self.int_br.setup_canary_table() # Insert 'drop' action as the default for Table DVR_TO_SRC_MAC - self.int_br.add_flow(table=constants.DVR_TO_SRC_MAC, - priority=1, - actions="drop") + self.int_br.install_drop(table_id=constants.DVR_TO_SRC_MAC, priority=1) - self.int_br.add_flow(table=constants.DVR_TO_SRC_MAC_VLAN, - priority=1, - actions="drop") + self.int_br.install_drop(table_id=constants.DVR_TO_SRC_MAC_VLAN, + priority=1) # Insert 'normal' action as the default for Table LOCAL_SWITCHING - self.int_br.add_flow(table=constants.LOCAL_SWITCHING, - priority=1, - actions="normal") + self.int_br.install_normal(table_id=constants.LOCAL_SWITCHING, + priority=1) for physical_network in self.bridge_mappings: - self.int_br.add_flow(table=constants.LOCAL_SWITCHING, - priority=2, - in_port=self.int_ofports[physical_network], - actions="drop") + self.int_br.install_drop(table_id=constants.LOCAL_SWITCHING, + priority=2, + in_port=self.int_ofports[ + physical_network]) def setup_dvr_flows_on_tun_br(self): '''Setup up initial dvr flows into br-tun''' if not self.enable_tunneling or not self.in_distributed_mode(): return - self.tun_br.add_flow(priority=1, - in_port=self.patch_int_ofport, - actions="resubmit(,%s)" % - constants.DVR_PROCESS) + self.tun_br.install_goto(dest_table_id=constants.DVR_PROCESS, + priority=1, + in_port=self.patch_int_ofport) # table-miss should be sent to learning table - self.tun_br.add_flow(table=constants.DVR_NOT_LEARN, - priority=0, - actions="resubmit(,%s)" % - constants.LEARN_FROM_TUN) + self.tun_br.install_goto(table_id=constants.DVR_NOT_LEARN, + dest_table_id=constants.LEARN_FROM_TUN) - self.tun_br.add_flow(table=constants.DVR_PROCESS, - priority=0, - actions="resubmit(,%s)" % - constants.PATCH_LV_TO_TUN) + self.tun_br.install_goto(table_id=constants.DVR_PROCESS, + dest_table_id=constants.PATCH_LV_TO_TUN) def setup_dvr_flows_on_phys_br(self): '''Setup up initial dvr flows into br-phys''' @@ -257,27 +248,63 @@ class OVSDVRNeutronAgent(object): return for physical_network in self.bridge_mappings: - self.phys_brs[physical_network].add_flow(priority=2, + self.phys_brs[physical_network].install_goto( in_port=self.phys_ofports[physical_network], - actions="resubmit(,%s)" % - constants.DVR_PROCESS_VLAN) - self.phys_brs[physical_network].add_flow(priority=1, - actions="resubmit(,%s)" % - constants.DVR_NOT_LEARN_VLAN) - self.phys_brs[physical_network].add_flow( - table=constants.DVR_PROCESS_VLAN, - priority=0, - actions="resubmit(,%s)" % - constants.LOCAL_VLAN_TRANSLATION) - self.phys_brs[physical_network].add_flow( - table=constants.LOCAL_VLAN_TRANSLATION, priority=2, - in_port=self.phys_ofports[physical_network], - actions="drop") - self.phys_brs[physical_network].add_flow( - table=constants.DVR_NOT_LEARN_VLAN, + dest_table_id=constants.DVR_PROCESS_VLAN) + self.phys_brs[physical_network].install_goto( priority=1, - actions="NORMAL") + dest_table_id=constants.DVR_NOT_LEARN_VLAN) + self.phys_brs[physical_network].install_goto( + table_id=constants.DVR_PROCESS_VLAN, + priority=0, + dest_table_id=constants.LOCAL_VLAN_TRANSLATION) + self.phys_brs[physical_network].install_drop( + table_id=constants.LOCAL_VLAN_TRANSLATION, + in_port=self.phys_ofports[physical_network], + priority=2) + self.phys_brs[physical_network].install_normal( + table_id=constants.DVR_NOT_LEARN_VLAN, + priority=1) + + def _add_dvr_mac_for_phys_br(self, physical_network, mac): + self.int_br.add_dvr_mac_vlan(mac=mac, + port=self.int_ofports[physical_network]) + phys_br = self.phys_brs[physical_network] + phys_br.add_dvr_mac_vlan(mac=mac, + port=self.phys_ofports[physical_network]) + + def _remove_dvr_mac_for_phys_br(self, physical_network, mac): + # REVISIT(yamamoto): match in_port as well? + self.int_br.remove_dvr_mac_vlan(mac=mac) + phys_br = self.phys_brs[physical_network] + # REVISIT(yamamoto): match in_port as well? + phys_br.remove_dvr_mac_vlan(mac=mac) + + def _add_dvr_mac_for_tun_br(self, mac): + self.int_br.add_dvr_mac_tun(mac=mac, port=self.patch_tun_ofport) + self.tun_br.add_dvr_mac_tun(mac=mac, port=self.patch_int_ofport) + + def _remove_dvr_mac_for_tun_br(self, mac): + self.int_br.remove_dvr_mac_tun(mac=mac, port=self.patch_tun_ofport) + # REVISIT(yamamoto): match in_port as well? + self.tun_br.remove_dvr_mac_tun(mac=mac) + + def _add_dvr_mac(self, mac): + for physical_network in self.bridge_mappings: + self._add_dvr_mac_for_phys_br(physical_network, mac) + if self.enable_tunneling: + self._add_dvr_mac_for_tun_br(mac) + LOG.debug("Added DVR MAC flow for %s", mac) + self.registered_dvr_macs.add(mac) + + def _remove_dvr_mac(self, mac): + for physical_network in self.bridge_mappings: + self._remove_dvr_mac_for_phys_br(physical_network, mac) + if self.enable_tunneling: + self._remove_dvr_mac_for_tun_br(mac) + LOG.debug("Removed DVR MAC flow for %s", mac) + self.registered_dvr_macs.remove(mac) def setup_dvr_mac_flows_on_all_brs(self): if not self.in_distributed_mode(): @@ -289,38 +316,7 @@ class OVSDVRNeutronAgent(object): for mac in dvr_macs: if mac['mac_address'] == self.dvr_mac_address: continue - for physical_network in self.bridge_mappings: - self.int_br.add_flow(table=constants.LOCAL_SWITCHING, - priority=4, - in_port=self.int_ofports[physical_network], - dl_src=mac['mac_address'], - actions="resubmit(,%s)" % - constants.DVR_TO_SRC_MAC_VLAN) - self.phys_brs[physical_network].add_flow( - table=constants.DVR_NOT_LEARN_VLAN, - priority=2, - dl_src=mac['mac_address'], - actions="output:%s" % - self.phys_ofports[physical_network]) - - if self.enable_tunneling: - # Table 0 (default) will now sort DVR traffic from other - # traffic depending on in_port - self.int_br.add_flow(table=constants.LOCAL_SWITCHING, - priority=2, - in_port=self.patch_tun_ofport, - dl_src=mac['mac_address'], - actions="resubmit(,%s)" % - constants.DVR_TO_SRC_MAC) - # Table DVR_NOT_LEARN ensures unique dvr macs in the cloud - # are not learnt, as they may - # result in flow explosions - self.tun_br.add_flow(table=constants.DVR_NOT_LEARN, - priority=1, - dl_src=mac['mac_address'], - actions="output:%s" % - self.patch_int_ofport) - self.registered_dvr_macs.add(mac['mac_address']) + self._add_dvr_mac(mac['mac_address']) def dvr_mac_address_update(self, dvr_macs): if not self.dvr_mac_address: @@ -342,50 +338,10 @@ class OVSDVRNeutronAgent(object): dvr_macs_removed = self.registered_dvr_macs - dvr_host_macs for oldmac in dvr_macs_removed: - for physical_network in self.bridge_mappings: - self.int_br.delete_flows(table=constants.LOCAL_SWITCHING, - in_port=self.int_ofports[physical_network], - dl_src=oldmac) - self.phys_brs[physical_network].delete_flows( - table=constants.DVR_NOT_LEARN_VLAN, - dl_src=oldmac) - if self.enable_tunneling: - self.int_br.delete_flows(table=constants.LOCAL_SWITCHING, - in_port=self.patch_tun_ofport, - dl_src=oldmac) - self.tun_br.delete_flows(table=constants.DVR_NOT_LEARN, - dl_src=oldmac) - LOG.debug("Removed DVR MAC flow for %s", oldmac) - self.registered_dvr_macs.remove(oldmac) + self._remove_dvr_mac(oldmac) for newmac in dvr_macs_added: - for physical_network in self.bridge_mappings: - self.int_br.add_flow(table=constants.LOCAL_SWITCHING, - priority=4, - in_port=self.int_ofports[physical_network], - dl_src=newmac, - actions="resubmit(,%s)" % - constants.DVR_TO_SRC_MAC_VLAN) - self.phys_brs[physical_network].add_flow( - table=constants.DVR_NOT_LEARN_VLAN, - priority=2, - dl_src=newmac, - actions="output:%s" % - self.phys_ofports[physical_network]) - if self.enable_tunneling: - self.int_br.add_flow(table=constants.LOCAL_SWITCHING, - priority=2, - in_port=self.patch_tun_ofport, - dl_src=newmac, - actions="resubmit(,%s)" % - constants.DVR_TO_SRC_MAC) - self.tun_br.add_flow(table=constants.DVR_NOT_LEARN, - priority=1, - dl_src=newmac, - actions="output:%s" % - self.patch_int_ofport) - LOG.debug("Added DVR MAC flow for %s", newmac) - self.registered_dvr_macs.add(newmac) + self._add_dvr_mac(newmac) def in_distributed_mode(self): return self.dvr_mac_address is not None @@ -394,16 +350,11 @@ class OVSDVRNeutronAgent(object): return device_owner == n_const.DEVICE_OWNER_DVR_INTERFACE def process_tunneled_network(self, network_type, lvid, segmentation_id): - if self.in_distributed_mode(): - table_id = constants.DVR_NOT_LEARN - else: - table_id = constants.LEARN_FROM_TUN - self.tun_br.add_flow(table=constants.TUN_TABLE[network_type], - priority=1, - tun_id=segmentation_id, - actions="mod_vlan_vid:%s," - "resubmit(,%s)" % - (lvid, table_id)) + self.tun_br.provision_local_vlan( + network_type=network_type, + lvid=lvid, + segmentation_id=segmentation_id, + distributed=self.in_distributed_mode()) def _bind_distributed_router_interface_port(self, port, lvm, fixed_ips, device_owner): @@ -436,10 +387,8 @@ class OVSDVRNeutronAgent(object): # DVR takes over ldm.set_dvr_owned(True) - table_id = constants.DVR_TO_SRC_MAC vlan_to_use = lvm.vlan if lvm.network_type == p_const.TYPE_VLAN: - table_id = constants.DVR_TO_SRC_MAC_VLAN vlan_to_use = lvm.segmentation_id subnet_info = ldm.get_subnet_info() @@ -469,73 +418,31 @@ class OVSDVRNeutronAgent(object): comp_ovsport.add_subnet(subnet_uuid) self.local_ports[vif.vif_id] = comp_ovsport # create rule for just this vm port - self.int_br.add_flow(table=table_id, - priority=4, - dl_vlan=vlan_to_use, - dl_dst=comp_ovsport.get_mac(), - actions="strip_vlan,mod_dl_src:%s," - "output:%s" % - (subnet_info['gateway_mac'], - comp_ovsport.get_ofport())) + self.int_br.install_dvr_to_src_mac( + network_type=lvm.network_type, + vlan_tag=vlan_to_use, + gateway_mac=subnet_info['gateway_mac'], + dst_mac=comp_ovsport.get_mac(), + dst_port=comp_ovsport.get_ofport()) if lvm.network_type == p_const.TYPE_VLAN: - args = {'table': constants.DVR_PROCESS_VLAN, - 'priority': 3, - 'dl_vlan': lvm.vlan, - 'actions': "drop"} - if ip_version == 4: - args['proto'] = 'arp' - args['nw_dst'] = subnet_info['gateway_ip'] - else: - args['proto'] = 'icmp6' - args['icmp_type'] = n_const.ICMPV6_TYPE_RA - args['dl_src'] = subnet_info['gateway_mac'] - # TODO(vivek) remove the IPv6 related add_flow once SNAT is not + # TODO(vivek) remove the IPv6 related flows once SNAT is not # used for IPv6 DVR. - self.phys_brs[lvm.physical_network].add_flow(**args) - self.phys_brs[lvm.physical_network].add_flow( - table=constants.DVR_PROCESS_VLAN, - priority=2, - dl_vlan=lvm.vlan, - dl_dst=port.vif_mac, - actions="drop") - - self.phys_brs[lvm.physical_network].add_flow( - table=constants.DVR_PROCESS_VLAN, - priority=1, - dl_vlan=lvm.vlan, - dl_src=port.vif_mac, - actions="mod_dl_src:%s,resubmit(,%s)" % - (self.dvr_mac_address, constants.LOCAL_VLAN_TRANSLATION)) - + br = self.phys_brs[lvm.physical_network] if lvm.network_type in constants.TUNNEL_NETWORK_TYPES: - args = {'table': constants.DVR_PROCESS, - 'priority': 3, - 'dl_vlan': lvm.vlan, - 'actions': "drop"} - if ip_version == 4: - args['proto'] = 'arp' - args['nw_dst'] = subnet_info['gateway_ip'] - else: - args['proto'] = 'icmp6' - args['icmp_type'] = n_const.ICMPV6_TYPE_RA - args['dl_src'] = subnet_info['gateway_mac'] - # TODO(vivek) remove the IPv6 related add_flow once SNAT is not - # used for IPv6 DVR. - self.tun_br.add_flow(**args) - self.tun_br.add_flow(table=constants.DVR_PROCESS, - priority=2, - dl_vlan=lvm.vlan, - dl_dst=port.vif_mac, - actions="drop") - - self.tun_br.add_flow(table=constants.DVR_PROCESS, - priority=1, - dl_vlan=lvm.vlan, - dl_src=port.vif_mac, - actions="mod_dl_src:%s,resubmit(,%s)" % - (self.dvr_mac_address, - constants.PATCH_LV_TO_TUN)) + br = self.tun_br + # TODO(vivek) remove the IPv6 related flows once SNAT is not + # used for IPv6 DVR. + if ip_version == 4: + br.install_dvr_process_ipv4( + vlan_tag=lvm.vlan, gateway_ip=subnet_info['gateway_ip']) + else: + br.install_dvr_process_ipv6( + vlan_tag=lvm.vlan, gateway_mac=subnet_info['gateway_mac']) + br.install_dvr_process( + vlan_tag=lvm.vlan, vif_mac=port.vif_mac, + dvr_mac_address=self.dvr_mac_address) + # the dvr router interface is itself a port, so capture it # queue this subnet to that port. A subnet appears only once as # a router interface on any given router @@ -578,20 +485,16 @@ class OVSDVRNeutronAgent(object): port.vif_mac, device_owner) ovsport.add_subnet(subnet_uuid) self.local_ports[port.vif_id] = ovsport - table_id = constants.DVR_TO_SRC_MAC vlan_to_use = lvm.vlan if lvm.network_type == p_const.TYPE_VLAN: - table_id = constants.DVR_TO_SRC_MAC_VLAN vlan_to_use = lvm.segmentation_id # create a rule for this vm port - self.int_br.add_flow(table=table_id, - priority=4, - dl_vlan=vlan_to_use, - dl_dst=ovsport.get_mac(), - actions="strip_vlan,mod_dl_src:%s," - "output:%s" % - (subnet_info['gateway_mac'], - ovsport.get_ofport())) + self.int_br.install_dvr_to_src_mac( + network_type=lvm.network_type, + vlan_tag=vlan_to_use, + gateway_mac=subnet_info['gateway_mac'], + dst_mac=ovsport.get_mac(), + dst_port=ovsport.get_ofport()) def _bind_centralized_snat_port_on_dvr_subnet(self, port, lvm, fixed_ips, device_owner): @@ -631,19 +534,15 @@ class OVSDVRNeutronAgent(object): port.vif_mac, device_owner) ovsport.add_subnet(subnet_uuid) self.local_ports[port.vif_id] = ovsport - table_id = constants.DVR_TO_SRC_MAC vlan_to_use = lvm.vlan if lvm.network_type == p_const.TYPE_VLAN: - table_id = constants.DVR_TO_SRC_MAC_VLAN vlan_to_use = lvm.segmentation_id - self.int_br.add_flow(table=table_id, - priority=4, - dl_vlan=vlan_to_use, - dl_dst=ovsport.get_mac(), - actions="strip_vlan,mod_dl_src:%s," - " output:%s" % - (subnet_info['gateway_mac'], - ovsport.get_ofport())) + self.int_br.install_dvr_to_src_mac( + network_type=lvm.network_type, + vlan_tag=vlan_to_use, + gateway_mac=subnet_info['gateway_mac'], + dst_mac=ovsport.get_mac(), + dst_port=ovsport.get_ofport()) def bind_port_to_dvr(self, port, local_vlan_map, fixed_ips, device_owner): @@ -681,10 +580,8 @@ class OVSDVRNeutronAgent(object): subnet_set = set(subnet_ids) network_type = lvm.network_type physical_network = lvm.physical_network - table_id = constants.DVR_TO_SRC_MAC vlan_to_use = lvm.vlan if network_type == p_const.TYPE_VLAN: - table_id = constants.DVR_TO_SRC_MAC_VLAN vlan_to_use = lvm.segmentation_id # ensure we process for all the subnets laid on this removed port for sub_uuid in subnet_set: @@ -700,9 +597,9 @@ class OVSDVRNeutronAgent(object): compute_ports = ldm.get_compute_ofports() for vif_id in compute_ports: comp_port = self.local_ports[vif_id] - self.int_br.delete_flows(table=table_id, - dl_vlan=vlan_to_use, - dl_dst=comp_port.get_mac()) + self.int_br.delete_dvr_to_src_mac( + network_type=network_type, + vlan_tag=vlan_to_use, dst_mac=comp_port.get_mac()) ldm.remove_all_compute_ofports() if ldm.get_csnat_ofport() == constants.OFPORT_INVALID: @@ -711,47 +608,23 @@ class OVSDVRNeutronAgent(object): # ports available on this agent anymore self.local_dvr_map.pop(sub_uuid, None) if network_type == p_const.TYPE_VLAN: - args = {'table': constants.DVR_PROCESS_VLAN, - 'dl_vlan': lvm.vlan} - if ip_version == 4: - args['proto'] = 'arp' - args['nw_dst'] = subnet_info['gateway_ip'] - else: - args['proto'] = 'icmp6' - args['icmp_type'] = n_const.ICMPV6_TYPE_RA - args['dl_src'] = subnet_info['gateway_mac'] - self.phys_br[physical_network].delete_flows(**args) - + br = self.phys_br[physical_network] if network_type in constants.TUNNEL_NETWORK_TYPES: - args = {'table': constants.DVR_PROCESS, - 'dl_vlan': lvm.vlan} - if ip_version == 4: - args['proto'] = 'arp' - args['nw_dst'] = subnet_info['gateway_ip'] - else: - args['proto'] = 'icmp6' - args['icmp_type'] = n_const.ICMPV6_TYPE_RA - args['dl_src'] = subnet_info['gateway_mac'] - self.tun_br.delete_flows(**args) + br = self.tun_br + if ip_version == 4: + br.delete_dvr_process_ipv4( + vlan_tag=lvm.vlan, gateway_ip=subnet_info['gateway_ip']) + else: + br.delete_dvr_process_ipv6( + vlan_tag=lvm.vlan, gateway_mac=subnet_info['gateway_mac']) ovsport.remove_subnet(sub_uuid) if lvm.network_type == p_const.TYPE_VLAN: - self.phys_br[physical_network].delete_flows( - table=constants.DVR_PROCESS_VLAN, - dl_vlan=lvm.vlan, - dl_dst=port.vif_mac) - self.phys_br[physical_network].delete_flows( - table=constants.DVR_PROCESS_VLAN, - dl_vlan=lvm.vlan, - dl_src=port.vif_mac) - + br = self.phys_br[physical_network] if lvm.network_type in constants.TUNNEL_NETWORK_TYPES: - self.tun_br.delete_flows(table=constants.DVR_PROCESS, - dl_vlan=lvm.vlan, - dl_dst=port.vif_mac) - self.tun_br.delete_flows(table=constants.DVR_PROCESS, - dl_vlan=lvm.vlan, - dl_src=port.vif_mac) + br = self.tun_br + br.delete_dvr_process(vlan_tag=lvm.vlan, vif_mac=port.vif_mac) + # release port state self.local_ports.pop(port.vif_id, None) @@ -767,15 +640,13 @@ class OVSDVRNeutronAgent(object): continue ldm = self.local_dvr_map[sub_uuid] ldm.remove_compute_ofport(port.vif_id) - table_id = constants.DVR_TO_SRC_MAC vlan_to_use = lvm.vlan if lvm.network_type == p_const.TYPE_VLAN: - table_id = constants.DVR_TO_SRC_MAC_VLAN vlan_to_use = lvm.segmentation_id # first remove this vm port rule - self.int_br.delete_flows(table=table_id, - dl_vlan=vlan_to_use, - dl_dst=ovsport.get_mac()) + self.int_br.delete_dvr_to_src_mac( + network_type=lvm.network_type, + vlan_tag=vlan_to_use, dst_mac=ovsport.get_mac()) # release port state self.local_ports.pop(port.vif_id, None) @@ -790,15 +661,13 @@ class OVSDVRNeutronAgent(object): return ldm = self.local_dvr_map[sub_uuid] ldm.set_csnat_ofport(constants.OFPORT_INVALID) - table_id = constants.DVR_TO_SRC_MAC vlan_to_use = lvm.vlan if lvm.network_type == p_const.TYPE_VLAN: - table_id = constants.DVR_TO_SRC_MAC_VLAN vlan_to_use = lvm.segmentation_id # then remove csnat port rule - self.int_br.delete_flows(table=table_id, - dl_vlan=vlan_to_use, - dl_dst=ovsport.get_mac()) + self.int_br.delete_dvr_to_src_mac( + network_type=lvm.network_type, + vlan_tag=vlan_to_use, dst_mac=ovsport.get_mac()) if not ldm.is_dvr_owned(): # if not owned by DVR (only used for csnat), remove this # subnet state altogether diff --git a/neutron/plugins/openvswitch/agent/ovs_neutron_agent.py b/neutron/plugins/openvswitch/agent/ovs_neutron_agent.py index 168e90933..cb3d44827 100644 --- a/neutron/plugins/openvswitch/agent/ovs_neutron_agent.py +++ b/neutron/plugins/openvswitch/agent/ovs_neutron_agent.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # Copyright 2011 VMware, Inc. # All Rights Reserved. # @@ -25,7 +24,6 @@ from oslo_log import log as logging import oslo_messaging from six import moves -from neutron.agent.common import config from neutron.agent.common import ovs_lib from neutron.agent.common import polling from neutron.agent.common import utils @@ -34,7 +32,6 @@ from neutron.agent.linux import ip_lib from neutron.agent import rpc as agent_rpc from neutron.agent import securitygroups_rpc as sg_rpc from neutron.api.rpc.handlers import dvr_rpc -from neutron.common import config as common_config from neutron.common import constants as q_const from neutron.common import exceptions from neutron.common import topics @@ -49,11 +46,16 @@ from neutron.plugins.openvswitch.common import constants LOG = logging.getLogger(__name__) cfg.CONF.import_group('AGENT', 'neutron.plugins.openvswitch.common.config') +cfg.CONF.import_group('OVS', 'neutron.plugins.openvswitch.common.config') # A placeholder for dead vlans. DEAD_VLAN_TAG = p_const.MAX_VLAN_TAG + 1 +class _mac_mydialect(netaddr.mac_unix): + word_fmt = '%.2x' + + class DeviceListRetrievalError(exceptions.NeutronException): message = _("Unable to retrieve port details for devices: %(devices)s " "because of error: %(error)s") @@ -119,7 +121,7 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, # 1.2 Support DVR (Distributed Virtual Router) RPC target = oslo_messaging.Target(version='1.2') - def __init__(self, integ_br, tun_br, local_ip, + def __init__(self, bridge_classes, integ_br, tun_br, local_ip, bridge_mappings, polling_interval, tunnel_types=None, veth_mtu=None, l2_population=False, enable_distributed_routing=False, @@ -132,6 +134,7 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, quitting_rpc_timeout=None): '''Constructor. + :param bridge_classes: a dict for bridge classes. :param integ_br: name of the integration bridge. :param tun_br: name of the tunnel bridge. :param local_ip: local IP address of this hypervisor. @@ -159,6 +162,9 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, SIGTERM is received ''' super(OVSNeutronAgent, self).__init__() + self.br_int_cls = bridge_classes['br_int'] + self.br_phys_cls = bridge_classes['br_phys'] + self.br_tun_cls = bridge_classes['br_tun'] self.use_veth_interconnection = use_veth_interconnection self.veth_mtu = veth_mtu self.available_local_vlans = set(moves.range(p_const.MIN_VLAN_TAG, @@ -197,7 +203,7 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, # Keep track of int_br's device count for use by _report_state() self.int_br_device_count = 0 - self.int_br = ovs_lib.OVSBridge(integ_br) + self.int_br = self.br_int_cls(integ_br) self.setup_integration_br() # Stores port update notifications for processing in main rpc loop self.updated_ports = set() @@ -440,21 +446,16 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, def add_fdb_flow(self, br, port_info, remote_ip, lvm, ofport): if port_info == q_const.FLOODING_ENTRY: lvm.tun_ofports.add(ofport) - ofports = _ofport_set_to_str(lvm.tun_ofports) - br.mod_flow(table=constants.FLOOD_TO_TUN, - dl_vlan=lvm.vlan, - actions="strip_vlan,set_tunnel:%s,output:%s" % - (lvm.segmentation_id, ofports)) + br.install_flood_to_tun(lvm.vlan, lvm.segmentation_id, + lvm.tun_ofports) else: self.setup_entry_for_arp_reply(br, 'add', lvm.vlan, port_info.mac_address, port_info.ip_address) - br.add_flow(table=constants.UCAST_TO_TUN, - priority=2, - dl_vlan=lvm.vlan, - dl_dst=port_info.mac_address, - actions="strip_vlan,set_tunnel:%s,output:%s" % - (lvm.segmentation_id, ofport)) + br.install_unicast_to_tun(lvm.vlan, + lvm.segmentation_id, + ofport, + port_info.mac_address) def del_fdb_flow(self, br, port_info, remote_ip, lvm, ofport): if port_info == q_const.FLOODING_ENTRY: @@ -463,21 +464,16 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, return lvm.tun_ofports.remove(ofport) if len(lvm.tun_ofports) > 0: - ofports = _ofport_set_to_str(lvm.tun_ofports) - br.mod_flow(table=constants.FLOOD_TO_TUN, - dl_vlan=lvm.vlan, - actions="strip_vlan,set_tunnel:%s,output:%s" % - (lvm.segmentation_id, ofports)) + br.install_flood_to_tun(lvm.vlan, lvm.segmentation_id, + lvm.tun_ofports) else: # This local vlan doesn't require any more tunnelling - br.delete_flows(table=constants.FLOOD_TO_TUN, dl_vlan=lvm.vlan) + br.delete_flood_to_tun(lvm.vlan) else: self.setup_entry_for_arp_reply(br, 'remove', lvm.vlan, port_info.mac_address, port_info.ip_address) - br.delete_flows(table=constants.UCAST_TO_TUN, - dl_vlan=lvm.vlan, - dl_dst=port_info.mac_address) + br.delete_unicast_to_tun(lvm.vlan, port_info.mac_address) def _fdb_chg_ip(self, context, fdb_entries): LOG.debug("update chg_ip received") @@ -496,25 +492,39 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, if not self.arp_responder_enabled: return - mac = netaddr.EUI(mac_address, dialect=netaddr.mac_unix) - ip = netaddr.IPAddress(ip_address) + mac = str(netaddr.EUI(mac_address, dialect=_mac_mydialect)) + ip = str(netaddr.IPAddress(ip_address)) if action == 'add': - actions = constants.ARP_RESPONDER_ACTIONS % {'mac': mac, 'ip': ip} - br.add_flow(table=constants.ARP_RESPONDER, - priority=1, - proto='arp', - dl_vlan=local_vid, - nw_dst='%s' % ip, - actions=actions) + br.install_arp_responder(local_vid, ip, mac) elif action == 'remove': - br.delete_flows(table=constants.ARP_RESPONDER, - proto='arp', - dl_vlan=local_vid, - nw_dst='%s' % ip) + br.delete_arp_responder(local_vid, ip) else: LOG.warning(_LW('Action %s not supported'), action) + def _local_vlan_for_flat(self, lvid, physical_network): + phys_br = self.phys_brs[physical_network] + phys_port = self.phys_ofports[physical_network] + int_br = self.int_br + int_port = self.int_ofports[physical_network] + phys_br.provision_local_vlan(port=phys_port, lvid=lvid, + segmentation_id=None, + distributed=False) + int_br.provision_local_vlan(port=int_port, lvid=lvid, + segmentation_id=None) + + def _local_vlan_for_vlan(self, lvid, physical_network, segmentation_id): + distributed = self.enable_distributed_routing + phys_br = self.phys_brs[physical_network] + phys_port = self.phys_ofports[physical_network] + int_br = self.int_br + int_port = self.int_ofports[physical_network] + phys_br.provision_local_vlan(port=phys_port, lvid=lvid, + segmentation_id=segmentation_id, + distributed=distributed) + int_br.provision_local_vlan(port=int_port, lvid=lvid, + segmentation_id=segmentation_id) + def provision_local_vlan(self, net_uuid, network_type, physical_network, segmentation_id, local_vlan=None): '''Provisions a local VLAN. @@ -554,28 +564,20 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, if network_type in constants.TUNNEL_NETWORK_TYPES: if self.enable_tunneling: # outbound broadcast/multicast - ofports = _ofport_set_to_str( - self.tun_br_ofports[network_type].values()) + ofports = self.tun_br_ofports[network_type].values() if ofports: - self.tun_br.mod_flow(table=constants.FLOOD_TO_TUN, - dl_vlan=lvid, - actions="strip_vlan," - "set_tunnel:%s,output:%s" % - (segmentation_id, ofports)) + self.tun_br.install_flood_to_tun(lvid, + segmentation_id, + ofports) # inbound from tunnels: set lvid in the right table # and resubmit to Table LEARN_FROM_TUN for mac learning if self.enable_distributed_routing: self.dvr_agent.process_tunneled_network( network_type, lvid, segmentation_id) else: - self.tun_br.add_flow( - table=constants.TUN_TABLE[network_type], - priority=1, - tun_id=segmentation_id, - actions="mod_vlan_vid:%s," - "resubmit(,%s)" % - (lvid, constants.LEARN_FROM_TUN)) - + self.tun_br.provision_local_vlan( + network_type=network_type, lvid=lvid, + segmentation_id=segmentation_id) else: LOG.error(_LE("Cannot provision %(network_type)s network for " "net-id=%(net_uuid)s - tunneling disabled"), @@ -583,18 +585,7 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, 'net_uuid': net_uuid}) elif network_type == p_const.TYPE_FLAT: if physical_network in self.phys_brs: - # outbound - br = self.phys_brs[physical_network] - br.add_flow(priority=4, - in_port=self.phys_ofports[physical_network], - dl_vlan=lvid, - actions="strip_vlan,normal") - # inbound - self.int_br.add_flow( - priority=3, - in_port=self.int_ofports[physical_network], - dl_vlan=0xffff, - actions="mod_vlan_vid:%s,normal" % lvid) + self._local_vlan_for_flat(lvid, physical_network) else: LOG.error(_LE("Cannot provision flat network for " "net-id=%(net_uuid)s - no bridge for " @@ -603,26 +594,8 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, 'physical_network': physical_network}) elif network_type == p_const.TYPE_VLAN: if physical_network in self.phys_brs: - # outbound - br = self.phys_brs[physical_network] - if self.enable_distributed_routing: - br.add_flow(table=constants.LOCAL_VLAN_TRANSLATION, - priority=4, - in_port=self.phys_ofports[physical_network], - dl_vlan=lvid, - actions="mod_vlan_vid:%s,normal" % segmentation_id) - else: - br.add_flow(priority=4, - in_port=self.phys_ofports[physical_network], - dl_vlan=lvid, - actions="mod_vlan_vid:%s,normal" % segmentation_id) - - # inbound - self.int_br.add_flow(priority=3, - in_port=self. - int_ofports[physical_network], - dl_vlan=segmentation_id, - actions="mod_vlan_vid:%s,normal" % lvid) + self._local_vlan_for_vlan(lvid, physical_network, + segmentation_id) else: LOG.error(_LE("Cannot provision VLAN network for " "net-id=%(net_uuid)s - no bridge for " @@ -654,10 +627,12 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, if lvm.network_type in constants.TUNNEL_NETWORK_TYPES: if self.enable_tunneling: - self.tun_br.delete_flows( - table=constants.TUN_TABLE[lvm.network_type], - tun_id=lvm.segmentation_id) - self.tun_br.delete_flows(dl_vlan=lvm.vlan) + self.tun_br.reclaim_local_vlan( + network_type=lvm.network_type, + segmentation_id=lvm.segmentation_id) + self.tun_br.delete_flood_to_tun(lvm.vlan) + self.tun_br.delete_unicast_to_tun(lvm.vlan, None) + self.tun_br.delete_arp_responder(lvm.vlan, None) if self.l2_pop: # Try to remove tunnel ports if not used by other networks for ofport in lvm.tun_ofports: @@ -667,24 +642,26 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, if lvm.physical_network in self.phys_brs: # outbound br = self.phys_brs[lvm.physical_network] - br.delete_flows(in_port=self.phys_ofports[lvm. - physical_network], - dl_vlan=lvm.vlan) + br.reclaim_local_vlan( + port=self.phys_ofports[lvm.physical_network], + lvid=lvm.vlan) # inbound br = self.int_br - br.delete_flows(in_port=self.int_ofports[lvm.physical_network], - dl_vlan=0xffff) + br.reclaim_local_vlan( + port=self.int_ofports[lvm.physical_network], + segmentation_id=None) elif lvm.network_type == p_const.TYPE_VLAN: if lvm.physical_network in self.phys_brs: # outbound br = self.phys_brs[lvm.physical_network] - br.delete_flows(in_port=self.phys_ofports[lvm. - physical_network], - dl_vlan=lvm.vlan) + br.reclaim_local_vlan( + port=self.phys_ofports[lvm.physical_network], + lvid=lvm.vlan) # inbound br = self.int_br - br.delete_flows(in_port=self.int_ofports[lvm.physical_network], - dl_vlan=lvm.segmentation_id) + br.reclaim_local_vlan( + port=self.int_ofports[lvm.physical_network], + segmentation_id=lvm.segmentation_id) elif lvm.network_type == p_const.TYPE_LOCAL: # no flows needed for local networks pass @@ -746,6 +723,8 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, self.int_br.set_db_attribute( "Port", port.port_name, "tag", lvm.vlan) if port.ofport != -1: + # NOTE(yamamoto): Remove possible drop_port flow + # installed by port_dead. self.int_br.delete_flows(in_port=port.ofport) # update plugin about port status @@ -766,43 +745,22 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, @staticmethod def setup_arp_spoofing_protection(bridge, vif, port_details): # clear any previous flows related to this port in our ARP table - bridge.delete_flows(table=constants.LOCAL_SWITCHING, - in_port=vif.ofport, proto='arp') - bridge.delete_flows(table=constants.ARP_SPOOF_TABLE, - in_port=vif.ofport) + bridge.delete_arp_spoofing_protection(port=vif.ofport) if not port_details.get('port_security_enabled', True): LOG.info(_LI("Skipping ARP spoofing rules for port '%s' because " "it has port security disabled"), vif.port_name) return - # all of the rules here are based on 'in_port' match criteria - # so their cleanup will be handled by 'update_stale_ofport_rules' - # collect all of the addresses and cidrs that belong to the port - addresses = [f['ip_address'] for f in port_details['fixed_ips']] + addresses = {f['ip_address'] for f in port_details['fixed_ips']} if port_details.get('allowed_address_pairs'): - addresses += [p['ip_address'] - for p in port_details['allowed_address_pairs']] + addresses |= {p['ip_address'] + for p in port_details['allowed_address_pairs']} - # allow ARPs as long as they match addresses that actually - # belong to the port. - for ip in addresses: - if netaddr.IPNetwork(ip).version != 4: - continue - bridge.add_flow(table=constants.ARP_SPOOF_TABLE, priority=2, - proto='arp', arp_spa=ip, in_port=vif.ofport, - actions="NORMAL") - - # drop any ARPs in this table that aren't explicitly allowed - bridge.add_flow(table=constants.ARP_SPOOF_TABLE, priority=1, - proto='arp', actions="DROP") - - # Now that the rules are ready, direct ARP traffic from the port into - # the anti-spoof table. - # This strategy fails gracefully because OVS versions that can't match - # on ARP headers will just process traffic normally. - bridge.add_flow(table=constants.LOCAL_SWITCHING, - priority=10, proto='arp', in_port=vif.ofport, - actions=("resubmit(,%s)" % constants.ARP_SPOOF_TABLE)) + addresses = {ip for ip in addresses + if netaddr.IPNetwork(ip).version == 4} + + bridge.install_arp_spoofing_protection(port=vif.ofport, + ip_addresses=addresses) def port_unbound(self, vif_id, net_uuid=None): '''Unbind port. @@ -841,8 +799,7 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, if cur_tag != DEAD_VLAN_TAG: self.int_br.set_db_attribute("Port", port.port_name, "tag", DEAD_VLAN_TAG) - self.int_br.add_flow(priority=2, in_port=port.ofport, - actions="drop") + self.int_br.drop_port(in_port=port.ofport) def setup_integration_br(self): '''Setup the integration bridge. @@ -855,14 +812,11 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, # which does nothing if bridge already exists. self.int_br.create() self.int_br.set_secure_mode() + self.int_br.setup_controllers(cfg.CONF) self.int_br.delete_port(cfg.CONF.OVS.int_peer_patch_port) - self.int_br.remove_all_flows() - # switch all traffic using L2 learning - self.int_br.add_flow(priority=1, actions="normal") - # Add a canary flow to int_br to track OVS restarts - self.int_br.add_flow(table=constants.CANARY_TABLE, priority=0, - actions="drop") + + self.int_br.setup_default_table() def setup_ancillary_bridges(self, integ_br, tun_br): '''Setup ancillary bridges - for example br-ex.''' @@ -899,9 +853,10 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, :param tun_br_name: the name of the tunnel bridge. ''' if not self.tun_br: - self.tun_br = ovs_lib.OVSBridge(tun_br_name) + self.tun_br = self.br_tun_cls(tun_br_name) self.tun_br.reset_bridge(secure_mode=True) + self.tun_br.setup_controllers(cfg.CONF) self.patch_tun_ofport = self.int_br.add_patch_port( cfg.CONF.OVS.int_peer_patch_port, cfg.CONF.OVS.tun_peer_patch_port) self.patch_int_ofport = self.tun_br.add_patch_port( @@ -913,83 +868,15 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, "version of OVS does not support tunnels or patch " "ports. Agent terminated!")) exit(1) - self.tun_br.remove_all_flows() + self.tun_br.delete_flows() def setup_tunnel_br(self): '''Setup the tunnel bridge. Add all flows to the tunnel bridge. ''' - # Table 0 (default) will sort incoming traffic depending on in_port - self.tun_br.add_flow(priority=1, - in_port=self.patch_int_ofport, - actions="resubmit(,%s)" % - constants.PATCH_LV_TO_TUN) - self.tun_br.add_flow(priority=0, actions="drop") - if self.arp_responder_enabled: - # ARP broadcast-ed request go to the local ARP_RESPONDER table to - # be locally resolved - self.tun_br.add_flow(table=constants.PATCH_LV_TO_TUN, - priority=1, - proto='arp', - dl_dst="ff:ff:ff:ff:ff:ff", - actions=("resubmit(,%s)" % - constants.ARP_RESPONDER)) - # PATCH_LV_TO_TUN table will handle packets coming from patch_int - # unicasts go to table UCAST_TO_TUN where remote addresses are learnt - self.tun_br.add_flow(table=constants.PATCH_LV_TO_TUN, - priority=0, - dl_dst="00:00:00:00:00:00/01:00:00:00:00:00", - actions="resubmit(,%s)" % constants.UCAST_TO_TUN) - # Broadcasts/multicasts go to table FLOOD_TO_TUN that handles flooding - self.tun_br.add_flow(table=constants.PATCH_LV_TO_TUN, - priority=0, - dl_dst="01:00:00:00:00:00/01:00:00:00:00:00", - actions="resubmit(,%s)" % constants.FLOOD_TO_TUN) - # Tables [tunnel_type]_TUN_TO_LV will set lvid depending on tun_id - # for each tunnel type, and resubmit to table LEARN_FROM_TUN where - # remote mac addresses will be learnt - for tunnel_type in constants.TUNNEL_NETWORK_TYPES: - self.tun_br.add_flow(table=constants.TUN_TABLE[tunnel_type], - priority=0, - actions="drop") - # LEARN_FROM_TUN table will have a single flow using a learn action to - # dynamically set-up flows in UCAST_TO_TUN corresponding to remote mac - # addresses (assumes that lvid has already been set by a previous flow) - learned_flow = ("table=%s," - "priority=1," - "hard_timeout=300," - "NXM_OF_VLAN_TCI[0..11]," - "NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[]," - "load:0->NXM_OF_VLAN_TCI[]," - "load:NXM_NX_TUN_ID[]->NXM_NX_TUN_ID[]," - "output:NXM_OF_IN_PORT[]" % - constants.UCAST_TO_TUN) - # Once remote mac addresses are learnt, output packet to patch_int - self.tun_br.add_flow(table=constants.LEARN_FROM_TUN, - priority=1, - actions="learn(%s),output:%s" % - (learned_flow, self.patch_int_ofport)) - # Egress unicast will be handled in table UCAST_TO_TUN, where remote - # mac addresses will be learned. For now, just add a default flow that - # will resubmit unknown unicasts to table FLOOD_TO_TUN to treat them - # as broadcasts/multicasts - self.tun_br.add_flow(table=constants.UCAST_TO_TUN, - priority=0, - actions="resubmit(,%s)" % - constants.FLOOD_TO_TUN) - if self.arp_responder_enabled: - # If none of the ARP entries correspond to the requested IP, the - # broadcast-ed packet is resubmitted to the flooding table - self.tun_br.add_flow(table=constants.ARP_RESPONDER, - priority=0, - actions="resubmit(,%s)" % - constants.FLOOD_TO_TUN) - # FLOOD_TO_TUN will handle flooding in tunnels based on lvid, - # for now, add a default drop action - self.tun_br.add_flow(table=constants.FLOOD_TO_TUN, - priority=0, - actions="drop") + self.tun_br.setup_default_table(self.patch_int_ofport, + self.arp_responder_enabled) def get_peer_name(self, prefix, name): """Construct a peer name based on the prefix and name. @@ -1041,9 +928,9 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, {'physical_network': physical_network, 'bridge': bridge}) sys.exit(1) - br = ovs_lib.OVSBridge(bridge) - br.remove_all_flows() - br.add_flow(priority=1, actions="normal") + br = self.br_phys_cls(bridge) + br.setup_controllers(cfg.CONF) + br.setup_default_table() self.phys_brs[physical_network] = br # interconnect physical and integration bridges using veth/patchs @@ -1076,9 +963,8 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, self.phys_ofports[physical_network] = phys_ofport # block all untranslated traffic between bridges - self.int_br.add_flow(priority=2, in_port=int_ofport, - actions="drop") - br.add_flow(priority=2, in_port=phys_ofport, actions="drop") + self.int_br.drop_port(in_port=int_ofport) + br.drop_port(in_port=phys_ofport) if self.use_veth_interconnection: # enable veth to pass traffic @@ -1114,7 +1000,7 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, # delete any stale rules based on removed ofports ofports_deleted = set(previous.values()) - set(current.values()) for ofport in ofports_deleted: - self.int_br.delete_flows(in_port=ofport) + self.int_br.delete_arp_spoofing_protection(port=ofport) # store map for next iteration self.vifname_to_ofport_map = current @@ -1237,20 +1123,16 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, self.tun_br_ofports[tunnel_type][remote_ip] = ofport # Add flow in default table to resubmit to the right # tunnelling table (lvid will be set in the latter) - br.add_flow(priority=1, - in_port=ofport, - actions="resubmit(,%s)" % - constants.TUN_TABLE[tunnel_type]) + br.setup_tunnel_port(tunnel_type, ofport) - ofports = _ofport_set_to_str(self.tun_br_ofports[tunnel_type].values()) + ofports = self.tun_br_ofports[tunnel_type].values() if ofports and not self.l2_pop: # Update flooding flows to include the new tunnel for vlan_mapping in list(self.local_vlan_map.values()): if vlan_mapping.network_type == tunnel_type: - br.mod_flow(table=constants.FLOOD_TO_TUN, - dl_vlan=vlan_mapping.vlan, - actions="strip_vlan,set_tunnel:%s,output:%s" % - (vlan_mapping.segmentation_id, ofports)) + br.install_flood_to_tun(vlan_mapping.vlan, + vlan_mapping.segmentation_id, + ofports) return ofport def setup_tunnel_port(self, br, remote_ip, network_type): @@ -1276,7 +1158,7 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, port_name = '%s-%s' % (tunnel_type, self.get_ip_in_hex(remote_ip)) br.delete_port(port_name) - br.delete_flows(in_port=ofport) + br.cleanup_tunnel_port(ofport) self.tun_br_ofports[tunnel_type].pop(remote_ip, None) def treat_devices_added_or_updated(self, devices, ovs_restarted): @@ -1521,18 +1403,14 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, def check_ovs_status(self): # Check for the canary flow - canary_flow = self.int_br.dump_flows_for_table(constants.CANARY_TABLE) - if canary_flow == '': + status = self.int_br.check_canary_table() + if status == constants.OVS_RESTARTED: LOG.warn(_LW("OVS is restarted. OVSNeutronAgent will reset " "bridges and recover ports.")) - return constants.OVS_RESTARTED - elif canary_flow is None: + elif status == constants.OVS_DEAD: LOG.warn(_LW("OVS is dead. OVSNeutronAgent will keep running " "and checking OVS status periodically.")) - return constants.OVS_DEAD - else: - # OVS is in normal status - return constants.OVS_NORMAL + return status def loop_count_and_wait(self, start_time, port_stats): # sleep till end of polling interval @@ -1688,6 +1566,9 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, self.loop_count_and_wait(start, port_stats) def daemon_loop(self): + # Start everything. + LOG.info(_LI("Agent initialized successfully, now running... ")) + signal.signal(signal.SIGTERM, self._handle_sigterm) with polling.get_polling_manager( self.minimize_polling, self.ovsdb_monitor_respawn_interval) as pm: @@ -1713,10 +1594,6 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin, "Agent and Server side.")) -def _ofport_set_to_str(ofport_set): - return ",".join(map(str, ofport_set)) - - def create_agent_config_map(config): """Create a map of agent config parameters. @@ -1757,36 +1634,25 @@ def create_agent_config_map(config): return kwargs -def main(): - cfg.CONF.register_opts(ip_lib.OPTS) - config.register_root_helper(cfg.CONF) - common_config.init(sys.argv[1:]) - common_config.setup_logging() - q_utils.log_opt_values(LOG) - - try: - agent_config = create_agent_config_map(cfg.CONF) - except ValueError as e: - LOG.error(_LE('%s Agent terminated!'), e) - sys.exit(1) - +def prepare_xen_compute(): is_xen_compute_host = 'rootwrap-xen-dom0' in cfg.CONF.AGENT.root_helper if is_xen_compute_host: # Force ip_lib to always use the root helper to ensure that ip # commands target xen dom0 rather than domU. + cfg.CONF.register_opts(ip_lib.OPTS) cfg.CONF.set_default('ip_lib_force_root', True) + +def main(bridge_classes): + try: + agent_config = create_agent_config_map(cfg.CONF) + except ValueError: + LOG.exception(_LE("Agent failed to create agent config map")) + raise SystemExit(1) + prepare_xen_compute() try: - agent = OVSNeutronAgent(**agent_config) + agent = OVSNeutronAgent(bridge_classes, **agent_config) except RuntimeError as e: LOG.error(_LE("%s Agent terminated!"), e) sys.exit(1) - signal.signal(signal.SIGTERM, agent._handle_sigterm) - - # Start everything. - LOG.info(_LI("Agent initialized successfully, now running... ")) agent.daemon_loop() - - -if __name__ == "__main__": - main() diff --git a/neutron/plugins/openvswitch/common/config.py b/neutron/plugins/openvswitch/common/config.py index 887a6c5cb..cbde41733 100644 --- a/neutron/plugins/openvswitch/common/config.py +++ b/neutron/plugins/openvswitch/common/config.py @@ -44,6 +44,8 @@ ovs_opts = [ cfg.BoolOpt('use_veth_interconnection', default=False, help=_("Use veths instead of patch ports to interconnect the " "integration bridge to physical bridges.")), + cfg.StrOpt('of_interface', default='ovs-ofctl', choices=['ovs-ofctl'], + help=_("OpenFlow interface to use.")), ] agent_opts = [ diff --git a/neutron/tests/functional/agent/test_ovs_flows.py b/neutron/tests/functional/agent/test_ovs_flows.py index 73719d1c4..bf9936d63 100644 --- a/neutron/tests/functional/agent/test_ovs_flows.py +++ b/neutron/tests/functional/agent/test_ovs_flows.py @@ -13,9 +13,16 @@ # License for the specific language governing permissions and limitations # under the License. +import eventlet +import mock + +from oslo_config import cfg +from oslo_utils import importutils + from neutron.agent.linux import ip_lib from neutron.cmd.sanity import checks from neutron.plugins.openvswitch.agent import ovs_neutron_agent as ovsagt +from neutron.plugins.openvswitch.common import constants from neutron.tests.common import machine_fixtures from neutron.tests.common import net_helpers from neutron.tests.functional.agent import test_ovs_lib @@ -23,16 +30,68 @@ from neutron.tests.functional import base from neutron.tests import tools -class ARPSpoofTestCase(test_ovs_lib.OVSBridgeTestBase, - base.BaseSudoTestCase): +cfg.CONF.import_group('OVS', 'neutron.plugins.openvswitch.common.config') + +class _OVSAgentTestBase(test_ovs_lib.OVSBridgeTestBase, + base.BaseSudoTestCase): + def setUp(self): + super(_OVSAgentTestBase, self).setUp() + self.br = self.useFixture(net_helpers.OVSBridgeFixture()).bridge + self.of_interface_mod = importutils.import_module(self._MAIN_MODULE) + self.br_int_cls = None + self.br_tun_cls = None + self.br_phys_cls = None + self.br_int = None + self.init_done = False + self.init_done_ev = eventlet.event.Event() + self._main_thread = eventlet.spawn(self._kick_main) + self.addCleanup(self._kill_main) + + # Wait for _kick_main -> of_interface main -> _agent_main + # NOTE(yamamoto): This complexity came from how "native" of_interface + # runs its openflow controller. "native" of_interface's main routine + # blocks while running the embedded openflow controller. In that case, + # the agent rpc_loop runs in another thread. However, for FT we need + # to run setUp() and test_xxx() in the same thread. So I made this + # run of_interface's main in a separate thread instead. + while not self.init_done: + self.init_done_ev.wait() + + def _kick_main(self): + with mock.patch.object(ovsagt, 'main', self._agent_main): + self.of_interface_mod.main() + + def _kill_main(self): + self._main_thread.kill() + self._main_thread.wait() + + def _agent_main(self, bridge_classes): + self.br_int_cls = bridge_classes['br_int'] + self.br_phys_cls = bridge_classes['br_phys'] + self.br_tun_cls = bridge_classes['br_tun'] + self.br_int = self.br_int_cls(self.br.br_name) + self.br_int.set_secure_mode() + self.br_int.setup_controllers(cfg.CONF) + self.br_int.setup_default_table() + + # signal to setUp() + self.init_done = True + self.init_done_ev.send() + + +class _OVSAgentOFCtlTestBase(_OVSAgentTestBase): + _MAIN_MODULE = 'neutron.plugins.openvswitch.agent.openflow.ovs_ofctl.main' + + +class _ARPSpoofTestCase(object): def setUp(self): if not checks.arp_header_match_supported(): self.skipTest("ARP header matching not supported") # NOTE(kevinbenton): it would be way cooler to use scapy for # these but scapy requires the python process to be running as # root to bind to the ports. - super(ARPSpoofTestCase, self).setUp() + super(_ARPSpoofTestCase, self).setUp() self.src_addr = '192.168.0.1' self.dst_addr = '192.168.0.2' self.src_namespace = self.useFixture( @@ -120,4 +179,22 @@ class ARPSpoofTestCase(test_ovs_lib.OVSBridgeTestBase, 'allowed_address_pairs': [ dict(ip_address=ip) for ip in addrs]} ovsagt.OVSNeutronAgent.setup_arp_spoofing_protection( - self.br, VifPort(), details) + self.br_int, VifPort(), details) + + +class ARPSpoofOFCtlTestCase(_ARPSpoofTestCase, _OVSAgentOFCtlTestBase): + pass + + +class _CanaryTableTestCase(object): + def test_canary_table(self): + self.br_int.delete_flows() + self.assertEqual(constants.OVS_RESTARTED, + self.br_int.check_canary_table()) + self.br_int.setup_canary_table() + self.assertEqual(constants.OVS_NORMAL, + self.br_int.check_canary_table()) + + +class CanaryTableOFCtlTestCase(_CanaryTableTestCase, _OVSAgentOFCtlTestBase): + pass diff --git a/neutron/tests/unit/plugins/openvswitch/agent/openflow/__init__.py b/neutron/tests/unit/plugins/openvswitch/agent/openflow/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/neutron/tests/unit/plugins/openvswitch/agent/openflow/ovs_ofctl/__init__.py b/neutron/tests/unit/plugins/openvswitch/agent/openflow/ovs_ofctl/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/neutron/tests/unit/plugins/openvswitch/agent/openflow/ovs_ofctl/ovs_bridge_test_base.py b/neutron/tests/unit/plugins/openvswitch/agent/openflow/ovs_ofctl/ovs_bridge_test_base.py new file mode 100644 index 000000000..5d071fe3b --- /dev/null +++ b/neutron/tests/unit/plugins/openvswitch/agent/openflow/ovs_ofctl/ovs_bridge_test_base.py @@ -0,0 +1,160 @@ +# Copyright (C) 2014,2015 VA Linux Systems Japan K.K. +# Copyright (C) 2014,2015 YAMAMOTO Takashi +# 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 mock + +from neutron.tests.unit.plugins.openvswitch.agent import ovs_test_base + + +call = mock.call # short hand + + +class OVSBridgeTestBase(ovs_test_base.OVSOFCtlTestBase): + def setup_bridge_mock(self, name, cls): + self.br = cls(name) + mock_add_flow = mock.patch.object(self.br, 'add_flow').start() + mock_mod_flow = mock.patch.object(self.br, 'mod_flow').start() + mock_delete_flows = mock.patch.object(self.br, 'delete_flows').start() + self.mock = mock.Mock() + self.mock.attach_mock(mock_add_flow, 'add_flow') + self.mock.attach_mock(mock_mod_flow, 'mod_flow') + self.mock.attach_mock(mock_delete_flows, 'delete_flows') + + def test_drop_port(self): + in_port = 2345 + self.br.drop_port(in_port=in_port) + expected = [ + call.add_flow(priority=2, table=0, actions='drop', + in_port=in_port), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_install_goto(self): + dest_table_id = 123 + priority = 99 + in_port = 666 + self.br.install_goto(dest_table_id=dest_table_id, + priority=priority, in_port=in_port) + expected = [ + call.add_flow(priority=priority, table=0, + actions='resubmit(,%s)' % dest_table_id, + in_port=in_port), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_install_drop(self): + priority = 99 + in_port = 666 + self.br.install_drop(priority=priority, in_port=in_port) + expected = [ + call.add_flow(priority=priority, table=0, + actions='drop', + in_port=in_port), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_install_normal(self): + priority = 99 + in_port = 666 + self.br.install_normal(priority=priority, in_port=in_port) + expected = [ + call.add_flow(priority=priority, table=0, + actions='normal', + in_port=in_port), + ] + self.assertEqual(expected, self.mock.mock_calls) + + +class OVSDVRProcessTestMixin(object): + def test_install_dvr_process_ipv4(self): + vlan_tag = 999 + gateway_ip = '192.0.2.1' + self.br.install_dvr_process_ipv4(vlan_tag=vlan_tag, + gateway_ip=gateway_ip) + expected = [ + call.add_flow(table=self.dvr_process_table_id, + proto='arp', nw_dst=gateway_ip, actions='drop', + priority=3, dl_vlan=vlan_tag), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_delete_dvr_process_ipv4(self): + vlan_tag = 999 + gateway_ip = '192.0.2.1' + self.br.delete_dvr_process_ipv4(vlan_tag=vlan_tag, + gateway_ip=gateway_ip) + expected = [ + call.delete_flows(table=self.dvr_process_table_id, + dl_vlan=vlan_tag, proto='arp', + nw_dst=gateway_ip), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_install_dvr_process_ipv6(self): + vlan_tag = 999 + gateway_mac = '08:60:6e:7f:74:e7' + self.br.install_dvr_process_ipv6(vlan_tag=vlan_tag, + gateway_mac=gateway_mac) + expected = [ + call.add_flow(table=self.dvr_process_table_id, + proto='icmp6', dl_src=gateway_mac, actions='drop', + priority=3, dl_vlan=vlan_tag), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_delete_dvr_process_ipv6(self): + vlan_tag = 999 + gateway_mac = '08:60:6e:7f:74:e7' + self.br.delete_dvr_process_ipv6(vlan_tag=vlan_tag, + gateway_mac=gateway_mac) + expected = [ + call.delete_flows(table=self.dvr_process_table_id, + dl_vlan=vlan_tag, dl_src=gateway_mac, + proto='icmp6'), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_install_dvr_process(self): + vlan_tag = 999 + vif_mac = '00:0e:0c:5e:95:d0' + dvr_mac_address = 'f2:0b:a4:5b:b2:ab' + self.br.install_dvr_process(vlan_tag=vlan_tag, + vif_mac=vif_mac, + dvr_mac_address=dvr_mac_address) + expected = [ + call.add_flow(priority=2, table=self.dvr_process_table_id, + dl_dst=vif_mac, dl_vlan=vlan_tag, actions='drop'), + call.add_flow(priority=1, table=self.dvr_process_table_id, + dl_vlan=vlan_tag, dl_src=vif_mac, + actions='mod_dl_src:%(mac)s,resubmit(,%(next)s)' % { + 'mac': dvr_mac_address, + 'next': self.dvr_process_next_table_id, + }), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_delete_dvr_process(self): + vlan_tag = 999 + vif_mac = '00:0e:0c:5e:95:d0' + self.br.delete_dvr_process(vlan_tag=vlan_tag, + vif_mac=vif_mac) + expected = [ + call.delete_flows(table=self.dvr_process_table_id, + dl_dst=vif_mac, dl_vlan=vlan_tag), + call.delete_flows(table=self.dvr_process_table_id, + dl_vlan=vlan_tag, dl_src=vif_mac), + ] + self.assertEqual(expected, self.mock.mock_calls) diff --git a/neutron/tests/unit/plugins/openvswitch/agent/openflow/ovs_ofctl/test_br_int.py b/neutron/tests/unit/plugins/openvswitch/agent/openflow/ovs_ofctl/test_br_int.py new file mode 100644 index 000000000..27cf9de3f --- /dev/null +++ b/neutron/tests/unit/plugins/openvswitch/agent/openflow/ovs_ofctl/test_br_int.py @@ -0,0 +1,213 @@ +# Copyright (C) 2014,2015 VA Linux Systems Japan K.K. +# Copyright (C) 2014,2015 YAMAMOTO Takashi +# 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 mock + +from neutron.tests.unit.plugins.openvswitch.agent.openflow.ovs_ofctl \ + import ovs_bridge_test_base + + +call = mock.call # short hand + + +class OVSIntegrationBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase): + def setUp(self): + super(OVSIntegrationBridgeTest, self).setUp() + self.setup_bridge_mock('br-int', self.br_int_cls) + + def test_setup_default_table(self): + self.br.setup_default_table() + expected = [ + call.delete_flows(), + call.add_flow(priority=0, table=0, actions='normal'), + call.add_flow(priority=0, table=23, actions='drop'), + call.add_flow(priority=0, table=24, actions='drop'), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_provision_local_vlan(self): + port = 999 + lvid = 888 + segmentation_id = 777 + self.br.provision_local_vlan(port=port, lvid=lvid, + segmentation_id=segmentation_id) + expected = [ + call.add_flow(priority=3, dl_vlan=segmentation_id, + in_port=port, + actions='mod_vlan_vid:%s,normal' % lvid), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_provision_local_vlan_novlan(self): + port = 999 + lvid = 888 + segmentation_id = None + self.br.provision_local_vlan(port=port, lvid=lvid, + segmentation_id=segmentation_id) + expected = [ + call.add_flow(priority=3, dl_vlan=0xffff, + in_port=port, + actions='mod_vlan_vid:%s,normal' % lvid), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_reclaim_local_vlan(self): + port = 999 + segmentation_id = 777 + self.br.reclaim_local_vlan(port=port, segmentation_id=segmentation_id) + expected = [ + call.delete_flows(dl_vlan=segmentation_id, in_port=port), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_reclaim_local_vlan_novlan(self): + port = 999 + segmentation_id = None + self.br.reclaim_local_vlan(port=port, segmentation_id=segmentation_id) + expected = [ + call.delete_flows(dl_vlan=0xffff, in_port=port), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_install_dvr_to_src_mac(self): + network_type = 'vxlan' + vlan_tag = 1111 + gateway_mac = '08:60:6e:7f:74:e7' + dst_mac = '00:02:b3:13:fe:3d' + dst_port = 6666 + self.br.install_dvr_to_src_mac(network_type=network_type, + vlan_tag=vlan_tag, + gateway_mac=gateway_mac, + dst_mac=dst_mac, + dst_port=dst_port) + expected = [ + call.add_flow(priority=4, table=1, dl_dst=dst_mac, + dl_vlan=vlan_tag, + actions='strip_vlan,mod_dl_src:%(mac)s,' + 'output:%(port)s' % { + 'mac': gateway_mac, + 'port': dst_port, + }), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_delete_dvr_to_src_mac(self): + network_type = 'vxlan' + vlan_tag = 1111 + dst_mac = '00:02:b3:13:fe:3d' + self.br.delete_dvr_to_src_mac(network_type=network_type, + vlan_tag=vlan_tag, + dst_mac=dst_mac) + expected = [ + call.delete_flows(table=1, dl_dst=dst_mac, dl_vlan=vlan_tag), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_install_dvr_to_src_mac_vlan(self): + network_type = 'vlan' + vlan_tag = 1111 + gateway_mac = '08:60:6e:7f:74:e7' + dst_mac = '00:02:b3:13:fe:3d' + dst_port = 6666 + self.br.install_dvr_to_src_mac(network_type=network_type, + vlan_tag=vlan_tag, + gateway_mac=gateway_mac, + dst_mac=dst_mac, + dst_port=dst_port) + expected = [ + call.add_flow(priority=4, table=2, dl_dst=dst_mac, + dl_vlan=vlan_tag, + actions='strip_vlan,mod_dl_src:%(mac)s,' + 'output:%(port)s' % { + 'mac': gateway_mac, + 'port': dst_port, + }), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_delete_dvr_to_src_mac_vlan(self): + network_type = 'vlan' + vlan_tag = 1111 + dst_mac = '00:02:b3:13:fe:3d' + self.br.delete_dvr_to_src_mac(network_type=network_type, + vlan_tag=vlan_tag, + dst_mac=dst_mac) + expected = [ + call.delete_flows(table=2, dl_dst=dst_mac, dl_vlan=vlan_tag), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_add_dvr_mac_vlan(self): + mac = '00:02:b3:13:fe:3d' + port = 8888 + self.br.add_dvr_mac_vlan(mac=mac, port=port) + expected = [ + call.add_flow(priority=4, table=0, actions='resubmit(,2)', + dl_src=mac, in_port=port), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_remove_dvr_mac_vlan(self): + mac = '00:02:b3:13:fe:3d' + self.br.remove_dvr_mac_vlan(mac=mac) + expected = [ + call.delete_flows(eth_src=mac, table_id=0), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_add_dvr_mac_tun(self): + mac = '00:02:b3:13:fe:3d' + port = 8888 + self.br.add_dvr_mac_tun(mac=mac, port=port) + expected = [ + call.add_flow(priority=2, table=0, actions='resubmit(,1)', + dl_src=mac, in_port=port), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_remove_dvr_mac_tun(self): + mac = '00:02:b3:13:fe:3d' + port = 8888 + self.br.remove_dvr_mac_tun(mac=mac, port=port) + expected = [ + call.delete_flows(eth_src=mac, table_id=0, in_port=port), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_install_arp_spoofing_protection(self): + port = 8888 + ip_addresses = ['192.0.2.1', '192.0.2.2/32'] + self.br.install_arp_spoofing_protection(port, ip_addresses) + expected = [ + call.add_flow(proto='arp', actions='normal', + arp_spa='192.0.2.1', + priority=2, table=24, in_port=8888), + call.add_flow(proto='arp', actions='normal', + arp_spa='192.0.2.2/32', + priority=2, table=24, in_port=8888), + call.add_flow(priority=10, table=0, in_port=8888, + actions='resubmit(,24)', proto='arp') + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_delete_arp_spoofing_protection(self): + port = 8888 + self.br.delete_arp_spoofing_protection(port) + expected = [ + call.delete_flows(table_id=0, in_port=8888, proto='arp'), + call.delete_flows(table_id=24, in_port=8888), + ] + self.assertEqual(expected, self.mock.mock_calls) diff --git a/neutron/tests/unit/plugins/openvswitch/agent/openflow/ovs_ofctl/test_br_phys.py b/neutron/tests/unit/plugins/openvswitch/agent/openflow/ovs_ofctl/test_br_phys.py new file mode 100644 index 000000000..ae8753afc --- /dev/null +++ b/neutron/tests/unit/plugins/openvswitch/agent/openflow/ovs_ofctl/test_br_phys.py @@ -0,0 +1,97 @@ +# Copyright (C) 2014,2015 VA Linux Systems Japan K.K. +# Copyright (C) 2014,2015 YAMAMOTO Takashi +# 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 mock + +import neutron.plugins.openvswitch.common.constants as ovs_const +from neutron.tests.unit.plugins.openvswitch.agent.openflow.ovs_ofctl \ + import ovs_bridge_test_base + + +call = mock.call # short hand + + +class OVSPhysicalBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase, + ovs_bridge_test_base.OVSDVRProcessTestMixin): + dvr_process_table_id = ovs_const.DVR_PROCESS_VLAN + dvr_process_next_table_id = ovs_const.LOCAL_VLAN_TRANSLATION + + def setUp(self): + super(OVSPhysicalBridgeTest, self).setUp() + self.setup_bridge_mock('br-phys', self.br_phys_cls) + + def test_setup_default_table(self): + self.br.setup_default_table() + expected = [ + call.delete_flows(), + call.add_flow(priority=0, table=0, actions='normal'), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_provision_local_vlan(self): + port = 999 + lvid = 888 + segmentation_id = 777 + distributed = False + self.br.provision_local_vlan(port=port, lvid=lvid, + segmentation_id=segmentation_id, + distributed=distributed) + expected = [ + call.add_flow(priority=4, table=0, dl_vlan=lvid, in_port=port, + actions='mod_vlan_vid:%s,normal' % segmentation_id), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_provision_local_vlan_novlan(self): + port = 999 + lvid = 888 + segmentation_id = None + distributed = False + self.br.provision_local_vlan(port=port, lvid=lvid, + segmentation_id=segmentation_id, + distributed=distributed) + expected = [ + call.add_flow(priority=4, table=0, dl_vlan=lvid, in_port=port, + actions='strip_vlan,normal') + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_reclaim_local_vlan(self): + port = 999 + lvid = 888 + self.br.reclaim_local_vlan(port=port, lvid=lvid) + expected = [ + call.delete_flows(dl_vlan=lvid, in_port=port), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_add_dvr_mac_vlan(self): + mac = '00:02:b3:13:fe:3d' + port = 8888 + self.br.add_dvr_mac_vlan(mac=mac, port=port) + expected = [ + call.add_flow(priority=2, table=3, dl_src=mac, + actions='output:%s' % port), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_remove_dvr_mac_vlan(self): + mac = '00:02:b3:13:fe:3d' + self.br.remove_dvr_mac_vlan(mac=mac) + expected = [ + call.delete_flows(eth_src=mac, table_id=3), + ] + self.assertEqual(expected, self.mock.mock_calls) diff --git a/neutron/tests/unit/plugins/openvswitch/agent/openflow/ovs_ofctl/test_br_tun.py b/neutron/tests/unit/plugins/openvswitch/agent/openflow/ovs_ofctl/test_br_tun.py new file mode 100644 index 000000000..27a046d0a --- /dev/null +++ b/neutron/tests/unit/plugins/openvswitch/agent/openflow/ovs_ofctl/test_br_tun.py @@ -0,0 +1,259 @@ +# Copyright (C) 2014,2015 VA Linux Systems Japan K.K. +# Copyright (C) 2014,2015 YAMAMOTO Takashi +# 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 mock +import netaddr + +import neutron.plugins.openvswitch.common.constants as ovs_const +from neutron.tests.unit.plugins.openvswitch.agent.openflow.ovs_ofctl \ + import ovs_bridge_test_base + + +call = mock.call # short hand + + +class OVSTunnelBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase, + ovs_bridge_test_base.OVSDVRProcessTestMixin): + dvr_process_table_id = ovs_const.DVR_PROCESS + dvr_process_next_table_id = ovs_const.PATCH_LV_TO_TUN + + def setUp(self): + super(OVSTunnelBridgeTest, self).setUp() + self.setup_bridge_mock('br-tun', self.br_tun_cls) + + def test_setup_default_table(self): + patch_int_ofport = 5555 + arp_responder_enabled = False + self.br.setup_default_table(patch_int_ofport=patch_int_ofport, + arp_responder_enabled=arp_responder_enabled) + expected = [ + call.add_flow(priority=1, in_port=patch_int_ofport, + actions='resubmit(,2)'), + call.add_flow(priority=0, actions='drop'), + call.add_flow(priority=0, table=2, + dl_dst='00:00:00:00:00:00/01:00:00:00:00:00', + actions='resubmit(,20)'), + call.add_flow(priority=0, table=2, + dl_dst='01:00:00:00:00:00/01:00:00:00:00:00', + actions='resubmit(,22)'), + call.add_flow(priority=0, table=3, actions='drop'), + call.add_flow(priority=0, table=4, actions='drop'), + call.add_flow(priority=1, table=10, + actions='learn(table=20,priority=1,' + 'hard_timeout=300,NXM_OF_VLAN_TCI[0..11],' + 'NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],' + 'load:0->NXM_OF_VLAN_TCI[],' + 'load:NXM_NX_TUN_ID[]->NXM_NX_TUN_ID[],' + 'output:NXM_OF_IN_PORT[]),' + 'output:%s' % patch_int_ofport), + call.add_flow(priority=0, table=20, actions='resubmit(,22)'), + call.add_flow(priority=0, table=22, actions='drop'), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_setup_default_table_arp_responder_enabled(self): + patch_int_ofport = 5555 + arp_responder_enabled = True + self.br.setup_default_table(patch_int_ofport=patch_int_ofport, + arp_responder_enabled=arp_responder_enabled) + expected = [ + call.add_flow(priority=1, in_port=patch_int_ofport, + actions='resubmit(,2)'), + call.add_flow(priority=0, actions='drop'), + call.add_flow(priority=1, table=2, dl_dst='ff:ff:ff:ff:ff:ff', + actions='resubmit(,21)', proto='arp'), + call.add_flow(priority=0, table=2, + dl_dst='00:00:00:00:00:00/01:00:00:00:00:00', + actions='resubmit(,20)'), + call.add_flow(priority=0, table=2, + dl_dst='01:00:00:00:00:00/01:00:00:00:00:00', + actions='resubmit(,22)'), + call.add_flow(priority=0, table=3, actions='drop'), + call.add_flow(priority=0, table=4, actions='drop'), + call.add_flow(priority=1, table=10, + actions='learn(table=20,priority=1,' + 'hard_timeout=300,NXM_OF_VLAN_TCI[0..11],' + 'NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],' + 'load:0->NXM_OF_VLAN_TCI[],' + 'load:NXM_NX_TUN_ID[]->NXM_NX_TUN_ID[],' + 'output:NXM_OF_IN_PORT[]),' + 'output:%s' % patch_int_ofport), + call.add_flow(priority=0, table=20, actions='resubmit(,22)'), + call.add_flow(priority=0, table=21, actions='resubmit(,22)'), + call.add_flow(priority=0, table=22, actions='drop'), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_provision_local_vlan(self): + network_type = 'vxlan' + lvid = 888 + segmentation_id = 777 + distributed = False + self.br.provision_local_vlan(network_type=network_type, lvid=lvid, + segmentation_id=segmentation_id, + distributed=distributed) + expected = [ + call.add_flow(priority=1, tun_id=segmentation_id, + actions='mod_vlan_vid:%s,resubmit(,10)' % lvid, + table=4), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_reclaim_local_vlan(self): + network_type = 'vxlan' + segmentation_id = 777 + self.br.reclaim_local_vlan(network_type=network_type, + segmentation_id=segmentation_id) + expected = [ + call.delete_flows(tun_id=segmentation_id, table=4), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_install_flood_to_tun(self): + vlan = 3333 + tun_id = 2222 + ports = [11, 44, 22, 33] + self.br.install_flood_to_tun(vlan=vlan, + tun_id=tun_id, + ports=ports) + expected = [ + call.mod_flow(table=22, dl_vlan=vlan, + actions='strip_vlan,set_tunnel:%(tun)s,' + 'output:%(ports)s' % { + 'tun': tun_id, + 'ports': ','.join(map(str, ports)), + }), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_delete_flood_to_tun(self): + vlan = 3333 + self.br.delete_flood_to_tun(vlan=vlan) + expected = [ + call.delete_flows(table=22, dl_vlan=vlan), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_install_unicast_to_tun(self): + vlan = 3333 + port = 55 + mac = '08:60:6e:7f:74:e7' + tun_id = 2222 + self.br.install_unicast_to_tun(vlan=vlan, + tun_id=tun_id, + port=port, + mac=mac) + expected = [ + call.add_flow(priority=2, table=20, dl_dst=mac, dl_vlan=vlan, + actions='strip_vlan,set_tunnel:%(tun)s,' + 'output:%(port)s' % { + 'tun': tun_id, + 'port': port, + }), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_delete_unicast_to_tun(self): + vlan = 3333 + mac = '08:60:6e:7f:74:e7' + self.br.delete_unicast_to_tun(vlan=vlan, mac=mac) + expected = [ + call.delete_flows(table=20, dl_dst=mac, dl_vlan=vlan), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_delete_unicast_to_tun_without_mac(self): + vlan = 3333 + mac = None + self.br.delete_unicast_to_tun(vlan=vlan, mac=mac) + expected = [ + call.delete_flows(table=20, dl_vlan=vlan), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_install_arp_responder(self): + vlan = 3333 + ip = '192.0.2.1' + mac = '08:60:6e:7f:74:e7' + self.br.install_arp_responder(vlan=vlan, ip=ip, mac=mac) + expected = [ + call.add_flow(proto='arp', nw_dst=ip, + actions='move:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[],' + 'mod_dl_src:%(mac)s,load:0x2->NXM_OF_ARP_OP[],' + 'move:NXM_NX_ARP_SHA[]->NXM_NX_ARP_THA[],' + 'move:NXM_OF_ARP_SPA[]->NXM_OF_ARP_TPA[],' + 'load:%(mac)#x->NXM_NX_ARP_SHA[],' + 'load:%(ip)#x->NXM_OF_ARP_SPA[],in_port' % { + 'mac': netaddr.EUI(mac, + dialect=netaddr.mac_unix), + 'ip': netaddr.IPAddress(ip), + }, + priority=1, table=21, dl_vlan=vlan), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_delete_arp_responder(self): + vlan = 3333 + ip = '192.0.2.1' + self.br.delete_arp_responder(vlan=vlan, ip=ip) + expected = [ + call.delete_flows(table=21, dl_vlan=vlan, proto='arp', nw_dst=ip), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_delete_arp_responder_without_ip(self): + vlan = 3333 + ip = None + self.br.delete_arp_responder(vlan=vlan, ip=ip) + expected = [ + call.delete_flows(table=21, dl_vlan=vlan, proto='arp'), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_setup_tunnel_port(self): + network_type = 'vxlan' + port = 11111 + self.br.setup_tunnel_port(network_type=network_type, port=port) + expected = [ + call.add_flow(priority=1, in_port=port, actions='resubmit(,4)'), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_cleanup_tunnel_port(self): + port = 11111 + self.br.cleanup_tunnel_port(port=port) + expected = [ + call.delete_flows(in_port=port), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_add_dvr_mac_tun(self): + mac = '00:02:b3:13:fe:3d' + port = 8888 + self.br.add_dvr_mac_tun(mac=mac, port=port) + expected = [ + call.add_flow(priority=1, table=9, dl_src=mac, + actions='output:%s' % port), + ] + self.assertEqual(expected, self.mock.mock_calls) + + def test_remove_dvr_mac_tun(self): + mac = '00:02:b3:13:fe:3d' + self.br.remove_dvr_mac_tun(mac=mac) + expected = [ + call.delete_flows(eth_src=mac, table_id=9), + ] + self.assertEqual(expected, self.mock.mock_calls) diff --git a/neutron/tests/unit/plugins/openvswitch/agent/ovs_test_base.py b/neutron/tests/unit/plugins/openvswitch/agent/ovs_test_base.py new file mode 100644 index 000000000..0932a1ea2 --- /dev/null +++ b/neutron/tests/unit/plugins/openvswitch/agent/ovs_test_base.py @@ -0,0 +1,54 @@ +# Copyright (C) 2014,2015 VA Linux Systems Japan K.K. +# Copyright (C) 2014 Fumihiko Kakuma +# Copyright (C) 2014,2015 YAMAMOTO Takashi +# 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_utils import importutils + +from neutron.tests import base + + +_AGENT_PACKAGE = 'neutron.plugins.openvswitch.agent' +_AGENT_NAME = _AGENT_PACKAGE + '.ovs_neutron_agent' +_DVR_AGENT_NAME = 'neutron.plugins.openvswitch.agent.ovs_dvr_neutron_agent' + + +class OVSAgentConfigTestBase(base.BaseTestCase): + def setUp(self): + super(OVSAgentConfigTestBase, self).setUp() + self.mod_agent = importutils.import_module(_AGENT_NAME) + self.mod_dvr_agent = importutils.import_module(_DVR_AGENT_NAME) + + +class OVSAgentTestBase(OVSAgentConfigTestBase): + def setUp(self): + super(OVSAgentTestBase, self).setUp() + self.br_int_cls = importutils.import_class(self._BR_INT_CLASS) + self.br_phys_cls = importutils.import_class(self._BR_PHYS_CLASS) + self.br_tun_cls = importutils.import_class(self._BR_TUN_CLASS) + + def _bridge_classes(self): + return { + 'br_int': self.br_int_cls, + 'br_phys': self.br_phys_cls, + 'br_tun': self.br_tun_cls, + } + + +class OVSOFCtlTestBase(OVSAgentTestBase): + _DRIVER_PACKAGE = _AGENT_PACKAGE + '.openflow.ovs_ofctl' + _BR_INT_CLASS = _DRIVER_PACKAGE + '.br_int.OVSIntegrationBridge' + _BR_TUN_CLASS = _DRIVER_PACKAGE + '.br_tun.OVSTunnelBridge' + _BR_PHYS_CLASS = _DRIVER_PACKAGE + '.br_phys.OVSPhysicalBridge' diff --git a/neutron/tests/unit/plugins/openvswitch/agent/test_ovs_neutron_agent.py b/neutron/tests/unit/plugins/openvswitch/agent/test_ovs_neutron_agent.py index c21d7da93..fb068790c 100644 --- a/neutron/tests/unit/plugins/openvswitch/agent/test_ovs_neutron_agent.py +++ b/neutron/tests/unit/plugins/openvswitch/agent/test_ovs_neutron_agent.py @@ -17,7 +17,6 @@ import sys import time import mock -import netaddr from oslo_config import cfg from oslo_log import log import oslo_messaging @@ -30,9 +29,8 @@ from neutron.agent.linux import ip_lib from neutron.common import constants as n_const from neutron.plugins.common import constants as p_const from neutron.plugins.ml2.drivers.l2pop import rpc as l2pop_rpc -from neutron.plugins.openvswitch.agent import ovs_neutron_agent from neutron.plugins.openvswitch.common import constants -from neutron.tests import base +from neutron.tests.unit.plugins.openvswitch.agent import ovs_test_base NOTIFIER = 'neutron.plugins.ml2.rpc.AgentNotifierApi' @@ -48,10 +46,10 @@ class FakeVif(object): port_name = 'name' -class CreateAgentConfigMap(base.BaseTestCase): +class CreateAgentConfigMap(ovs_test_base.OVSAgentConfigTestBase): def test_create_agent_config_map_succeeds(self): - self.assertTrue(ovs_neutron_agent.create_agent_config_map(cfg.CONF)) + self.assertTrue(self.mod_agent.create_agent_config_map(cfg.CONF)) def test_create_agent_config_map_fails_for_invalid_tunnel_config(self): # An ip address is required for tunneling but there is no default, @@ -59,29 +57,29 @@ class CreateAgentConfigMap(base.BaseTestCase): cfg.CONF.set_override('tunnel_types', [p_const.TYPE_GRE], group='AGENT') with testtools.ExpectedException(ValueError): - ovs_neutron_agent.create_agent_config_map(cfg.CONF) + self.mod_agent.create_agent_config_map(cfg.CONF) cfg.CONF.set_override('tunnel_types', [p_const.TYPE_VXLAN], group='AGENT') with testtools.ExpectedException(ValueError): - ovs_neutron_agent.create_agent_config_map(cfg.CONF) + self.mod_agent.create_agent_config_map(cfg.CONF) def test_create_agent_config_map_fails_no_local_ip(self): # An ip address is required for tunneling but there is no default cfg.CONF.set_override('tunnel_types', [p_const.TYPE_VXLAN], group='AGENT') with testtools.ExpectedException(ValueError): - ovs_neutron_agent.create_agent_config_map(cfg.CONF) + self.mod_agent.create_agent_config_map(cfg.CONF) def test_create_agent_config_map_fails_for_invalid_tunnel_type(self): cfg.CONF.set_override('tunnel_types', ['foobar'], group='AGENT') with testtools.ExpectedException(ValueError): - ovs_neutron_agent.create_agent_config_map(cfg.CONF) + self.mod_agent.create_agent_config_map(cfg.CONF) def test_create_agent_config_map_multiple_tunnel_types(self): cfg.CONF.set_override('local_ip', '10.10.10.10', group='OVS') cfg.CONF.set_override('tunnel_types', [p_const.TYPE_GRE, p_const.TYPE_VXLAN], group='AGENT') - cfgmap = ovs_neutron_agent.create_agent_config_map(cfg.CONF) + cfgmap = self.mod_agent.create_agent_config_map(cfg.CONF) self.assertEqual(cfgmap['tunnel_types'], [p_const.TYPE_GRE, p_const.TYPE_VXLAN]) @@ -90,11 +88,11 @@ class CreateAgentConfigMap(base.BaseTestCase): # Verify setting only enable_tunneling will default tunnel_type to GRE cfg.CONF.set_override('enable_distributed_routing', True, group='AGENT') - cfgmap = ovs_neutron_agent.create_agent_config_map(cfg.CONF) + cfgmap = self.mod_agent.create_agent_config_map(cfg.CONF) self.assertEqual(cfgmap['enable_distributed_routing'], True) -class TestOvsNeutronAgent(base.BaseTestCase): +class TestOvsNeutronAgent(object): def setUp(self): super(TestOvsNeutronAgent, self).setUp() @@ -107,7 +105,7 @@ class TestOvsNeutronAgent(base.BaseTestCase): group='SECURITYGROUP') cfg.CONF.set_default('quitting_rpc_timeout', 10, 'AGENT') cfg.CONF.set_default('prevent_arp_spoofing', False, 'AGENT') - kwargs = ovs_neutron_agent.create_agent_config_map(cfg.CONF) + kwargs = self.mod_agent.create_agent_config_map(cfg.CONF) class MockFixedIntervalLoopingCall(object): def __init__(self, f): @@ -117,18 +115,11 @@ class TestOvsNeutronAgent(base.BaseTestCase): self.f() with contextlib.nested( - mock.patch('neutron.plugins.openvswitch.agent.ovs_neutron_agent.' - 'OVSNeutronAgent.setup_integration_br'), - mock.patch('neutron.plugins.openvswitch.agent.ovs_neutron_agent.' - 'OVSNeutronAgent.setup_ancillary_bridges', + mock.patch.object(self.mod_agent.OVSNeutronAgent, + 'setup_integration_br'), + mock.patch.object(self.mod_agent.OVSNeutronAgent, + 'setup_ancillary_bridges', return_value=[]), - mock.patch('neutron.agent.common.ovs_lib.OVSBridge.' - 'create'), - mock.patch('neutron.agent.common.ovs_lib.OVSBridge.' - 'set_secure_mode'), - mock.patch('neutron.agent.common.ovs_lib.OVSBridge.' - 'get_local_port_mac', - return_value='00:00:00:00:00:01'), mock.patch('neutron.agent.linux.utils.get_interface_mac', return_value='00:00:00:00:00:01'), mock.patch('neutron.agent.common.ovs_lib.BaseOVS.get_bridges'), @@ -137,11 +128,12 @@ class TestOvsNeutronAgent(base.BaseTestCase): new=MockFixedIntervalLoopingCall), mock.patch('neutron.agent.common.ovs_lib.OVSBridge.' 'get_vif_ports', return_value=[])): - self.agent = ovs_neutron_agent.OVSNeutronAgent(**kwargs) + self.agent = self.mod_agent.OVSNeutronAgent(self._bridge_classes(), + **kwargs) # set back to true because initial report state will succeed due # to mocked out RPC calls self.agent.use_call = True - self.agent.tun_br = mock.Mock() + self.agent.tun_br = self.br_tun_cls(br_name='br-tun') self.agent.sg_agent = mock.Mock() def _mock_port_bound(self, ofport=None, new_local_vlan=None, @@ -153,22 +145,18 @@ class TestOvsNeutronAgent(base.BaseTestCase): 'ip_address': '1.1.1.1'}] if old_local_vlan is not None: self.agent.local_vlan_map[net_uuid] = ( - ovs_neutron_agent.LocalVLANMapping( + self.mod_agent.LocalVLANMapping( old_local_vlan, None, None, None)) - with contextlib.nested( - mock.patch('neutron.agent.common.ovs_lib.OVSBridge.' - 'set_db_attribute', return_value=True), - mock.patch('neutron.agent.common.ovs_lib.OVSBridge.' - 'db_get_val', return_value={}), - mock.patch.object(self.agent.int_br, 'delete_flows') - ) as (set_ovs_db_func, get_ovs_db_func, delete_flows_func): + with mock.patch.object(self.agent, 'int_br', autospec=True) as int_br: + int_br.db_get_val.return_value = {} + int_br.set_db_attribute.return_value = True self.agent.port_bound(port, net_uuid, 'local', None, None, fixed_ips, "compute:None", False) vlan_mapping = {'net_uuid': net_uuid, 'network_type': 'local', 'physical_network': None, 'segmentation_id': None} - set_ovs_db_func.assert_called_once_with( + int_br.set_db_attribute.assert_called_once_with( "Port", mock.ANY, "other_config", vlan_mapping) def test_check_agent_configurations_for_dvr_raises(self): @@ -202,29 +190,24 @@ class TestOvsNeutronAgent(base.BaseTestCase): def _test_port_dead(self, cur_tag=None): port = mock.Mock() port.ofport = 1 - with contextlib.nested( - mock.patch('neutron.agent.common.ovs_lib.OVSBridge.' - 'set_db_attribute', return_value=True), - mock.patch('neutron.agent.common.ovs_lib.OVSBridge.' - 'db_get_val', return_value=cur_tag), - mock.patch.object(self.agent.int_br, 'add_flow') - ) as (set_ovs_db_func, get_ovs_db_func, add_flow_func): + with mock.patch.object(self.agent, 'int_br') as int_br: + int_br.db_get_val.return_value = cur_tag self.agent.port_dead(port) - get_ovs_db_func.assert_called_once_with("Port", mock.ANY, "tag") - if cur_tag == ovs_neutron_agent.DEAD_VLAN_TAG: - self.assertFalse(set_ovs_db_func.called) - self.assertFalse(add_flow_func.called) + if cur_tag == self.mod_agent.DEAD_VLAN_TAG: + self.assertFalse(int_br.set_db_attribute.called) + self.assertFalse(int_br.drop_port.called) else: - set_ovs_db_func.assert_called_once_with( - "Port", mock.ANY, "tag", ovs_neutron_agent.DEAD_VLAN_TAG) - add_flow_func.assert_called_once_with( - priority=2, in_port=port.ofport, actions="drop") + int_br.assert_has_calls([ + mock.call.set_db_attribute("Port", mock.ANY, "tag", + self.mod_agent.DEAD_VLAN_TAG), + mock.call.drop_port(in_port=port.ofport), + ]) def test_port_dead(self): self._test_port_dead() def test_port_dead_with_port_already_dead(self): - self._test_port_dead(ovs_neutron_agent.DEAD_VLAN_TAG) + self._test_port_dead(self.mod_agent.DEAD_VLAN_TAG) def mock_scan_ports(self, vif_port_set=None, registered_ports=None, updated_ports=None, port_tags_dict=None): @@ -289,10 +272,10 @@ class TestOvsNeutronAgent(base.BaseTestCase): self.assertEqual(expected, actual) def test_update_ports_returns_changed_vlan(self): - br = ovs_lib.OVSBridge('br-int') + br = self.br_int_cls('br-int') mac = "ca:fe:de:ad:be:ef" port = ovs_lib.VifPort(1, 1, 1, mac, br) - lvm = ovs_neutron_agent.LocalVLANMapping( + lvm = self.mod_agent.LocalVLANMapping( 1, '1', None, 1, {port.vif_id: port}) local_vlan_map = {'1': lvm} vif_port_set = set([1, 3]) @@ -302,7 +285,10 @@ class TestOvsNeutronAgent(base.BaseTestCase): added=set([3]), current=vif_port_set, removed=set([2]), updated=set([1]) ) - with mock.patch.dict(self.agent.local_vlan_map, local_vlan_map): + with contextlib.nested( + mock.patch.dict(self.agent.local_vlan_map, local_vlan_map), + mock.patch.object(self.agent, 'tun_br', autospec=True), + ): actual = self.mock_scan_ports( vif_port_set, registered_ports, port_tags_dict=port_tags_dict) self.assertEqual(expected, actual) @@ -315,7 +301,7 @@ class TestOvsNeutronAgent(base.BaseTestCase): mock.patch.object(self.agent.int_br, 'get_vif_port_by_id', return_value=mock.Mock())): self.assertRaises( - ovs_neutron_agent.DeviceListRetrievalError, + self.mod_agent.DeviceListRetrievalError, self.agent.treat_devices_added_or_updated, [{}], False) def _mock_treat_devices_added_updated(self, details, port, func_name): @@ -543,46 +529,37 @@ class TestOvsNeutronAgent(base.BaseTestCase): mock.patch.object(ip_lib, "device_exists"), mock.patch.object(sys, "exit"), mock.patch.object(utils, "execute"), - mock.patch.object(ovs_lib.OVSBridge, "remove_all_flows"), - mock.patch.object(ovs_lib.OVSBridge, "add_flow"), - mock.patch.object(ovs_lib.OVSBridge, "add_patch_port"), - mock.patch.object(ovs_lib.OVSBridge, "delete_port"), - mock.patch.object(ovs_lib.OVSBridge, "set_db_attribute"), - mock.patch.object(self.agent.int_br, "add_flow"), - mock.patch.object(self.agent.int_br, "add_patch_port"), - mock.patch.object(self.agent.int_br, "delete_port"), - mock.patch.object(self.agent.int_br, "set_db_attribute"), - ) as (devex_fn, sysexit_fn, utilsexec_fn, remflows_fn, ovs_add_flow_fn, - ovs_addpatch_port_fn, ovs_delport_fn, ovs_set_attr_fn, - br_add_flow_fn, br_addpatch_port_fn, br_delport_fn, - br_set_attr_fn): + mock.patch.object(self.agent, 'br_phys_cls'), + mock.patch.object(self.agent, 'int_br'), + ) as (devex_fn, sysexit_fn, utilsexec_fn, + phys_br_cls, int_br): devex_fn.return_value = True parent = mock.MagicMock() - parent.attach_mock(ovs_addpatch_port_fn, 'phy_add_patch_port') - parent.attach_mock(ovs_add_flow_fn, 'phy_add_flow') - parent.attach_mock(ovs_set_attr_fn, 'phy_set_attr') - parent.attach_mock(br_addpatch_port_fn, 'int_add_patch_port') - parent.attach_mock(br_add_flow_fn, 'int_add_flow') - parent.attach_mock(br_set_attr_fn, 'int_set_attr') - - ovs_addpatch_port_fn.return_value = "phy_ofport" - br_addpatch_port_fn.return_value = "int_ofport" + phys_br = phys_br_cls() + parent.attach_mock(phys_br_cls, 'phys_br_cls') + parent.attach_mock(phys_br, 'phys_br') + parent.attach_mock(int_br, 'int_br') + phys_br.add_patch_port.return_value = "phy_ofport" + int_br.add_patch_port.return_value = "int_ofport" self.agent.setup_physical_bridges({"physnet1": "br-eth"}) expected_calls = [ - mock.call.phy_add_flow(priority=1, actions='normal'), - mock.call.int_add_patch_port('int-br-eth', - constants.NONEXISTENT_PEER), - mock.call.phy_add_patch_port('phy-br-eth', - constants.NONEXISTENT_PEER), - mock.call.int_add_flow(priority=2, in_port='int_ofport', - actions='drop'), - mock.call.phy_add_flow(priority=2, in_port='phy_ofport', - actions='drop'), - mock.call.int_set_attr('Interface', 'int-br-eth', - 'options:peer', 'phy-br-eth'), - mock.call.phy_set_attr('Interface', 'phy-br-eth', - 'options:peer', 'int-br-eth'), - + mock.call.phys_br_cls('br-eth'), + mock.call.phys_br.setup_controllers(mock.ANY), + mock.call.phys_br.setup_default_table(), + mock.call.int_br.delete_port('int-br-eth'), + mock.call.phys_br.delete_port('phy-br-eth'), + mock.call.int_br.add_patch_port('int-br-eth', + constants.NONEXISTENT_PEER), + mock.call.phys_br.add_patch_port('phy-br-eth', + constants.NONEXISTENT_PEER), + mock.call.int_br.drop_port(in_port='int_ofport'), + mock.call.phys_br.drop_port(in_port='phy_ofport'), + mock.call.int_br.set_db_attribute('Interface', 'int-br-eth', + 'options:peer', + 'phy-br-eth'), + mock.call.phys_br.set_db_attribute('Interface', 'phy-br-eth', + 'options:peer', + 'int-br-eth'), ] parent.assert_has_calls(expected_calls) self.assertEqual(self.agent.int_ofports["physnet1"], @@ -596,19 +573,14 @@ class TestOvsNeutronAgent(base.BaseTestCase): mock.patch.object(ip_lib, "device_exists"), mock.patch.object(sys, "exit"), mock.patch.object(utils, "execute"), - mock.patch.object(ovs_lib.OVSBridge, "remove_all_flows"), - mock.patch.object(ovs_lib.OVSBridge, "add_flow"), - mock.patch.object(ovs_lib.OVSBridge, "add_port"), - mock.patch.object(ovs_lib.OVSBridge, "delete_port"), - mock.patch.object(self.agent.int_br, "add_port"), - mock.patch.object(self.agent.int_br, "delete_port"), + mock.patch.object(self.agent, 'br_phys_cls'), + mock.patch.object(self.agent, 'int_br'), mock.patch.object(ip_lib.IPWrapper, "add_veth"), mock.patch.object(ip_lib.IpLinkCommand, "delete"), mock.patch.object(ip_lib.IpLinkCommand, "set_up"), mock.patch.object(ip_lib.IpLinkCommand, "set_mtu"), mock.patch.object(ovs_lib.BaseOVS, "get_bridges") - ) as (devex_fn, sysexit_fn, utilsexec_fn, remflows_fn, ovs_addfl_fn, - ovs_addport_fn, ovs_delport_fn, br_addport_fn, br_delport_fn, + ) as (devex_fn, sysexit_fn, utilsexec_fn, phys_br_cls, int_br, addveth_fn, linkdel_fn, linkset_fn, linkmtu_fn, get_br_fn): devex_fn.return_value = True parent = mock.MagicMock() @@ -617,8 +589,9 @@ class TestOvsNeutronAgent(base.BaseTestCase): parent.attach_mock(addveth_fn, 'add_veth') addveth_fn.return_value = (ip_lib.IPDevice("int-br-eth1"), ip_lib.IPDevice("phy-br-eth1")) - ovs_addport_fn.return_value = "phys_veth_ofport" - br_addport_fn.return_value = "int_veth_ofport" + phys_br = phys_br_cls() + phys_br.add_port.return_value = "phys_veth_ofport" + int_br.add_port.return_value = "int_veth_ofport" get_br_fn.return_value = ["br-eth"] self.agent.setup_physical_bridges({"physnet1": "br-eth"}) expected_calls = [mock.call.link_delete(), @@ -649,15 +622,10 @@ class TestOvsNeutronAgent(base.BaseTestCase): with contextlib.nested( mock.patch.object(self.agent.int_br, "add_patch_port", return_value=1), - mock.patch.object(self.agent.tun_br, "add_patch_port", - return_value=2), - mock.patch.object(self.agent.tun_br, "remove_all_flows"), - mock.patch.object(self.agent.tun_br, "add_flow"), - mock.patch.object(ovs_lib, "OVSBridge"), - mock.patch.object(self.agent.tun_br, "reset_bridge"), + mock.patch.object(self.agent, 'tun_br', autospec=True), mock.patch.object(sys, "exit") - ) as (intbr_patch_fn, tunbr_patch_fn, remove_all_fn, - add_flow_fn, ovs_br_fn, reset_br_fn, exit_fn): + ) as (intbr_patch_fn, tun_br, exit_fn): + tun_br.add_patch_port.return_value = 2 self.agent.reset_tunnel_br(None) self.agent.setup_tunnel_br() self.assertTrue(intbr_patch_fn.called) @@ -755,50 +723,22 @@ class TestOvsNeutronAgent(base.BaseTestCase): [l2pop_rpc.PortInfo(FAKE_MAC, FAKE_IP1), n_const.FLOODING_ENTRY]}}} - class ActionMatcher(object): - def __init__(self, action_str): - self.ordered = self.order_ports(action_str) - - def order_ports(self, action_str): - halves = action_str.split('output:') - ports = sorted(halves.pop().split(',')) - halves.append(','.join(ports)) - return 'output:'.join(halves) - - def __eq__(self, other): - return self.ordered == self.order_ports(other) - with contextlib.nested( - mock.patch.object(self.agent.tun_br, 'deferred'), - mock.patch.object(self.agent.tun_br, 'do_action_flows'), - mock.patch.object(self.agent, '_setup_tunnel_port'), - ) as (deferred_fn, do_action_flows_fn, add_tun_fn): - deferred_fn.return_value = ovs_lib.DeferredOVSBridge( - self.agent.tun_br) + mock.patch.object(self.agent, 'tun_br', autospec=True), + mock.patch.object(self.agent, '_setup_tunnel_port', autospec=True), + ) as (tun_br, add_tun_fn): self.agent.fdb_add(None, fdb_entry) self.assertFalse(add_tun_fn.called) - actions = (constants.ARP_RESPONDER_ACTIONS % - {'mac': netaddr.EUI(FAKE_MAC, dialect=netaddr.mac_unix), - 'ip': netaddr.IPAddress(FAKE_IP1)}) + deferred_br_call = mock.call.deferred().__enter__() expected_calls = [ - mock.call('add', [dict(table=constants.ARP_RESPONDER, - priority=1, - proto='arp', - dl_vlan='vlan1', - nw_dst=FAKE_IP1, - actions=actions), - dict(table=constants.UCAST_TO_TUN, - priority=2, - dl_vlan='vlan1', - dl_dst=FAKE_MAC, - actions='strip_vlan,' - 'set_tunnel:seg1,output:2')]), - mock.call('mod', [dict(table=constants.FLOOD_TO_TUN, - dl_vlan='vlan1', - actions=ActionMatcher('strip_vlan,' - 'set_tunnel:seg1,output:1,2'))]), + deferred_br_call.install_arp_responder('vlan1', FAKE_IP1, + FAKE_MAC), + deferred_br_call.install_unicast_to_tun('vlan1', 'seg1', '2', + FAKE_MAC), + deferred_br_call.install_flood_to_tun('vlan1', 'seg1', + set(['1', '2'])), ] - do_action_flows_fn.assert_has_calls(expected_calls) + tun_br.assert_has_calls(expected_calls) def test_fdb_del_flows(self): self._prepare_l2_pop_ofports() @@ -809,28 +749,21 @@ class TestOvsNeutronAgent(base.BaseTestCase): {'2.2.2.2': [l2pop_rpc.PortInfo(FAKE_MAC, FAKE_IP1), n_const.FLOODING_ENTRY]}}} - with contextlib.nested( - mock.patch.object(self.agent.tun_br, 'deferred'), - mock.patch.object(self.agent.tun_br, 'do_action_flows'), - ) as (deferred_fn, do_action_flows_fn): - deferred_fn.return_value = ovs_lib.DeferredOVSBridge( - self.agent.tun_br) + with mock.patch.object(self.agent, 'tun_br', autospec=True) as br_tun: self.agent.fdb_remove(None, fdb_entry) + deferred_br_call = mock.call.deferred().__enter__() expected_calls = [ - mock.call('mod', [dict(table=constants.FLOOD_TO_TUN, - dl_vlan='vlan2', - actions='strip_vlan,' - 'set_tunnel:seg2,output:1')]), - mock.call('del', [dict(table=constants.ARP_RESPONDER, - proto='arp', - dl_vlan='vlan2', - nw_dst=FAKE_IP1), - dict(table=constants.UCAST_TO_TUN, - dl_vlan='vlan2', - dl_dst=FAKE_MAC), - dict(in_port='2')]), + mock.call.deferred(), + mock.call.deferred().__enter__(), + deferred_br_call.delete_arp_responder('vlan2', FAKE_IP1), + deferred_br_call.delete_unicast_to_tun('vlan2', FAKE_MAC), + deferred_br_call.install_flood_to_tun('vlan2', 'seg2', + set(['1'])), + deferred_br_call.delete_port('gre-02020202'), + deferred_br_call.cleanup_tunnel_port('2'), + mock.call.deferred().__exit__(None, None, None), ] - do_action_flows_fn.assert_has_calls(expected_calls) + br_tun.assert_has_calls(expected_calls) def test_fdb_add_port(self): self._prepare_l2_pop_ofports() @@ -840,17 +773,15 @@ class TestOvsNeutronAgent(base.BaseTestCase): 'ports': {'1.1.1.1': [l2pop_rpc.PortInfo(FAKE_MAC, FAKE_IP1)]}}} with contextlib.nested( - mock.patch.object(self.agent.tun_br, 'deferred'), - mock.patch.object(self.agent.tun_br, 'do_action_flows'), + mock.patch.object(self.agent, 'tun_br', autospec=True), mock.patch.object(self.agent, '_setup_tunnel_port') - ) as (deferred_fn, do_action_flows_fn, add_tun_fn): - deferred_br = ovs_lib.DeferredOVSBridge(self.agent.tun_br) - deferred_fn.return_value = deferred_br + ) as (tun_br, add_tun_fn): self.agent.fdb_add(None, fdb_entry) self.assertFalse(add_tun_fn.called) fdb_entry['net1']['ports']['10.10.10.10'] = [ l2pop_rpc.PortInfo(FAKE_MAC, FAKE_IP1)] self.agent.fdb_add(None, fdb_entry) + deferred_br = tun_br.deferred().__enter__() add_tun_fn.assert_called_with( deferred_br, 'gre-0a0a0a0a', '10.10.10.10', 'gre') @@ -862,13 +793,12 @@ class TestOvsNeutronAgent(base.BaseTestCase): 'ports': {'2.2.2.2': [n_const.FLOODING_ENTRY]}}} with contextlib.nested( mock.patch.object(self.agent.tun_br, 'deferred'), - mock.patch.object(self.agent.tun_br, 'do_action_flows'), - mock.patch.object(self.agent.tun_br, 'delete_port') - ) as (deferred_fn, do_action_flows_fn, delete_port_fn): - deferred_br = ovs_lib.DeferredOVSBridge(self.agent.tun_br) - deferred_fn.return_value = deferred_br + mock.patch.object(self.agent.tun_br, 'delete_port'), + ) as (defer_fn, delete_port_fn): self.agent.fdb_remove(None, fdb_entry) - delete_port_fn.assert_called_once_with('gre-02020202') + deferred_br = defer_fn().__enter__() + deferred_br.delete_port.assert_called_once_with('gre-02020202') + self.assertFalse(delete_port_fn.called) def test_fdb_update_chg_ip(self): self._prepare_l2_pop_ofports() @@ -877,31 +807,13 @@ class TestOvsNeutronAgent(base.BaseTestCase): {'agent_ip': {'before': [l2pop_rpc.PortInfo(FAKE_MAC, FAKE_IP1)], 'after': [l2pop_rpc.PortInfo(FAKE_MAC, FAKE_IP2)]}}}} - with contextlib.nested( - mock.patch.object(self.agent.tun_br, 'deferred'), - mock.patch.object(self.agent.tun_br, 'do_action_flows'), - ) as (deferred_fn, do_action_flows_fn): - deferred_br = ovs_lib.DeferredOVSBridge(self.agent.tun_br) - deferred_fn.return_value = deferred_br + with mock.patch.object(self.agent.tun_br, 'deferred') as deferred_fn: self.agent.fdb_update(None, fdb_entries) - actions = (constants.ARP_RESPONDER_ACTIONS % - {'mac': netaddr.EUI(FAKE_MAC, dialect=netaddr.mac_unix), - 'ip': netaddr.IPAddress(FAKE_IP2)}) - expected_calls = [ - mock.call('add', [dict(table=constants.ARP_RESPONDER, - priority=1, - proto='arp', - dl_vlan='vlan1', - nw_dst=FAKE_IP2, - actions=actions)]), - mock.call('del', [dict(table=constants.ARP_RESPONDER, - proto='arp', - dl_vlan='vlan1', - nw_dst=FAKE_IP1)]) - ] - do_action_flows_fn.assert_has_calls(expected_calls) - self.assertEqual(len(expected_calls), - len(do_action_flows_fn.mock_calls)) + deferred_br = deferred_fn().__enter__() + deferred_br.assert_has_calls([ + mock.call.install_arp_responder('vlan1', FAKE_IP2, FAKE_MAC), + mock.call.delete_arp_responder('vlan1', FAKE_IP1) + ]) def test_del_fdb_flow_idempotency(self): lvm = mock.Mock() @@ -922,22 +834,17 @@ class TestOvsNeutronAgent(base.BaseTestCase): self._prepare_l2_pop_ofports() self.agent.l2_pop = True self.agent.enable_tunneling = True - with mock.patch.object( - self.agent.tun_br, 'cleanup_tunnel_port' - ) as clean_tun_fn: + with mock.patch.object(self.agent, 'tun_br', autospec=True) as tun_br: self.agent.reclaim_local_vlan('net1') - self.assertFalse(clean_tun_fn.called) + self.assertFalse(tun_br.cleanup_tunnel_port.called) def test_recl_lv_port_to_remove(self): self._prepare_l2_pop_ofports() self.agent.l2_pop = True self.agent.enable_tunneling = True - with contextlib.nested( - mock.patch.object(self.agent.tun_br, 'delete_port'), - mock.patch.object(self.agent.tun_br, 'delete_flows') - ) as (del_port_fn, del_flow_fn): + with mock.patch.object(self.agent, 'tun_br', autospec=True) as tun_br: self.agent.reclaim_local_vlan('net2') - del_port_fn.assert_called_once_with('gre-02020202') + tun_br.delete_port.assert_called_once_with('gre-02020202') def test_daemon_loop_uses_polling_manager(self): with mock.patch( @@ -952,7 +859,7 @@ class TestOvsNeutronAgent(base.BaseTestCase): with contextlib.nested( mock.patch.object(self.agent.tun_br, 'add_tunnel_port', return_value=ovs_lib.INVALID_OFPORT), - mock.patch.object(ovs_neutron_agent.LOG, 'error') + mock.patch.object(self.mod_agent.LOG, 'error') ) as (add_tunnel_port_fn, log_error_fn): ofport = self.agent._setup_tunnel_port( self.agent.tun_br, 'gre-1', 'remote_ip', p_const.TYPE_GRE) @@ -968,7 +875,7 @@ class TestOvsNeutronAgent(base.BaseTestCase): with contextlib.nested( mock.patch.object(self.agent.tun_br, 'add_tunnel_port', return_value=ovs_lib.INVALID_OFPORT), - mock.patch.object(ovs_neutron_agent.LOG, 'error') + mock.patch.object(self.mod_agent.LOG, 'error') ) as (add_tunnel_port_fn, log_error_fn): self.agent.dont_fragment = False ofport = self.agent._setup_tunnel_port( @@ -1045,18 +952,18 @@ class TestOvsNeutronAgent(base.BaseTestCase): with contextlib.nested( mock.patch.object(async_process.AsyncProcess, "_spawn"), mock.patch.object(log.KeywordArgumentAdapter, 'exception'), - mock.patch.object(ovs_neutron_agent.OVSNeutronAgent, + mock.patch.object(self.mod_agent.OVSNeutronAgent, 'scan_ports'), - mock.patch.object(ovs_neutron_agent.OVSNeutronAgent, + mock.patch.object(self.mod_agent.OVSNeutronAgent, 'process_network_ports'), - mock.patch.object(ovs_neutron_agent.OVSNeutronAgent, + mock.patch.object(self.mod_agent.OVSNeutronAgent, 'check_ovs_status'), - mock.patch.object(ovs_neutron_agent.OVSNeutronAgent, + mock.patch.object(self.mod_agent.OVSNeutronAgent, 'setup_integration_br'), - mock.patch.object(ovs_neutron_agent.OVSNeutronAgent, + mock.patch.object(self.mod_agent.OVSNeutronAgent, 'setup_physical_bridges'), mock.patch.object(time, 'sleep'), - mock.patch.object(ovs_neutron_agent.OVSNeutronAgent, + mock.patch.object(self.mod_agent.OVSNeutronAgent, 'update_stale_ofport_rules') ) as (spawn_fn, log_exception, scan_ports, process_network_ports, check_ovs_status, setup_int_br, setup_phys_br, time_sleep, @@ -1113,7 +1020,7 @@ class TestOvsNeutronAgent(base.BaseTestCase): # all of this is required just to get to the part of # treat_devices_added_or_updated that checks the prevent_arp_spoofing # flag - self.agent.int_br = mock.Mock() + self.agent.int_br = mock.create_autospec(self.agent.int_br) self.agent.treat_vif_port = mock.Mock() self.agent.get_vif_port_by_id = mock.Mock(return_value=FakeVif()) self.agent.plugin_rpc = mock.Mock() @@ -1127,27 +1034,24 @@ class TestOvsNeutronAgent(base.BaseTestCase): self.assertFalse(self.agent.setup_arp_spoofing_protection.called) def test_arp_spoofing_port_security_disabled(self): - int_br = mock.Mock() + int_br = mock.create_autospec(self.agent.int_br) self.agent.setup_arp_spoofing_protection( int_br, FakeVif(), {'port_security_enabled': False}) - self.assertFalse(int_br.add_flows.called) + self.assertTrue(int_br.delete_arp_spoofing_protection.called) + self.assertFalse(int_br.install_arp_spoofing_protection.called) def test_arp_spoofing_basic_rule_setup(self): vif = FakeVif() fake_details = {'fixed_ips': []} self.agent.prevent_arp_spoofing = True - int_br = mock.Mock() + int_br = mock.create_autospec(self.agent.int_br) self.agent.setup_arp_spoofing_protection(int_br, vif, fake_details) - int_br.delete_flows.assert_has_calls( - [mock.call(table=mock.ANY, in_port=vif.ofport)]) - # make sure redirect into spoof table is installed - int_br.add_flow.assert_any_call( - table=constants.LOCAL_SWITCHING, in_port=vif.ofport, - proto='arp', actions=mock.ANY, priority=10) - # make sure drop rule for replies is installed - int_br.add_flow.assert_any_call( - table=constants.ARP_SPOOF_TABLE, - proto='arp', actions='DROP', priority=mock.ANY) + self.assertEqual( + [mock.call(port=vif.ofport)], + int_br.delete_arp_spoofing_protection.mock_calls) + self.assertEqual( + [mock.call(ip_addresses=set(), port=vif.ofport)], + int_br.install_arp_spoofing_protection.mock_calls) def test_arp_spoofing_fixed_and_allowed_addresses(self): vif = FakeVif() @@ -1158,14 +1062,14 @@ class TestOvsNeutronAgent(base.BaseTestCase): {'ip_address': '192.168.44.103/32'}] } self.agent.prevent_arp_spoofing = True - int_br = mock.Mock() + int_br = mock.create_autospec(self.agent.int_br) self.agent.setup_arp_spoofing_protection(int_br, vif, fake_details) # make sure all addresses are allowed - for addr in ('192.168.44.100', '192.168.44.101', '192.168.44.102/32', - '192.168.44.103/32'): - int_br.add_flow.assert_any_call( - table=constants.ARP_SPOOF_TABLE, in_port=vif.ofport, - proto='arp', actions='NORMAL', arp_spa=addr, priority=mock.ANY) + addresses = {'192.168.44.100', '192.168.44.101', '192.168.44.102/32', + '192.168.44.103/32'} + self.assertEqual( + [mock.call(port=vif.ofport, ip_addresses=addresses)], + int_br.install_arp_spoofing_protection.mock_calls) def test__get_ofport_moves(self): previous = {'port1': 1, 'port2': 2} @@ -1184,8 +1088,9 @@ class TestOvsNeutronAgent(base.BaseTestCase): self.agent.int_br.get_vif_port_to_ofport_map.return_value = newmap self.agent.update_stale_ofport_rules() # rules matching port 1 should have been deleted - self.assertEqual(self.agent.int_br.delete_flows.mock_calls, - [mock.call(in_port=1)]) + self.assertEqual( + [mock.call(port=1)], + self.agent.int_br.delete_arp_spoofing_protection.mock_calls) # make sure the state was updated with the new map self.assertEqual(self.agent.vifname_to_ofport_map, newmap) @@ -1210,22 +1115,24 @@ class TestOvsNeutronAgent(base.BaseTestCase): def add_new_vlan_mapping(*args, **kwargs): self.agent.local_vlan_map['bar'] = ( - ovs_neutron_agent.LocalVLANMapping(1, 2, 3, 4)) + self.mod_agent.LocalVLANMapping(1, 2, 3, 4)) bridge = mock.Mock() tunnel_type = 'vxlan' self.agent.tun_br_ofports = {tunnel_type: dict()} self.agent.l2_pop = False self.agent.local_vlan_map = { - 'foo': ovs_neutron_agent.LocalVLANMapping(4, tunnel_type, 2, 1)} - bridge.mod_flow.side_effect = add_new_vlan_mapping - with mock.patch('neutron.plugins.openvswitch.agent.ovs_neutron_agent.' - '_ofport_set_to_str', return_value=True): - self.agent._setup_tunnel_port(bridge, 1, 2, - tunnel_type=tunnel_type) + 'foo': self.mod_agent.LocalVLANMapping(4, tunnel_type, 2, 1)} + bridge.install_flood_to_tun.side_effect = add_new_vlan_mapping + self.agent._setup_tunnel_port(bridge, 1, 2, tunnel_type=tunnel_type) self.assertIn('bar', self.agent.local_vlan_map) -class AncillaryBridgesTest(base.BaseTestCase): +class TestOvsNeutronAgentOFCtl(TestOvsNeutronAgent, + ovs_test_base.OVSOFCtlTestBase): + pass + + +class AncillaryBridgesTest(object): def setUp(self): super(AncillaryBridgesTest, self).setUp() @@ -1237,7 +1144,7 @@ class AncillaryBridgesTest(base.BaseTestCase): 'neutron.agent.firewall.NoopFirewallDriver', group='SECURITYGROUP') cfg.CONF.set_override('report_interval', 0, 'AGENT') - self.kwargs = ovs_neutron_agent.create_agent_config_map(cfg.CONF) + self.kwargs = self.mod_agent.create_agent_config_map(cfg.CONF) def _test_ancillary_bridges(self, bridges, ancillary): device_ids = ancillary[:] @@ -1252,15 +1159,10 @@ class AncillaryBridgesTest(base.BaseTestCase): return None with contextlib.nested( - mock.patch('neutron.plugins.openvswitch.agent.ovs_neutron_agent.' - 'OVSNeutronAgent.setup_integration_br'), + mock.patch.object(self.mod_agent.OVSNeutronAgent, + 'setup_integration_br'), mock.patch('neutron.agent.linux.utils.get_interface_mac', return_value='00:00:00:00:00:01'), - mock.patch('neutron.agent.common.ovs_lib.OVSBridge.' - 'get_local_port_mac', - return_value='00:00:00:00:00:01'), - mock.patch('neutron.agent.common.ovs_lib.OVSBridge.' - 'set_secure_mode'), mock.patch('neutron.agent.common.ovs_lib.BaseOVS.get_bridges', return_value=bridges), mock.patch('neutron.agent.common.ovs_lib.BaseOVS.' @@ -1268,7 +1170,8 @@ class AncillaryBridgesTest(base.BaseTestCase): side_effect=pullup_side_effect), mock.patch('neutron.agent.common.ovs_lib.OVSBridge.' 'get_vif_ports', return_value=[])): - self.agent = ovs_neutron_agent.OVSNeutronAgent(**self.kwargs) + self.agent = self.mod_agent.OVSNeutronAgent(self._bridge_classes(), + **self.kwargs) self.assertEqual(len(ancillary), len(self.agent.ancillary_brs)) if ancillary: bridges = [br.br_name for br in self.agent.ancillary_brs] @@ -1288,7 +1191,12 @@ class AncillaryBridgesTest(base.BaseTestCase): self._test_ancillary_bridges(bridges, ['br-ex1', 'br-ex2']) -class TestOvsDvrNeutronAgent(base.BaseTestCase): +class AncillaryBridgesTestOFCtl(AncillaryBridgesTest, + ovs_test_base.OVSOFCtlTestBase): + pass + + +class TestOvsDvrNeutronAgent(object): def setUp(self): super(TestOvsDvrNeutronAgent, self).setUp() @@ -1299,7 +1207,7 @@ class TestOvsDvrNeutronAgent(base.BaseTestCase): cfg.CONF.set_default('firewall_driver', 'neutron.agent.firewall.NoopFirewallDriver', group='SECURITYGROUP') - kwargs = ovs_neutron_agent.create_agent_config_map(cfg.CONF) + kwargs = self.mod_agent.create_agent_config_map(cfg.CONF) class MockFixedIntervalLoopingCall(object): def __init__(self, f): @@ -1309,18 +1217,11 @@ class TestOvsDvrNeutronAgent(base.BaseTestCase): self.f() with contextlib.nested( - mock.patch('neutron.plugins.openvswitch.agent.ovs_neutron_agent.' - 'OVSNeutronAgent.setup_integration_br'), - mock.patch('neutron.plugins.openvswitch.agent.ovs_neutron_agent.' - 'OVSNeutronAgent.setup_ancillary_bridges', - return_value=[]), - mock.patch('neutron.agent.common.ovs_lib.OVSBridge.' - 'create'), - mock.patch('neutron.agent.common.ovs_lib.OVSBridge.' - 'set_secure_mode'), - mock.patch('neutron.agent.common.ovs_lib.OVSBridge.' - 'get_local_port_mac', - return_value='00:00:00:00:00:01'), + mock.patch.object(self.mod_agent.OVSNeutronAgent, + 'setup_integration_br'), + mock.patch.object(self.mod_agent.OVSNeutronAgent, + 'setup_ancillary_bridges', + return_value=[]), mock.patch('neutron.agent.linux.utils.get_interface_mac', return_value='00:00:00:00:00:01'), mock.patch('neutron.agent.common.ovs_lib.BaseOVS.get_bridges'), @@ -1329,11 +1230,12 @@ class TestOvsDvrNeutronAgent(base.BaseTestCase): new=MockFixedIntervalLoopingCall), mock.patch('neutron.agent.common.ovs_lib.OVSBridge.' 'get_vif_ports', return_value=[])): - self.agent = ovs_neutron_agent.OVSNeutronAgent(**kwargs) + self.agent = self.mod_agent.OVSNeutronAgent(self._bridge_classes(), + **kwargs) # set back to true because initial report state will succeed due # to mocked out RPC calls self.agent.use_call = True - self.agent.tun_br = mock.Mock() + self.agent.tun_br = self.br_tun_cls(br_name='br-tun') self.agent.sg_agent = mock.Mock() def _setup_for_dvr_test(self, ofport=10): @@ -1371,6 +1273,36 @@ class TestOvsDvrNeutronAgent(base.BaseTestCase): self._compute_fixed_ips = [{'subnet_id': 'my-subnet-uuid', 'ip_address': '1.1.1.3'}] + @staticmethod + def _expected_port_bound(port, lvid): + return [ + mock.call.db_get_val('Port', port.port_name, 'other_config'), + mock.call.set_db_attribute('Port', port.port_name, 'other_config', + mock.ANY), + ] + + def _expected_install_dvr_process(self, lvid, port, ip_version, + gateway_ip, gateway_mac): + if ip_version == 4: + ipvx_calls = [ + mock.call.install_dvr_process_ipv4( + vlan_tag=lvid, + gateway_ip=gateway_ip), + ] + else: + ipvx_calls = [ + mock.call.install_dvr_process_ipv6( + vlan_tag=lvid, + gateway_mac=gateway_mac), + ] + return ipvx_calls + [ + mock.call.install_dvr_process( + vlan_tag=lvid, + dvr_mac_address=self.agent.dvr_agent.dvr_mac_address, + vif_mac=port.vif_mac, + ), + ] + def _test_port_bound_for_dvr_on_vlan_network(self, device_owner, ip_version=4): self._setup_for_dvr_test() @@ -1385,105 +1317,84 @@ class TestOvsDvrNeutronAgent(base.BaseTestCase): physical_network = self._physical_network segmentation_id = self._segmentation_id network_type = p_const.TYPE_VLAN + int_br = mock.create_autospec(self.agent.int_br) + tun_br = mock.create_autospec(self.agent.tun_br) + phys_br = mock.create_autospec(self.br_phys_cls('br-phys')) + int_br.set_db_attribute.return_value = True + int_br.db_get_val.return_value = {} with contextlib.nested( - mock.patch('neutron.agent.common.ovs_lib.OVSBridge.' - 'set_db_attribute', - return_value=True), - mock.patch('neutron.agent.common.ovs_lib.OVSBridge.' - 'db_get_val', - return_value={})): - with contextlib.nested( - mock.patch.object(self.agent.dvr_agent.plugin_rpc, - 'get_subnet_for_dvr', - return_value={ - 'gateway_ip': gateway_ip, - 'cidr': cidr, - 'ip_version': ip_version, - 'gateway_mac': gateway_mac}), - mock.patch.object(self.agent.dvr_agent.plugin_rpc, - 'get_ports_on_host_by_subnet', - return_value=[]), - mock.patch.object(self.agent.dvr_agent.int_br, - 'get_vif_port_by_id', - return_value=self._port), - mock.patch.object(self.agent.dvr_agent.int_br, 'add_flow'), - mock.patch.object(self.agent.dvr_agent.tun_br, 'add_flow'), - mock.patch.object(self.agent.dvr_agent.tun_br, 'delete_flows'), - mock.patch.object( - self.agent.dvr_agent.phys_brs[physical_network], - 'add_flow'), - mock.patch.object( - self.agent.dvr_agent.phys_brs[physical_network], - 'delete_flows') - ) as (get_subnet_fn, get_cphost_fn, - get_vif_fn, add_flow_int_fn, - add_flow_tun_fn, delete_flows_tun_fn, add_flow_phys_fn, - delete_flows_phys_fn): - self.agent.port_bound( - self._port, self._net_uuid, network_type, - physical_network, segmentation_id, self._fixed_ips, - n_const.DEVICE_OWNER_DVR_INTERFACE, False) - lvm = self.agent.local_vlan_map[self._net_uuid] - phy_ofp = self.agent.dvr_agent.phys_ofports[physical_network] - int_ofp = self.agent.dvr_agent.int_ofports[physical_network] - expected_on_phys_br = [ - mock.call(table=constants.LOCAL_VLAN_TRANSLATION, - priority=4, - in_port=phy_ofp, - dl_vlan=lvm.vlan, - actions="mod_vlan_vid:%s,normal" % - (lvm.segmentation_id)), - mock.call(table=constants.DVR_PROCESS_VLAN, - priority=2, - dl_vlan=lvm.vlan, - dl_dst=self._port.vif_mac, - actions="drop"), - mock.call(table=constants.DVR_PROCESS_VLAN, - priority=1, - dl_vlan=lvm.vlan, - dl_src=self._port.vif_mac, - actions="mod_dl_src:%s,resubmit(,%s)" % - (self.agent.dvr_agent.dvr_mac_address, - constants.LOCAL_VLAN_TRANSLATION)) - ] - if ip_version == 4: - expected_on_phys_br.insert(1, mock.call( - proto='arp', - nw_dst=gateway_ip, actions='drop', - priority=3, table=constants.DVR_PROCESS_VLAN, - dl_vlan=lvm.vlan)) - else: - expected_on_phys_br.insert(1, mock.call( - icmp_type=n_const.ICMPV6_TYPE_RA, proto='icmp6', - dl_src=self._port.vif_mac, actions='drop', - priority=3, table=constants.DVR_PROCESS_VLAN, - dl_vlan=lvm.vlan)) - self.assertEqual(expected_on_phys_br, - add_flow_phys_fn.call_args_list) - self.agent.port_bound(self._compute_port, self._net_uuid, - network_type, physical_network, - segmentation_id, - self._compute_fixed_ips, - device_owner, False) - expected_on_int_br = [ - mock.call(priority=3, - in_port=int_ofp, - dl_vlan=lvm.segmentation_id, - actions="mod_vlan_vid:%s,normal" % lvm.vlan), - mock.call(table=constants.DVR_TO_SRC_MAC_VLAN, - priority=4, - dl_dst=self._compute_port.vif_mac, - dl_vlan=lvm.segmentation_id, - actions="strip_vlan,mod_dl_src:%s," - "output:%s" % - (gateway_mac, - self._compute_port.ofport)) - ] - self.assertEqual(expected_on_int_br, - add_flow_int_fn.call_args_list) - self.assertFalse(add_flow_tun_fn.called) - self.assertFalse(delete_flows_tun_fn.called) - self.assertFalse(delete_flows_phys_fn.called) + mock.patch.object(self.agent.dvr_agent.plugin_rpc, + 'get_subnet_for_dvr', + return_value={ + 'gateway_ip': gateway_ip, + 'cidr': cidr, + 'ip_version': ip_version, + 'gateway_mac': gateway_mac}), + mock.patch.object(self.agent.dvr_agent.plugin_rpc, + 'get_ports_on_host_by_subnet', + return_value=[]), + mock.patch.object(self.agent.dvr_agent.int_br, + 'get_vif_port_by_id', + return_value=self._port), + mock.patch.object(self.agent, 'int_br', new=int_br), + mock.patch.object(self.agent, 'tun_br', new=tun_br), + mock.patch.dict(self.agent.phys_brs, + {physical_network: phys_br}), + mock.patch.object(self.agent.dvr_agent, 'int_br', new=int_br), + mock.patch.object(self.agent.dvr_agent, 'tun_br', new=tun_br), + mock.patch.dict(self.agent.dvr_agent.phys_brs, + {physical_network: phys_br}), + ) as (get_subnet_fn, get_cphost_fn, get_vif_fn, _, _, _, _, _, _): + self.agent.port_bound( + self._port, self._net_uuid, network_type, + physical_network, segmentation_id, self._fixed_ips, + n_const.DEVICE_OWNER_DVR_INTERFACE, False) + phy_ofp = self.agent.dvr_agent.phys_ofports[physical_network] + int_ofp = self.agent.dvr_agent.int_ofports[physical_network] + lvid = self.agent.local_vlan_map[self._net_uuid].vlan + expected_on_phys_br = [ + mock.call.provision_local_vlan( + port=phy_ofp, + lvid=lvid, + segmentation_id=segmentation_id, + distributed=True, + ), + ] + self._expected_install_dvr_process( + port=self._port, + lvid=lvid, + ip_version=ip_version, + gateway_ip=gateway_ip, + gateway_mac=gateway_mac) + expected_on_int_br = [ + mock.call.provision_local_vlan( + port=int_ofp, + lvid=lvid, + segmentation_id=segmentation_id, + ), + ] + self._expected_port_bound(self._port, lvid) + self.assertEqual(expected_on_int_br, int_br.mock_calls) + self.assertEqual([], tun_br.mock_calls) + self.assertEqual(expected_on_phys_br, phys_br.mock_calls) + int_br.reset_mock() + tun_br.reset_mock() + phys_br.reset_mock() + self.agent.port_bound(self._compute_port, self._net_uuid, + network_type, physical_network, + segmentation_id, + self._compute_fixed_ips, + device_owner, False) + expected_on_int_br = [ + mock.call.install_dvr_to_src_mac( + network_type=network_type, + gateway_mac=gateway_mac, + dst_mac=self._compute_port.vif_mac, + dst_port=self._compute_port.ofport, + vlan_tag=segmentation_id, + ), + ] + self._expected_port_bound(self._compute_port, lvid) + self.assertEqual(expected_on_int_br, int_br.mock_calls) + self.assertFalse([], tun_br.mock_calls) + self.assertFalse([], phys_br.mock_calls) def _test_port_bound_for_dvr_on_vxlan_network(self, device_owner, ip_version=4): @@ -1499,99 +1410,76 @@ class TestOvsDvrNeutronAgent(base.BaseTestCase): self._compute_port.vif_mac = '77:88:99:00:11:22' physical_network = self._physical_network segmentation_id = self._segmentation_id + int_br = mock.create_autospec(self.agent.int_br) + tun_br = mock.create_autospec(self.agent.tun_br) + phys_br = mock.create_autospec(self.br_phys_cls('br-phys')) + int_br.set_db_attribute.return_value = True + int_br.db_get_val.return_value = {} with contextlib.nested( - mock.patch('neutron.agent.common.ovs_lib.OVSBridge.' - 'set_db_attribute', - return_value=True), - mock.patch('neutron.agent.common.ovs_lib.OVSBridge.' - 'db_get_val', - return_value={})): - with contextlib.nested( - mock.patch.object(self.agent.dvr_agent.plugin_rpc, - 'get_subnet_for_dvr', - return_value={ - 'gateway_ip': gateway_ip, - 'cidr': cidr, - 'ip_version': ip_version, - 'gateway_mac': gateway_mac}), - mock.patch.object(self.agent.dvr_agent.plugin_rpc, - 'get_ports_on_host_by_subnet', - return_value=[]), - mock.patch.object(self.agent.dvr_agent.int_br, - 'get_vif_port_by_id', - return_value=self._port), - mock.patch.object(self.agent.dvr_agent.int_br, 'add_flow'), - mock.patch.object(self.agent.dvr_agent.tun_br, 'add_flow'), - mock.patch.object(self.agent.dvr_agent.tun_br, 'delete_flows'), - mock.patch.object( - self.agent.dvr_agent.phys_brs[physical_network], - 'add_flow'), - mock.patch.object( - self.agent.dvr_agent.phys_brs[physical_network], - 'delete_flows') - ) as (get_subnet_fn, get_cphost_fn, - get_vif_fn, add_flow_int_fn, - add_flow_tun_fn, delete_flows_tun_fn, - add_flow_phys_fn, delete_flows_phys_fn): - self.agent.port_bound( - self._port, self._net_uuid, network_type, - physical_network, segmentation_id, self._fixed_ips, - n_const.DEVICE_OWNER_DVR_INTERFACE, False) - lvm = self.agent.local_vlan_map[self._net_uuid] - expected_on_tun_br = [ - mock.call( - table=constants.TUN_TABLE['vxlan'], - priority=1, tun_id=lvm.segmentation_id, - actions="mod_vlan_vid:%s," - "resubmit(,%s)" % - (lvm.vlan, constants.DVR_NOT_LEARN)), - mock.call( - table=constants.DVR_PROCESS, priority=2, - dl_vlan=lvm.vlan, - dl_dst=self._port.vif_mac, - actions='drop'), - mock.call( - table=constants.DVR_PROCESS, priority=1, - dl_vlan=lvm.vlan, - dl_src=self._port.vif_mac, - actions="mod_dl_src:%s,resubmit(,%s)" % ( - self.agent.dvr_agent.dvr_mac_address, - constants.PATCH_LV_TO_TUN))] - if ip_version == 4: - expected_on_tun_br.insert(1, mock.call( - proto='arp', - nw_dst=gateway_ip, actions='drop', - priority=3, table=constants.DVR_PROCESS, - dl_vlan=lvm.vlan)) - else: - expected_on_tun_br.insert(1, mock.call( - icmp_type=n_const.ICMPV6_TYPE_RA, - proto='icmp6', - dl_src=self._port.vif_mac, - actions='drop', - priority=3, table=constants.DVR_PROCESS, - dl_vlan=lvm.vlan)) - self.assertEqual(expected_on_tun_br, - add_flow_tun_fn.call_args_list) - self.agent.port_bound(self._compute_port, self._net_uuid, - network_type, physical_network, - segmentation_id, - self._compute_fixed_ips, - device_owner, False) - expected_on_int_br = [ - mock.call(table=constants.DVR_TO_SRC_MAC, priority=4, - dl_dst=self._compute_port.vif_mac, - dl_vlan=lvm.vlan, - actions="strip_vlan,mod_dl_src:%s," - "output:%s" % - (gateway_mac, self._compute_port.ofport)) - ] - self.assertEqual(expected_on_int_br, - add_flow_int_fn.call_args_list) - self.assertFalse(add_flow_phys_fn.called) - self.assertFalse(add_flow_phys_fn.called) - self.assertFalse(delete_flows_tun_fn.called) - self.assertFalse(delete_flows_phys_fn.called) + mock.patch.object(self.agent.dvr_agent.plugin_rpc, + 'get_subnet_for_dvr', + return_value={ + 'gateway_ip': gateway_ip, + 'cidr': cidr, + 'ip_version': ip_version, + 'gateway_mac': gateway_mac}), + mock.patch.object(self.agent.dvr_agent.plugin_rpc, + 'get_ports_on_host_by_subnet', + return_value=[]), + mock.patch.object(self.agent.dvr_agent.int_br, + 'get_vif_port_by_id', + return_value=self._port), + mock.patch.object(self.agent, 'int_br', new=int_br), + mock.patch.object(self.agent, 'tun_br', new=tun_br), + mock.patch.dict(self.agent.phys_brs, + {physical_network: phys_br}), + mock.patch.object(self.agent.dvr_agent, 'int_br', new=int_br), + mock.patch.object(self.agent.dvr_agent, 'tun_br', new=tun_br), + mock.patch.dict(self.agent.dvr_agent.phys_brs, + {physical_network: phys_br}), + ) as (get_subnet_fn, get_cphost_fn, get_vif_fn, _, _, _, _, _, _): + self.agent.port_bound( + self._port, self._net_uuid, network_type, + physical_network, segmentation_id, self._fixed_ips, + n_const.DEVICE_OWNER_DVR_INTERFACE, False) + lvid = self.agent.local_vlan_map[self._net_uuid].vlan + expected_on_int_br = self._expected_port_bound( + self._port, lvid) + expected_on_tun_br = [ + mock.call.provision_local_vlan( + network_type=network_type, + segmentation_id=segmentation_id, + lvid=lvid, + distributed=True), + ] + self._expected_install_dvr_process( + port=self._port, + lvid=lvid, + ip_version=ip_version, + gateway_ip=gateway_ip, + gateway_mac=gateway_mac) + self.assertEqual(expected_on_int_br, int_br.mock_calls) + self.assertEqual(expected_on_tun_br, tun_br.mock_calls) + self.assertEqual([], phys_br.mock_calls) + int_br.reset_mock() + tun_br.reset_mock() + phys_br.reset_mock() + self.agent.port_bound(self._compute_port, self._net_uuid, + network_type, physical_network, + segmentation_id, + self._compute_fixed_ips, + device_owner, False) + expected_on_int_br = [ + mock.call.install_dvr_to_src_mac( + network_type=network_type, + gateway_mac=gateway_mac, + dst_mac=self._compute_port.vif_mac, + dst_port=self._compute_port.ofport, + vlan_tag=lvid, + ), + ] + self._expected_port_bound(self._compute_port, lvid) + self.assertEqual(expected_on_int_br, int_br.mock_calls) + self.assertEqual([], tun_br.mock_calls) + self.assertEqual([], phys_br.mock_calls) def test_port_bound_for_dvr_with_compute_ports(self): self._test_port_bound_for_dvr_on_vlan_network( @@ -1625,38 +1513,53 @@ class TestOvsDvrNeutronAgent(base.BaseTestCase): def test_port_bound_for_dvr_with_csnat_ports(self, ofport=10): self._setup_for_dvr_test() + int_br = mock.create_autospec(self.agent.int_br) + tun_br = mock.create_autospec(self.agent.tun_br) + int_br.set_db_attribute.return_value = True + int_br.db_get_val.return_value = {} with contextlib.nested( - mock.patch('neutron.agent.common.ovs_lib.OVSBridge.' - 'set_db_attribute', - return_value=True), - mock.patch('neutron.agent.common.ovs_lib.OVSBridge.' - 'db_get_val', - return_value={})): - with contextlib.nested( - mock.patch.object( - self.agent.dvr_agent.plugin_rpc, 'get_subnet_for_dvr', - return_value={'gateway_ip': '1.1.1.1', - 'cidr': '1.1.1.0/24', - 'ip_version': 4, - 'gateway_mac': 'aa:bb:cc:11:22:33'}), - mock.patch.object(self.agent.dvr_agent.plugin_rpc, - 'get_ports_on_host_by_subnet', - return_value=[]), - mock.patch.object(self.agent.dvr_agent.int_br, - 'get_vif_port_by_id', - return_value=self._port), - mock.patch.object(self.agent.dvr_agent.int_br, 'add_flow'), - mock.patch.object(self.agent.dvr_agent.tun_br, 'add_flow'), - mock.patch.object(self.agent.dvr_agent.tun_br, 'delete_flows') - ) as (get_subnet_fn, get_cphost_fn, - get_vif_fn, add_flow_int_fn, - add_flow_tun_fn, delete_flows_tun_fn): - self.agent.port_bound( - self._port, self._net_uuid, 'vxlan', - None, None, self._fixed_ips, - n_const.DEVICE_OWNER_ROUTER_SNAT, - False) - self.assertTrue(add_flow_int_fn.called) + mock.patch.object( + self.agent.dvr_agent.plugin_rpc, 'get_subnet_for_dvr', + return_value={'gateway_ip': '1.1.1.1', + 'cidr': '1.1.1.0/24', + 'ip_version': 4, + 'gateway_mac': 'aa:bb:cc:11:22:33'}), + mock.patch.object(self.agent.dvr_agent.plugin_rpc, + 'get_ports_on_host_by_subnet', + return_value=[]), + mock.patch.object(self.agent.dvr_agent.int_br, + 'get_vif_port_by_id', + return_value=self._port), + mock.patch.object(self.agent, 'int_br', new=int_br), + mock.patch.object(self.agent, 'tun_br', new=tun_br), + mock.patch.object(self.agent.dvr_agent, 'int_br', new=int_br), + mock.patch.object(self.agent.dvr_agent, 'tun_br', new=tun_br), + ) as (get_subnet_fn, get_cphost_fn, get_vif_fn, _, _, _, _): + self.agent.port_bound( + self._port, self._net_uuid, 'vxlan', + None, None, self._fixed_ips, + n_const.DEVICE_OWNER_ROUTER_SNAT, + False) + lvid = self.agent.local_vlan_map[self._net_uuid].vlan + expected_on_int_br = [ + mock.call.install_dvr_to_src_mac( + network_type='vxlan', + gateway_mac='aa:bb:cc:11:22:33', + dst_mac=self._port.vif_mac, + dst_port=self._port.ofport, + vlan_tag=lvid, + ), + ] + self._expected_port_bound(self._port, lvid) + self.assertEqual(expected_on_int_br, int_br.mock_calls) + expected_on_tun_br = [ + mock.call.provision_local_vlan( + network_type='vxlan', + lvid=lvid, + segmentation_id=None, + distributed=True, + ), + ] + self.assertEqual(expected_on_tun_br, tun_br.mock_calls) def test_treat_devices_removed_for_dvr_interface(self, ofport=10): self._test_treat_devices_removed_for_dvr_interface(ofport) @@ -1672,77 +1575,79 @@ class TestOvsDvrNeutronAgent(base.BaseTestCase): else: gateway_ip = '2001:100::1' cidr = '2001:100::0/64' + gateway_mac = 'aa:bb:cc:11:22:33' + int_br = mock.create_autospec(self.agent.int_br) + tun_br = mock.create_autospec(self.agent.tun_br) + int_br.set_db_attribute.return_value = True + int_br.db_get_val.return_value = {} with contextlib.nested( - mock.patch('neutron.agent.common.ovs_lib.OVSBridge.' - 'set_db_attribute', - return_value=True), - mock.patch('neutron.agent.common.ovs_lib.OVSBridge.' - 'db_get_val', - return_value={})): - with contextlib.nested( - mock.patch.object( - self.agent.dvr_agent.plugin_rpc, 'get_subnet_for_dvr', - return_value={'gateway_ip': gateway_ip, - 'cidr': cidr, - 'ip_version': ip_version, - 'gateway_mac': 'aa:bb:cc:11:22:33'}), - mock.patch.object(self.agent.dvr_agent.plugin_rpc, - 'get_ports_on_host_by_subnet', - return_value=[]), - mock.patch.object(self.agent.dvr_agent.int_br, - 'get_vif_port_by_id', - return_value=self._port), - mock.patch.object(self.agent.dvr_agent.int_br, 'add_flow'), - mock.patch.object(self.agent.dvr_agent.tun_br, 'add_flow'), - mock.patch.object(self.agent.dvr_agent.tun_br, 'delete_flows') - ) as (get_subnet_fn, get_cphost_fn, - get_vif_fn, add_flow_int_fn, - add_flow_tun_fn, delete_flows_tun_fn): - self.agent.port_bound( - self._port, self._net_uuid, 'vxlan', - None, None, self._fixed_ips, - n_const.DEVICE_OWNER_DVR_INTERFACE, - False) - self.assertTrue(add_flow_tun_fn.called) - + mock.patch.object( + self.agent.dvr_agent.plugin_rpc, 'get_subnet_for_dvr', + return_value={'gateway_ip': gateway_ip, + 'cidr': cidr, + 'ip_version': ip_version, + 'gateway_mac': gateway_mac}), + mock.patch.object(self.agent.dvr_agent.plugin_rpc, + 'get_ports_on_host_by_subnet', + return_value=[]), + mock.patch.object(self.agent, 'int_br', new=int_br), + mock.patch.object(self.agent, 'tun_br', new=tun_br), + mock.patch.object(self.agent.dvr_agent, 'int_br', new=int_br), + mock.patch.object(self.agent.dvr_agent, 'tun_br', new=tun_br), + mock.patch.object(self.agent.dvr_agent.int_br, + 'get_vif_port_by_id', + return_value=self._port), + ) as (get_subnet_fn, get_cphost_fn, _, _, _, _, get_vif_fn): + self.agent.port_bound( + self._port, self._net_uuid, 'vxlan', + None, None, self._fixed_ips, + n_const.DEVICE_OWNER_DVR_INTERFACE, + False) + lvid = self.agent.local_vlan_map[self._net_uuid].vlan + self.assertEqual(self._expected_port_bound(self._port, lvid), + int_br.mock_calls) + expected_on_tun_br = [ + mock.call.provision_local_vlan(network_type='vxlan', + lvid=lvid, segmentation_id=None, distributed=True), + ] + self._expected_install_dvr_process( + port=self._port, + lvid=lvid, + ip_version=ip_version, + gateway_ip=gateway_ip, + gateway_mac=gateway_mac) + self.assertEqual(expected_on_tun_br, tun_br.mock_calls) + + int_br.reset_mock() + tun_br.reset_mock() with contextlib.nested( mock.patch.object(self.agent, 'reclaim_local_vlan'), mock.patch.object(self.agent.plugin_rpc, 'update_device_down', return_value=None), - mock.patch.object(self.agent.dvr_agent.int_br, 'delete_flows'), - mock.patch.object(self.agent.dvr_agent.tun_br, - 'delete_flows')) as (reclaim_vlan_fn, - update_dev_down_fn, - delete_flows_int_fn, - delete_flows_tun_fn): + mock.patch.object(self.agent, 'int_br', new=int_br), + mock.patch.object(self.agent, 'tun_br', new=tun_br), + mock.patch.object(self.agent.dvr_agent, 'int_br', new=int_br), + mock.patch.object(self.agent.dvr_agent, 'tun_br', new=tun_br), + ) as (reclaim_vlan_fn, update_dev_down_fn, _, _, _, _): self.agent.treat_devices_removed([self._port.vif_id]) if ip_version == 4: - expected = [mock.call( - proto='arp', - nw_dst=gateway_ip, - table=constants.DVR_PROCESS, - dl_vlan=( - self.agent.local_vlan_map[self._net_uuid].vlan))] + expected = [ + mock.call.delete_dvr_process_ipv4( + vlan_tag=lvid, + gateway_ip=gateway_ip), + ] else: - expected = [mock.call( - icmp_type=n_const.ICMPV6_TYPE_RA, proto='icmp6', - dl_src='aa:bb:cc:11:22:33', - table=constants.DVR_PROCESS, - dl_vlan=( - self.agent.local_vlan_map[self._net_uuid].vlan))] + expected = [ + mock.call.delete_dvr_process_ipv6( + vlan_tag=lvid, + gateway_mac=gateway_mac), + ] expected.extend([ - mock.call( - table=constants.DVR_PROCESS, - dl_dst=self._port.vif_mac, - dl_vlan=( - self.agent.local_vlan_map[self._net_uuid].vlan)), - mock.call( - table=constants.DVR_PROCESS, - dl_vlan=( - self.agent.local_vlan_map[self._net_uuid].vlan), - dl_src=self._port.vif_mac) + mock.call.delete_dvr_process( + vlan_tag=lvid, + vif_mac=self._port.vif_mac), ]) - self.assertEqual(expected, delete_flows_tun_fn.call_args_list) + self.assertEqual([], int_br.mock_calls) + self.assertEqual(expected, tun_br.mock_calls) def _test_treat_devices_removed_for_dvr(self, device_owner, ip_version=4): self._setup_for_dvr_test() @@ -1752,61 +1657,91 @@ class TestOvsDvrNeutronAgent(base.BaseTestCase): else: gateway_ip = '2001:100::1' cidr = '2001:100::0/64' + gateway_mac = 'aa:bb:cc:11:22:33' + int_br = mock.create_autospec(self.agent.int_br) + tun_br = mock.create_autospec(self.agent.tun_br) + int_br.set_db_attribute.return_value = True + int_br.db_get_val.return_value = {} with contextlib.nested( - mock.patch('neutron.agent.common.ovs_lib.OVSBridge.' - 'set_db_attribute', - return_value=True), - mock.patch('neutron.agent.common.ovs_lib.OVSBridge.' - 'db_get_val', - return_value={})): - with contextlib.nested( - mock.patch.object( - self.agent.dvr_agent.plugin_rpc, 'get_subnet_for_dvr', - return_value={'gateway_ip': gateway_ip, - 'cidr': cidr, - 'ip_version': ip_version, - 'gateway_mac': 'aa:bb:cc:11:22:33'}), - mock.patch.object(self.agent.dvr_agent.plugin_rpc, - 'get_ports_on_host_by_subnet', - return_value=[]), - mock.patch.object(self.agent.dvr_agent.int_br, - 'get_vif_port_by_id', - return_value=self._port), - mock.patch.object(self.agent.dvr_agent.int_br, 'add_flow'), - mock.patch.object(self.agent.dvr_agent.tun_br, 'add_flow'), - mock.patch.object(self.agent.dvr_agent.tun_br, 'delete_flows') - ) as (get_subnet_fn, get_cphost_fn, - get_vif_fn, add_flow_int_fn, - add_flow_tun_fn, delete_flows_tun_fn): - self.agent.port_bound( - self._port, self._net_uuid, 'vxlan', - None, None, self._fixed_ips, - n_const.DEVICE_OWNER_DVR_INTERFACE, - False) - self.agent.port_bound(self._compute_port, - self._net_uuid, 'vxlan', - None, None, - self._compute_fixed_ips, - device_owner, False) - self.assertTrue(add_flow_tun_fn.called) - self.assertTrue(add_flow_int_fn.called) - + mock.patch.object( + self.agent.dvr_agent.plugin_rpc, 'get_subnet_for_dvr', + return_value={'gateway_ip': gateway_ip, + 'cidr': cidr, + 'ip_version': ip_version, + 'gateway_mac': gateway_mac}), + mock.patch.object(self.agent.dvr_agent.plugin_rpc, + 'get_ports_on_host_by_subnet', + return_value=[]), + mock.patch.object(self.agent.dvr_agent.int_br, + 'get_vif_port_by_id', + return_value=self._port), + mock.patch.object(self.agent, 'int_br', new=int_br), + mock.patch.object(self.agent, 'tun_br', new=tun_br), + mock.patch.object(self.agent.dvr_agent, 'int_br', new=int_br), + mock.patch.object(self.agent.dvr_agent, 'tun_br', new=tun_br), + ) as (get_subnet_fn, get_cphost_fn, get_vif_fn, _, _, _, _): + self.agent.port_bound( + self._port, self._net_uuid, 'vxlan', + None, None, self._fixed_ips, + n_const.DEVICE_OWNER_DVR_INTERFACE, + False) + lvid = self.agent.local_vlan_map[self._net_uuid].vlan + self.assertEqual( + self._expected_port_bound(self._port, lvid), + int_br.mock_calls) + expected_on_tun_br = [ + mock.call.provision_local_vlan( + network_type='vxlan', + segmentation_id=None, + lvid=lvid, + distributed=True), + ] + self._expected_install_dvr_process( + port=self._port, + lvid=lvid, + ip_version=ip_version, + gateway_ip=gateway_ip, + gateway_mac=gateway_mac) + self.assertEqual(expected_on_tun_br, tun_br.mock_calls) + int_br.reset_mock() + tun_br.reset_mock() + self.agent.port_bound(self._compute_port, + self._net_uuid, 'vxlan', + None, None, + self._compute_fixed_ips, + device_owner, False) + self.assertEqual( + [ + mock.call.install_dvr_to_src_mac( + network_type='vxlan', + gateway_mac='aa:bb:cc:11:22:33', + dst_mac=self._compute_port.vif_mac, + dst_port=self._compute_port.ofport, + vlan_tag=lvid, + ), + ] + self._expected_port_bound(self._compute_port, lvid), + int_br.mock_calls) + self.assertEqual([], tun_br.mock_calls) + + int_br.reset_mock() + tun_br.reset_mock() with contextlib.nested( mock.patch.object(self.agent, 'reclaim_local_vlan'), mock.patch.object(self.agent.plugin_rpc, 'update_device_down', return_value=None), - mock.patch.object(self.agent.dvr_agent.int_br, - 'delete_flows')) as (reclaim_vlan_fn, - update_dev_down_fn, - delete_flows_int_fn): + mock.patch.object(self.agent, 'int_br', new=int_br), + mock.patch.object(self.agent, 'tun_br', new=tun_br), + mock.patch.object(self.agent.dvr_agent, 'int_br', new=int_br), + mock.patch.object(self.agent.dvr_agent, 'tun_br', new=tun_br), + ) as (reclaim_vlan_fn, update_dev_down_fn, _, _, _, _): self.agent.treat_devices_removed([self._compute_port.vif_id]) - expected = [ - mock.call( - table=constants.DVR_TO_SRC_MAC, - dl_dst=self._compute_port.vif_mac, - dl_vlan=( - self.agent.local_vlan_map[self._net_uuid].vlan))] - self.assertEqual(expected, delete_flows_int_fn.call_args_list) + int_br.assert_has_calls([ + mock.call.delete_dvr_to_src_mac( + network_type='vxlan', + vlan_tag=lvid, + dst_mac=self._compute_port.vif_mac, + ), + ]) + self.assertEqual([], tun_br.mock_calls) def test_treat_devices_removed_for_dvr_with_compute_ports(self): self._test_treat_devices_removed_for_dvr( @@ -1828,90 +1763,115 @@ class TestOvsDvrNeutronAgent(base.BaseTestCase): def test_treat_devices_removed_for_dvr_csnat_port(self, ofport=10): self._setup_for_dvr_test() + gateway_mac = 'aa:bb:cc:11:22:33' + int_br = mock.create_autospec(self.agent.int_br) + tun_br = mock.create_autospec(self.agent.tun_br) + int_br.set_db_attribute.return_value = True + int_br.db_get_val.return_value = {} with contextlib.nested( - mock.patch('neutron.agent.common.ovs_lib.OVSBridge.' - 'set_db_attribute', - return_value=True), - mock.patch('neutron.agent.common.ovs_lib.OVSBridge.' - 'db_get_val', - return_value={})): - with contextlib.nested( - mock.patch.object( - self.agent.dvr_agent.plugin_rpc, 'get_subnet_for_dvr', - return_value={'gateway_ip': '1.1.1.1', - 'cidr': '1.1.1.0/24', - 'ip_version': 4, - 'gateway_mac': 'aa:bb:cc:11:22:33'}), - mock.patch.object(self.agent.dvr_agent.plugin_rpc, - 'get_ports_on_host_by_subnet', - return_value=[]), - mock.patch.object(self.agent.dvr_agent.int_br, - 'get_vif_port_by_id', - return_value=self._port), - mock.patch.object(self.agent.dvr_agent.int_br, 'add_flow'), - mock.patch.object(self.agent.dvr_agent.tun_br, 'add_flow'), - mock.patch.object(self.agent.dvr_agent.tun_br, 'delete_flows') - ) as (get_subnet_fn, get_cphost_fn, - get_vif_fn, add_flow_int_fn, - add_flow_tun_fn, delete_flows_tun_fn): - self.agent.port_bound( - self._port, self._net_uuid, 'vxlan', - None, None, self._fixed_ips, - n_const.DEVICE_OWNER_ROUTER_SNAT, - False) - self.assertTrue(add_flow_int_fn.called) + mock.patch.object( + self.agent.dvr_agent.plugin_rpc, 'get_subnet_for_dvr', + return_value={'gateway_ip': '1.1.1.1', + 'cidr': '1.1.1.0/24', + 'ip_version': 4, + 'gateway_mac': gateway_mac}), + mock.patch.object(self.agent.dvr_agent.plugin_rpc, + 'get_ports_on_host_by_subnet', + return_value=[]), + mock.patch.object(self.agent.dvr_agent.int_br, + 'get_vif_port_by_id', + return_value=self._port), + mock.patch.object(self.agent, 'int_br', new=int_br), + mock.patch.object(self.agent, 'tun_br', new=tun_br), + mock.patch.object(self.agent.dvr_agent, 'int_br', new=int_br), + mock.patch.object(self.agent.dvr_agent, 'tun_br', new=tun_br), + ) as (get_subnet_fn, get_cphost_fn, get_vif_fn, _, _, _, _): + self.agent.port_bound( + self._port, self._net_uuid, 'vxlan', + None, None, self._fixed_ips, + n_const.DEVICE_OWNER_ROUTER_SNAT, + False) + lvid = self.agent.local_vlan_map[self._net_uuid].vlan + expected_on_int_br = [ + mock.call.install_dvr_to_src_mac( + network_type='vxlan', + gateway_mac=gateway_mac, + dst_mac=self._port.vif_mac, + dst_port=self._port.ofport, + vlan_tag=lvid, + ), + ] + self._expected_port_bound(self._port, lvid) + self.assertEqual(expected_on_int_br, int_br.mock_calls) + expected_on_tun_br = [ + mock.call.provision_local_vlan( + network_type='vxlan', + lvid=lvid, + segmentation_id=None, + distributed=True, + ), + ] + self.assertEqual(expected_on_tun_br, tun_br.mock_calls) + int_br.reset_mock() + tun_br.reset_mock() with contextlib.nested( mock.patch.object(self.agent, 'reclaim_local_vlan'), mock.patch.object(self.agent.plugin_rpc, 'update_device_down', return_value=None), - mock.patch.object(self.agent.dvr_agent.int_br, - 'delete_flows')) as (reclaim_vlan_fn, - update_dev_down_fn, - delete_flows_int_fn): + mock.patch.object(self.agent, 'int_br', new=int_br), + mock.patch.object(self.agent, 'tun_br', new=tun_br), + mock.patch.object(self.agent.dvr_agent, 'int_br', new=int_br), + mock.patch.object(self.agent.dvr_agent, 'tun_br', new=tun_br), + ) as (reclaim_vlan_fn, update_dev_down_fn, _, _, _, _): self.agent.treat_devices_removed([self._port.vif_id]) - self.assertTrue(delete_flows_int_fn.called) + expected_on_int_br = [ + mock.call.delete_dvr_to_src_mac( + network_type='vxlan', + dst_mac=self._port.vif_mac, + vlan_tag=lvid, + ), + ] + self.assertEqual(expected_on_int_br, int_br.mock_calls) + expected_on_tun_br = [] + self.assertEqual(expected_on_tun_br, tun_br.mock_calls) def test_setup_dvr_flows_on_int_br(self): self._setup_for_dvr_test() + int_br = mock.create_autospec(self.agent.int_br) + tun_br = mock.create_autospec(self.agent.tun_br) with contextlib.nested( - mock.patch.object(self.agent.dvr_agent.int_br, - 'remove_all_flows'), - mock.patch.object(self.agent.dvr_agent.int_br, 'add_flow'), - mock.patch.object(self.agent.dvr_agent.tun_br, 'add_flow'), - mock.patch.object( - self.agent.dvr_agent.plugin_rpc, - 'get_dvr_mac_address_list', - return_value=[{'host': 'cn1', - 'mac_address': 'aa:bb:cc:dd:ee:ff'}, - {'host': 'cn2', - 'mac_address': '11:22:33:44:55:66'}])) as \ - (remove_flows_fn, add_int_flow_fn, add_tun_flow_fn, - get_mac_list_fn): + mock.patch.object(self.agent, 'int_br', new=int_br), + mock.patch.object(self.agent, 'tun_br', new=tun_br), + mock.patch.object(self.agent.dvr_agent, 'int_br', new=int_br), + mock.patch.object(self.agent.dvr_agent, 'tun_br', new=tun_br), + mock.patch.object( + self.agent.dvr_agent.plugin_rpc, + 'get_dvr_mac_address_list', + return_value=[{'host': 'cn1', + 'mac_address': 'aa:bb:cc:dd:ee:ff'}, + {'host': 'cn2', + 'mac_address': '11:22:33:44:55:66'}]) + ) as (_, _, _, _, get_mac_list_fn): self.agent.dvr_agent.setup_dvr_flows_on_integ_br() self.assertTrue(self.agent.dvr_agent.in_distributed_mode()) physical_networks = self.agent.dvr_agent.bridge_mappings.keys() ioport = self.agent.dvr_agent.int_ofports[physical_networks[0]] - expected = [ - mock.call(table=constants.CANARY_TABLE, - priority=0, - actions="drop"), - mock.call(table=constants.DVR_TO_SRC_MAC, - priority=1, - actions="drop"), - mock.call(table=constants.DVR_TO_SRC_MAC_VLAN, - priority=1, - actions="drop"), - mock.call(table=constants.LOCAL_SWITCHING, - priority=1, - actions="normal"), - mock.call( - table=constants.LOCAL_SWITCHING, priority=2, - actions="drop", - in_port=ioport)] - self.assertTrue(remove_flows_fn.called) - self.assertEqual(expected, add_int_flow_fn.call_args_list) - self.assertEqual(add_int_flow_fn.call_count, 5) + expected_on_int_br = [ + # setup_dvr_flows_on_integ_br + mock.call.delete_flows(), + mock.call.setup_canary_table(), + mock.call.install_drop(table_id=constants.DVR_TO_SRC_MAC, + priority=1), + mock.call.install_drop(table_id=constants.DVR_TO_SRC_MAC_VLAN, + priority=1), + mock.call.install_normal(table_id=constants.LOCAL_SWITCHING, + priority=1), + mock.call.install_drop(table_id=constants.LOCAL_SWITCHING, + priority=2, + in_port=ioport), + ] + self.assertEqual(expected_on_int_br, int_br.mock_calls) + self.assertEqual([], tun_br.mock_calls) def test_get_dvr_mac_address(self): self._setup_for_dvr_test() @@ -1928,17 +1888,18 @@ class TestOvsDvrNeutronAgent(base.BaseTestCase): def test_get_dvr_mac_address_exception(self): self._setup_for_dvr_test() self.agent.dvr_agent.dvr_mac_address = None + int_br = mock.create_autospec(self.agent.int_br) with contextlib.nested( mock.patch.object(self.agent.dvr_agent.plugin_rpc, 'get_dvr_mac_address_by_host', side_effect=oslo_messaging.RemoteError), - mock.patch.object(self.agent.dvr_agent.int_br, - 'add_flow')) as (gd_mac, add_int_flow_fn): - + mock.patch.object(self.agent, 'int_br', new=int_br), + mock.patch.object(self.agent.dvr_agent, 'int_br', new=int_br), + ) as (gd_mac, _, _): self.agent.dvr_agent.get_dvr_mac_address() self.assertIsNone(self.agent.dvr_agent.dvr_mac_address) self.assertFalse(self.agent.dvr_agent.in_distributed_mode()) - self.assertEqual(add_int_flow_fn.call_count, 1) + self.assertEqual([mock.call.install_normal()], int_br.mock_calls) def test_get_dvr_mac_address_retried(self): valid_entry = {'host': 'cn1', 'mac_address': 'aa:22:33:44:55:66'} @@ -1962,12 +1923,15 @@ class TestOvsDvrNeutronAgent(base.BaseTestCase): # Raise a timeout every time until we give up, currently 5 tries self._setup_for_dvr_test() self.agent.dvr_agent.dvr_mac_address = None + int_br = mock.create_autospec(self.agent.int_br) with contextlib.nested( - mock.patch.object(self.agent.dvr_agent.plugin_rpc, - 'get_dvr_mac_address_by_host', - side_effect=raise_timeout), - mock.patch.object(utils, "execute"), - ) as (rpc_mock, execute_mock): + mock.patch.object(self.agent.dvr_agent.plugin_rpc, + 'get_dvr_mac_address_by_host', + side_effect=raise_timeout), + mock.patch.object(utils, "execute"), + mock.patch.object(self.agent, 'int_br', new=int_br), + mock.patch.object(self.agent.dvr_agent, 'int_br', new=int_br), + ) as (rpc_mock, execute_mock, _, _): self.agent.dvr_agent.get_dvr_mac_address() self.assertIsNone(self.agent.dvr_agent.dvr_mac_address) self.assertFalse(self.agent.dvr_agent.in_distributed_mode()) @@ -1978,69 +1942,77 @@ class TestOvsDvrNeutronAgent(base.BaseTestCase): self._setup_for_dvr_test() newhost = 'cn2' newmac = 'aa:bb:cc:dd:ee:ff' - int_ofport = self.agent.dvr_agent.int_ofports['physeth1'] - patch_int_ofport = self.agent.dvr_agent.patch_int_ofport - patch_tun_ofport = self.agent.dvr_agent.patch_tun_ofport + int_br = mock.create_autospec(self.agent.int_br) + tun_br = mock.create_autospec(self.agent.tun_br) + phys_br = mock.create_autospec(self.br_phys_cls('br-phys')) + physical_network = 'physeth1' with contextlib.nested( - mock.patch.object(self.agent.dvr_agent.int_br, 'add_flow'), - mock.patch.object(self.agent.dvr_agent.tun_br, 'add_flow'), - mock.patch.object(self.agent.dvr_agent.int_br, 'delete_flows'), - mock.patch.object(self.agent.dvr_agent.phys_brs['physeth1'], - 'add_flow') - ) as (add_flow_fn, add_flow_tn_fn, del_flows_fn, add_flow_phys_fn): + mock.patch.object(self.agent, 'int_br', new=int_br), + mock.patch.object(self.agent, 'tun_br', new=tun_br), + mock.patch.dict(self.agent.phys_brs, + {physical_network: phys_br}), + mock.patch.object(self.agent.dvr_agent, 'int_br', new=int_br), + mock.patch.object(self.agent.dvr_agent, 'tun_br', new=tun_br), + mock.patch.dict(self.agent.dvr_agent.phys_brs, + {physical_network: phys_br}), + ): self.agent.dvr_agent.\ dvr_mac_address_update( dvr_macs=[{'host': newhost, 'mac_address': newmac}]) - expected = [ - mock.call(table=constants.LOCAL_SWITCHING, - priority=4, - in_port=int_ofport, - dl_src=newmac, - actions="resubmit(,%s)" % - constants.DVR_TO_SRC_MAC_VLAN), - mock.call(table=constants.LOCAL_SWITCHING, - priority=2, - in_port=patch_tun_ofport, - dl_src=newmac, - actions="resubmit(,%s)" % - constants.DVR_TO_SRC_MAC)] - self.assertEqual(expected, add_flow_fn.call_args_list) - add_flow_phys_fn.assert_called_with( - table=constants.DVR_NOT_LEARN_VLAN, - priority=2, - dl_src=newmac, - actions="output:%s" % - self.agent.dvr_agent.phys_ofports['physeth1']) - add_flow_tn_fn.assert_called_with(table=constants.DVR_NOT_LEARN, - priority=1, - dl_src=newmac, - actions="output:%s" - % patch_int_ofport) - self.assertFalse(del_flows_fn.called) + expected_on_int_br = [ + mock.call.add_dvr_mac_vlan( + mac=newmac, + port=self.agent.int_ofports[physical_network]), + mock.call.add_dvr_mac_tun( + mac=newmac, + port=self.agent.patch_tun_ofport), + ] + expected_on_tun_br = [ + mock.call.add_dvr_mac_tun( + mac=newmac, + port=self.agent.patch_int_ofport), + ] + expected_on_phys_br = [ + mock.call.add_dvr_mac_vlan( + mac=newmac, + port=self.agent.phys_ofports[physical_network]), + ] + self.assertEqual(expected_on_int_br, int_br.mock_calls) + self.assertEqual(expected_on_tun_br, tun_br.mock_calls) + self.assertEqual(expected_on_phys_br, phys_br.mock_calls) + int_br.reset_mock() + tun_br.reset_mock() + phys_br.reset_mock() with contextlib.nested( - mock.patch.object(self.agent.dvr_agent.int_br, 'add_flow'), - mock.patch.object(self.agent.dvr_agent.tun_br, 'delete_flows'), - mock.patch.object(self.agent.dvr_agent.int_br, 'delete_flows'), - mock.patch.object(self.agent.dvr_agent.phys_brs['physeth1'], - 'delete_flows'), - ) as (add_flow_fn, del_flows_tn_fn, del_flows_fn, del_flows_phys_fn): + mock.patch.object(self.agent, 'int_br', new=int_br), + mock.patch.object(self.agent, 'tun_br', new=tun_br), + mock.patch.dict(self.agent.phys_brs, + {physical_network: phys_br}), + mock.patch.object(self.agent.dvr_agent, 'int_br', new=int_br), + mock.patch.object(self.agent.dvr_agent, 'tun_br', new=tun_br), + mock.patch.dict(self.agent.dvr_agent.phys_brs, + {physical_network: phys_br}), + ): self.agent.dvr_agent.dvr_mac_address_update(dvr_macs=[]) - ioport = self.agent.dvr_agent.int_ofports['physeth1'] - expected = [ - mock.call(table=constants.LOCAL_SWITCHING, - in_port=ioport, - dl_src=newmac), - mock.call(table=constants.LOCAL_SWITCHING, - in_port=patch_tun_ofport, - dl_src=newmac)] - self.assertEqual(expected, del_flows_fn.call_args_list) - del_flows_phys_fn.asert_called_with( - table=constants.DVR_NOT_LEARN_VLAN, - dl_src=newmac) - del_flows_tn_fn.assert_called_with(table=constants.DVR_NOT_LEARN, - dl_src=newmac) - self.assertFalse(add_flow_fn.called) + expected_on_int_br = [ + mock.call.remove_dvr_mac_vlan( + mac=newmac), + mock.call.remove_dvr_mac_tun( + mac=newmac, + port=self.agent.patch_tun_ofport), + ] + expected_on_tun_br = [ + mock.call.remove_dvr_mac_tun( + mac=newmac), + ] + expected_on_phys_br = [ + mock.call.remove_dvr_mac_vlan( + mac=newmac), + ] + self.assertEqual(expected_on_int_br, int_br.mock_calls) + self.assertEqual(expected_on_tun_br, tun_br.mock_calls) + self.assertEqual(expected_on_phys_br, phys_br.mock_calls) def test_ovs_restart(self): self._setup_for_dvr_test() @@ -2050,11 +2022,13 @@ class TestOvsDvrNeutronAgent(base.BaseTestCase): 'setup_dvr_flows_on_phys_br', 'setup_dvr_mac_flows_on_all_brs') reset_mocks = [mock.patch.object(self.agent.dvr_agent, method).start() for method in reset_methods] + tun_br = mock.create_autospec(self.agent.tun_br) with contextlib.nested( mock.patch.object(self.agent, 'check_ovs_status', return_value=constants.OVS_RESTARTED), mock.patch.object(self.agent, '_agent_has_updates', - side_effect=TypeError('loop exit')) + side_effect=TypeError('loop exit')), + mock.patch.object(self.agent, 'tun_br', new=tun_br), ): # block RPC calls and bridge calls self.agent.setup_physical_bridges = mock.Mock() @@ -2066,3 +2040,8 @@ class TestOvsDvrNeutronAgent(base.BaseTestCase): except TypeError: pass self.assertTrue(all([x.called for x in reset_mocks])) + + +class TestOvsDvrNeutronAgentOFCtl(TestOvsDvrNeutronAgent, + ovs_test_base.OVSOFCtlTestBase): + pass diff --git a/neutron/tests/unit/plugins/openvswitch/test_ovs_tunnel.py b/neutron/tests/unit/plugins/openvswitch/test_ovs_tunnel.py index 7773d5a0e..a3f8600ed 100644 --- a/neutron/tests/unit/plugins/openvswitch/test_ovs_tunnel.py +++ b/neutron/tests/unit/plugins/openvswitch/test_ovs_tunnel.py @@ -24,9 +24,8 @@ from oslo_log import log from neutron.agent.common import ovs_lib from neutron.agent.linux import ip_lib from neutron.plugins.common import constants as p_const -from neutron.plugins.openvswitch.agent import ovs_neutron_agent from neutron.plugins.openvswitch.common import constants -from neutron.tests import base +from neutron.tests.unit.plugins.openvswitch.agent import ovs_test_base # Useful global dummy variables. @@ -40,11 +39,6 @@ OFPORT_NUM = 1 VIF_PORT = ovs_lib.VifPort('port', OFPORT_NUM, VIF_ID, VIF_MAC, 'switch') VIF_PORTS = {VIF_ID: VIF_PORT} -LVM = ovs_neutron_agent.LocalVLANMapping(LV_ID, 'gre', None, LS_ID, VIF_PORTS) -LVM_FLAT = ovs_neutron_agent.LocalVLANMapping( - LV_ID, 'flat', 'net1', LS_ID, VIF_PORTS) -LVM_VLAN = ovs_neutron_agent.LocalVLANMapping( - LV_ID, 'vlan', 'net1', LS_ID, VIF_PORTS) FIXED_IPS = [{'subnet_id': 'my-subnet-uuid', 'ip_address': '1.1.1.1'}] VM_DEVICE_OWNER = "compute:None" @@ -66,7 +60,7 @@ class DummyVlanBinding(object): self.vlan_id = vlan_id -class TunnelTest(base.BaseTestCase): +class TunnelTest(object): USE_VETH_INTERCONNECTION = False VETH_MTU = None @@ -86,21 +80,41 @@ class TunnelTest(base.BaseTestCase): self.MAP_TUN_INT_OFPORT = 33333 self.MAP_TUN_PHY_OFPORT = 44444 + self.LVM = self.mod_agent.LocalVLANMapping( + LV_ID, 'gre', None, LS_ID, VIF_PORTS) + self.LVM_FLAT = self.mod_agent.LocalVLANMapping( + LV_ID, 'flat', 'net1', LS_ID, VIF_PORTS) + self.LVM_VLAN = self.mod_agent.LocalVLANMapping( + LV_ID, 'vlan', 'net1', LS_ID, VIF_PORTS) + self.inta = mock.Mock() self.intb = mock.Mock() - self.ovs_bridges = {self.INT_BRIDGE: mock.Mock(), - self.TUN_BRIDGE: mock.Mock(), - self.MAP_TUN_BRIDGE: mock.Mock(), - } + self.ovs_bridges = { + self.INT_BRIDGE: mock.create_autospec( + self.br_int_cls('br-int')), + self.TUN_BRIDGE: mock.create_autospec( + self.br_tun_cls('br-tun')), + self.MAP_TUN_BRIDGE: mock.create_autospec( + self.br_phys_cls('br-phys')), + } self.ovs_int_ofports = { 'patch-tun': self.TUN_OFPORT, 'int-%s' % self.MAP_TUN_BRIDGE: self.MAP_TUN_INT_OFPORT } - self.mock_bridge = mock.patch.object(ovs_lib, 'OVSBridge').start() - self.mock_bridge.side_effect = (lambda br_name: - self.ovs_bridges[br_name]) + def lookup_br(br_name, *args, **kwargs): + return self.ovs_bridges[br_name] + + self.mock_int_bridge_cls = mock.patch(self._BR_INT_CLASS, + autospec=True).start() + self.mock_int_bridge_cls.side_effect = lookup_br + self.mock_phys_bridge_cls = mock.patch(self._BR_PHYS_CLASS, + autospec=True).start() + self.mock_phys_bridge_cls.side_effect = lookup_br + self.mock_tun_bridge_cls = mock.patch(self._BR_TUN_CLASS, + autospec=True).start() + self.mock_tun_bridge_cls.side_effect = lookup_br self.mock_int_bridge = self.ovs_bridges[self.INT_BRIDGE] self.mock_int_bridge.add_port.return_value = self.MAP_TUN_INT_OFPORT @@ -139,10 +153,14 @@ class TunnelTest(base.BaseTestCase): self._define_expected_calls() - def _define_expected_calls(self): - self.mock_bridge_expected = [ + def _define_expected_calls(self, arp_responder=False): + self.mock_int_bridge_cls_expected = [ mock.call(self.INT_BRIDGE), + ] + self.mock_phys_bridge_cls_expected = [ mock.call(self.MAP_TUN_BRIDGE), + ] + self.mock_tun_bridge_cls_expected = [ mock.call(self.TUN_BRIDGE), ] @@ -150,20 +168,17 @@ class TunnelTest(base.BaseTestCase): self.mock_int_bridge_expected = [ mock.call.create(), mock.call.set_secure_mode(), + mock.call.setup_controllers(mock.ANY), mock.call.delete_port('patch-tun'), - mock.call.remove_all_flows(), - mock.call.add_flow(priority=1, actions='normal'), - mock.call.add_flow(priority=0, table=constants.CANARY_TABLE, - actions='drop'), + mock.call.setup_default_table(), ] self.mock_map_tun_bridge_expected = [ - mock.call.remove_all_flows(), - mock.call.add_flow(priority=1, actions='normal'), + mock.call.setup_controllers(mock.ANY), + mock.call.setup_default_table(), mock.call.delete_port('phy-%s' % self.MAP_TUN_BRIDGE), mock.call.add_patch_port('phy-%s' % self.MAP_TUN_BRIDGE, - constants.NONEXISTENT_PEER), - ] + constants.NONEXISTENT_PEER), ] self.mock_int_bridge_expected += [ mock.call.delete_port('int-%s' % self.MAP_TUN_BRIDGE), mock.call.add_patch_port('int-%s' % self.MAP_TUN_BRIDGE, @@ -171,17 +186,13 @@ class TunnelTest(base.BaseTestCase): ] self.mock_int_bridge_expected += [ - mock.call.add_flow(priority=2, - in_port=self.MAP_TUN_INT_OFPORT, - actions='drop'), + mock.call.drop_port(in_port=self.MAP_TUN_INT_OFPORT), mock.call.set_db_attribute( 'Interface', 'int-%s' % self.MAP_TUN_BRIDGE, 'options:peer', 'phy-%s' % self.MAP_TUN_BRIDGE), ] self.mock_map_tun_bridge_expected += [ - mock.call.add_flow(priority=2, - in_port=self.MAP_TUN_PHY_OFPORT, - actions='drop'), + mock.call.drop_port(in_port=self.MAP_TUN_PHY_OFPORT), mock.call.set_db_attribute( 'Interface', 'phy-%s' % self.MAP_TUN_BRIDGE, 'options:peer', 'int-%s' % self.MAP_TUN_BRIDGE), @@ -189,6 +200,7 @@ class TunnelTest(base.BaseTestCase): self.mock_tun_bridge_expected = [ mock.call.reset_bridge(secure_mode=True), + mock.call.setup_controllers(mock.ANY), mock.call.add_patch_port('patch-int', 'patch-tun'), ] self.mock_int_bridge_expected += [ @@ -199,48 +211,8 @@ class TunnelTest(base.BaseTestCase): ] self.mock_tun_bridge_expected += [ - mock.call.remove_all_flows(), - mock.call.add_flow(priority=1, - actions="resubmit(,%s)" % - constants.PATCH_LV_TO_TUN, - in_port=self.INT_OFPORT), - mock.call.add_flow(priority=0, actions="drop"), - mock.call.add_flow(priority=0, table=constants.PATCH_LV_TO_TUN, - dl_dst=UCAST_MAC, - actions="resubmit(,%s)" % - constants.UCAST_TO_TUN), - mock.call.add_flow(priority=0, table=constants.PATCH_LV_TO_TUN, - dl_dst=BCAST_MAC, - actions="resubmit(,%s)" % - constants.FLOOD_TO_TUN), - ] - for tunnel_type in constants.TUNNEL_NETWORK_TYPES: - self.mock_tun_bridge_expected.append( - mock.call.add_flow( - table=constants.TUN_TABLE[tunnel_type], - priority=0, - actions="drop")) - learned_flow = ("table=%s," - "priority=1," - "hard_timeout=300," - "NXM_OF_VLAN_TCI[0..11]," - "NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[]," - "load:0->NXM_OF_VLAN_TCI[]," - "load:NXM_NX_TUN_ID[]->NXM_NX_TUN_ID[]," - "output:NXM_OF_IN_PORT[]" % - constants.UCAST_TO_TUN) - self.mock_tun_bridge_expected += [ - mock.call.add_flow(table=constants.LEARN_FROM_TUN, - priority=1, - actions="learn(%s),output:%s" % - (learned_flow, self.INT_OFPORT)), - mock.call.add_flow(table=constants.UCAST_TO_TUN, - priority=0, - actions="resubmit(,%s)" % - constants.FLOOD_TO_TUN), - mock.call.add_flow(table=constants.FLOOD_TO_TUN, - priority=0, - actions="drop") + mock.call.delete_flows(), + mock.call.setup_default_table(self.INT_OFPORT, arp_responder), ] self.device_exists_expected = [] @@ -255,6 +227,12 @@ class TunnelTest(base.BaseTestCase): self.execute_expected = [] def _build_agent(self, **kwargs): + bridge_classes = { + 'br_int': self.mock_int_bridge_cls, + 'br_phys': self.mock_phys_bridge_cls, + 'br_tun': self.mock_tun_bridge_cls, + } + kwargs.setdefault('bridge_classes', bridge_classes) kwargs.setdefault('integ_br', self.INT_BRIDGE) kwargs.setdefault('tun_br', self.TUN_BRIDGE) kwargs.setdefault('local_ip', '10.0.0.1') @@ -264,14 +242,19 @@ class TunnelTest(base.BaseTestCase): kwargs.setdefault('veth_mtu', self.VETH_MTU) kwargs.setdefault('use_veth_interconnection', self.USE_VETH_INTERCONNECTION) - return ovs_neutron_agent.OVSNeutronAgent(**kwargs) + return self.mod_agent.OVSNeutronAgent(**kwargs) def _verify_mock_call(self, mock_obj, expected): mock_obj.assert_has_calls(expected) self.assertEqual(len(mock_obj.mock_calls), len(expected)) def _verify_mock_calls(self): - self._verify_mock_call(self.mock_bridge, self.mock_bridge_expected) + self._verify_mock_call(self.mock_int_bridge_cls, + self.mock_int_bridge_cls_expected) + self._verify_mock_call(self.mock_tun_bridge_cls, + self.mock_tun_bridge_cls_expected) + self._verify_mock_call(self.mock_phys_bridge_cls, + self.mock_phys_bridge_cls_expected) self._verify_mock_call(self.mock_int_bridge, self.mock_int_bridge_expected) self._verify_mock_call(self.mock_map_tun_bridge, @@ -296,20 +279,7 @@ class TunnelTest(base.BaseTestCase): # The next two tests use l2_pop flag to test ARP responder def test_construct_with_arp_responder(self): self._build_agent(l2_population=True, arp_responder=True) - self.mock_tun_bridge_expected.insert( - 5, mock.call.add_flow(table=constants.PATCH_LV_TO_TUN, - priority=1, - proto="arp", - dl_dst="ff:ff:ff:ff:ff:ff", - actions="resubmit(,%s)" % - constants.ARP_RESPONDER) - ) - self.mock_tun_bridge_expected.insert( - 12, mock.call.add_flow(table=constants.ARP_RESPONDER, - priority=0, - actions="resubmit(,%s)" % - constants.FLOOD_TO_TUN) - ) + self._define_expected_calls(True) self._verify_mock_calls() def test_construct_without_arp_responder(self): @@ -321,18 +291,13 @@ class TunnelTest(base.BaseTestCase): self._verify_mock_calls() def test_provision_local_vlan(self): - ofports = ','.join(TUN_OFPORTS[p_const.TYPE_GRE].values()) + ofports = TUN_OFPORTS[p_const.TYPE_GRE].values() self.mock_tun_bridge_expected += [ - mock.call.mod_flow(table=constants.FLOOD_TO_TUN, - dl_vlan=LV_ID, - actions="strip_vlan," - "set_tunnel:%s,output:%s" % - (LS_ID, ofports)), - mock.call.add_flow(table=constants.TUN_TABLE['gre'], - priority=1, - tun_id=LS_ID, - actions="mod_vlan_vid:%s,resubmit(,%s)" % - (LV_ID, constants.LEARN_FROM_TUN)), + mock.call.install_flood_to_tun(LV_ID, LS_ID, ofports), + mock.call.provision_local_vlan( + network_type=p_const.TYPE_GRE, + lvid=LV_ID, + segmentation_id=LS_ID), ] a = self._build_agent() @@ -342,15 +307,17 @@ class TunnelTest(base.BaseTestCase): self._verify_mock_calls() def test_provision_local_vlan_flat(self): - action_string = 'strip_vlan,normal' self.mock_map_tun_bridge_expected.append( - mock.call.add_flow(priority=4, in_port=self.MAP_TUN_PHY_OFPORT, - dl_vlan=LV_ID, actions=action_string)) - - action_string = 'mod_vlan_vid:%s,normal' % LV_ID + mock.call.provision_local_vlan( + port=self.MAP_TUN_PHY_OFPORT, + lvid=LV_ID, + segmentation_id=None, + distributed=False)) self.mock_int_bridge_expected.append( - mock.call.add_flow(priority=3, in_port=self.INT_OFPORT, - dl_vlan=65535, actions=action_string)) + mock.call.provision_local_vlan( + port=self.INT_OFPORT, + lvid=LV_ID, + segmentation_id=None)) a = self._build_agent() a.available_local_vlans = set([LV_ID]) @@ -366,16 +333,17 @@ class TunnelTest(base.BaseTestCase): self._verify_mock_calls() def test_provision_local_vlan_vlan(self): - action_string = 'mod_vlan_vid:%s,normal' % LS_ID self.mock_map_tun_bridge_expected.append( - mock.call.add_flow(priority=4, in_port=self.MAP_TUN_PHY_OFPORT, - dl_vlan=LV_ID, actions=action_string)) - - action_string = 'mod_vlan_vid:%s,normal' % LV_ID + mock.call.provision_local_vlan( + port=self.MAP_TUN_PHY_OFPORT, + lvid=LV_ID, + segmentation_id=LS_ID, + distributed=False)) self.mock_int_bridge_expected.append( - mock.call.add_flow(priority=3, in_port=self.INT_OFPORT, - dl_vlan=LS_ID, actions=action_string)) - + mock.call.provision_local_vlan( + port=self.INT_OFPORT, + lvid=LV_ID, + segmentation_id=LS_ID)) a = self._build_agent() a.available_local_vlans = set([LV_ID]) a.phys_brs['net1'] = self.mock_map_tun_bridge @@ -391,54 +359,58 @@ class TunnelTest(base.BaseTestCase): def test_reclaim_local_vlan(self): self.mock_tun_bridge_expected += [ - mock.call.delete_flows( - table=constants.TUN_TABLE['gre'], tun_id=LS_ID), - mock.call.delete_flows(dl_vlan=LVM.vlan) + mock.call.reclaim_local_vlan(network_type='gre', + segmentation_id=LS_ID), + mock.call.delete_flood_to_tun(LV_ID), + mock.call.delete_unicast_to_tun(LV_ID, None), + mock.call.delete_arp_responder(LV_ID, None), ] a = self._build_agent() a.available_local_vlans = set() - a.local_vlan_map[NET_UUID] = LVM + a.local_vlan_map[NET_UUID] = self.LVM a.reclaim_local_vlan(NET_UUID) - self.assertIn(LVM.vlan, a.available_local_vlans) + self.assertIn(self.LVM.vlan, a.available_local_vlans) self._verify_mock_calls() def test_reclaim_local_vlan_flat(self): self.mock_map_tun_bridge_expected.append( - mock.call.delete_flows( - in_port=self.MAP_TUN_PHY_OFPORT, dl_vlan=LVM_FLAT.vlan)) + mock.call.reclaim_local_vlan( + port=self.MAP_TUN_PHY_OFPORT, + lvid=self.LVM_FLAT.vlan)) self.mock_int_bridge_expected.append( - mock.call.delete_flows( - dl_vlan=65535, in_port=self.INT_OFPORT)) - + mock.call.reclaim_local_vlan( + port=self.INT_OFPORT, + segmentation_id=None)) a = self._build_agent() a.phys_brs['net1'] = self.mock_map_tun_bridge a.phys_ofports['net1'] = self.MAP_TUN_PHY_OFPORT a.int_ofports['net1'] = self.INT_OFPORT a.available_local_vlans = set() - a.local_vlan_map[NET_UUID] = LVM_FLAT + a.local_vlan_map[NET_UUID] = self.LVM_FLAT a.reclaim_local_vlan(NET_UUID) - self.assertIn(LVM_FLAT.vlan, a.available_local_vlans) + self.assertIn(self.LVM_FLAT.vlan, a.available_local_vlans) self._verify_mock_calls() def test_reclaim_local_vlan_vlan(self): self.mock_map_tun_bridge_expected.append( - mock.call.delete_flows( - in_port=self.MAP_TUN_PHY_OFPORT, dl_vlan=LVM_VLAN.vlan)) + mock.call.reclaim_local_vlan( + port=self.MAP_TUN_PHY_OFPORT, + lvid=self.LVM_VLAN.vlan)) self.mock_int_bridge_expected.append( - mock.call.delete_flows( - dl_vlan=LS_ID, in_port=self.INT_OFPORT)) - + mock.call.reclaim_local_vlan( + port=self.INT_OFPORT, + segmentation_id=LS_ID)) a = self._build_agent() a.phys_brs['net1'] = self.mock_map_tun_bridge a.phys_ofports['net1'] = self.MAP_TUN_PHY_OFPORT a.int_ofports['net1'] = self.INT_OFPORT a.available_local_vlans = set() - a.local_vlan_map[NET_UUID] = LVM_VLAN + a.local_vlan_map[NET_UUID] = self.LVM_VLAN a.reclaim_local_vlan(NET_UUID) - self.assertIn(LVM_VLAN.vlan, a.available_local_vlans) + self.assertIn(self.LVM_VLAN.vlan, a.available_local_vlans) self._verify_mock_calls() def test_port_bound(self): @@ -453,17 +425,18 @@ class TunnelTest(base.BaseTestCase): vlan_mapping)] a = self._build_agent() - a.local_vlan_map[NET_UUID] = LVM + a.local_vlan_map[NET_UUID] = self.LVM a.local_dvr_map = {} + self.ovs_bridges[self.INT_BRIDGE].db_get_val.return_value = {} a.port_bound(VIF_PORT, NET_UUID, 'gre', None, LS_ID, FIXED_IPS, VM_DEVICE_OWNER, False) self._verify_mock_calls() def test_port_unbound(self): - with mock.patch.object(ovs_neutron_agent.OVSNeutronAgent, + with mock.patch.object(self.mod_agent.OVSNeutronAgent, 'reclaim_local_vlan') as reclaim_local_vlan: a = self._build_agent() - a.local_vlan_map[NET_UUID] = LVM + a.local_vlan_map[NET_UUID] = self.LVM a.port_unbound(VIF_ID, NET_UUID) reclaim_local_vlan.assert_called_once_with(NET_UUID) @@ -474,14 +447,14 @@ class TunnelTest(base.BaseTestCase): mock.call.db_get_val('Port', VIF_PORT.port_name, 'tag'), mock.call.set_db_attribute( 'Port', VIF_PORT.port_name, - 'tag', ovs_neutron_agent.DEAD_VLAN_TAG), - mock.call.add_flow(priority=2, in_port=VIF_PORT.ofport, - actions='drop') + 'tag', self.mod_agent.DEAD_VLAN_TAG), + mock.call.drop_port(in_port=VIF_PORT.ofport), ] a = self._build_agent() a.available_local_vlans = set([LV_ID]) - a.local_vlan_map[NET_UUID] = LVM + a.local_vlan_map[NET_UUID] = self.LVM + self.ovs_bridges[self.INT_BRIDGE].db_get_val.return_value = mock.Mock() a.port_dead(VIF_PORT) self._verify_mock_calls() @@ -491,8 +464,7 @@ class TunnelTest(base.BaseTestCase): self.mock_tun_bridge_expected += [ mock.call.add_tunnel_port('gre-0a000a01', '10.0.10.1', '10.0.0.1', 'gre', 4789, True), - mock.call.add_flow(priority=1, in_port=tunnel_port, - actions='resubmit(,3)') + mock.call.setup_tunnel_port('gre', tunnel_port), ] a = self._build_agent() @@ -517,20 +489,22 @@ class TunnelTest(base.BaseTestCase): 'removed': set(['tap0'])} self.mock_int_bridge_expected += [ - mock.call.dump_flows_for_table(constants.CANARY_TABLE), - mock.call.dump_flows_for_table(constants.CANARY_TABLE) + mock.call.check_canary_table(), + mock.call.check_canary_table() ] + self.ovs_bridges[self.INT_BRIDGE].check_canary_table.return_value = \ + constants.OVS_NORMAL with contextlib.nested( mock.patch.object(log.KeywordArgumentAdapter, 'exception'), - mock.patch.object(ovs_neutron_agent.OVSNeutronAgent, + mock.patch.object(self.mod_agent.OVSNeutronAgent, 'scan_ports'), - mock.patch.object(ovs_neutron_agent.OVSNeutronAgent, + mock.patch.object(self.mod_agent.OVSNeutronAgent, 'process_network_ports'), - mock.patch.object(ovs_neutron_agent.OVSNeutronAgent, + mock.patch.object(self.mod_agent.OVSNeutronAgent, 'tunnel_sync'), mock.patch.object(time, 'sleep'), - mock.patch.object(ovs_neutron_agent.OVSNeutronAgent, + mock.patch.object(self.mod_agent.OVSNeutronAgent, 'update_stale_ofport_rules') ) as (log_exception, scan_ports, process_network_ports, ts, time_sleep, update_stale): @@ -569,29 +543,35 @@ class TunnelTest(base.BaseTestCase): self._verify_mock_calls() +class TunnelTestOFCtl(TunnelTest, ovs_test_base.OVSOFCtlTestBase): + pass + + class TunnelTestUseVethInterco(TunnelTest): USE_VETH_INTERCONNECTION = True - def _define_expected_calls(self): - self.mock_bridge_expected = [ + def _define_expected_calls(self, arp_responder=False): + self.mock_int_bridge_cls_expected = [ mock.call(self.INT_BRIDGE), + ] + self.mock_phys_bridge_cls_expected = [ mock.call(self.MAP_TUN_BRIDGE), + ] + self.mock_tun_bridge_cls_expected = [ mock.call(self.TUN_BRIDGE), ] self.mock_int_bridge_expected = [ mock.call.create(), mock.call.set_secure_mode(), + mock.call.setup_controllers(mock.ANY), mock.call.delete_port('patch-tun'), - mock.call.remove_all_flows(), - mock.call.add_flow(priority=1, actions='normal'), - mock.call.add_flow(table=constants.CANARY_TABLE, priority=0, - actions="drop") + mock.call.setup_default_table(), ] self.mock_map_tun_bridge_expected = [ - mock.call.remove_all_flows(), - mock.call.add_flow(priority=1, actions='normal'), + mock.call.setup_controllers(mock.ANY), + mock.call.setup_default_table(), mock.call.delete_port('phy-%s' % self.MAP_TUN_BRIDGE), mock.call.add_port(self.intb), ] @@ -601,18 +581,15 @@ class TunnelTestUseVethInterco(TunnelTest): ] self.mock_int_bridge_expected += [ - mock.call.add_flow(priority=2, - in_port=self.MAP_TUN_INT_OFPORT, - actions='drop') + mock.call.drop_port(in_port=self.MAP_TUN_INT_OFPORT), ] self.mock_map_tun_bridge_expected += [ - mock.call.add_flow(priority=2, - in_port=self.MAP_TUN_PHY_OFPORT, - actions='drop') + mock.call.drop_port(in_port=self.MAP_TUN_PHY_OFPORT), ] self.mock_tun_bridge_expected = [ mock.call.reset_bridge(secure_mode=True), + mock.call.setup_controllers(mock.ANY), mock.call.add_patch_port('patch-int', 'patch-tun'), ] self.mock_int_bridge_expected += [ @@ -622,50 +599,8 @@ class TunnelTestUseVethInterco(TunnelTest): mock.call.get_vif_ports(), ] self.mock_tun_bridge_expected += [ - mock.call.remove_all_flows(), - mock.call.add_flow(priority=1, - in_port=self.INT_OFPORT, - actions="resubmit(,%s)" % - constants.PATCH_LV_TO_TUN), - mock.call.add_flow(priority=0, actions='drop'), - mock.call.add_flow(priority=0, - table=constants.PATCH_LV_TO_TUN, - dl_dst=UCAST_MAC, - actions="resubmit(,%s)" % - constants.UCAST_TO_TUN), - mock.call.add_flow(priority=0, - table=constants.PATCH_LV_TO_TUN, - dl_dst=BCAST_MAC, - actions="resubmit(,%s)" % - constants.FLOOD_TO_TUN), - ] - for tunnel_type in constants.TUNNEL_NETWORK_TYPES: - self.mock_tun_bridge_expected.append( - mock.call.add_flow( - table=constants.TUN_TABLE[tunnel_type], - priority=0, - actions="drop")) - learned_flow = ("table=%s," - "priority=1," - "hard_timeout=300," - "NXM_OF_VLAN_TCI[0..11]," - "NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[]," - "load:0->NXM_OF_VLAN_TCI[]," - "load:NXM_NX_TUN_ID[]->NXM_NX_TUN_ID[]," - "output:NXM_OF_IN_PORT[]" % - constants.UCAST_TO_TUN) - self.mock_tun_bridge_expected += [ - mock.call.add_flow(table=constants.LEARN_FROM_TUN, - priority=1, - actions="learn(%s),output:%s" % - (learned_flow, self.INT_OFPORT)), - mock.call.add_flow(table=constants.UCAST_TO_TUN, - priority=0, - actions="resubmit(,%s)" % - constants.FLOOD_TO_TUN), - mock.call.add_flow(table=constants.FLOOD_TO_TUN, - priority=0, - actions="drop") + mock.call.delete_flows(), + mock.call.setup_default_table(self.INT_OFPORT, arp_responder), ] self.device_exists_expected = [ @@ -690,10 +625,20 @@ class TunnelTestUseVethInterco(TunnelTest): '--timeout=10'])] +class TunnelTestUseVethIntercoOFCtl(TunnelTestUseVethInterco, + ovs_test_base.OVSOFCtlTestBase): + pass + + class TunnelTestWithMTU(TunnelTestUseVethInterco): VETH_MTU = 1500 - def _define_expected_calls(self): - super(TunnelTestWithMTU, self)._define_expected_calls() + def _define_expected_calls(self, arp_responder=False): + super(TunnelTestWithMTU, self)._define_expected_calls(arp_responder) self.inta_expected.append(mock.call.link.set_mtu(self.VETH_MTU)) self.intb_expected.append(mock.call.link.set_mtu(self.VETH_MTU)) + + +class TunnelTestWithMTUOFCtl(TunnelTestWithMTU, + ovs_test_base.OVSOFCtlTestBase): + pass -- 2.45.2