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
This commit is contained in:
YAMAMOTO Takashi 2015-03-02 13:14:48 +09:00
parent 6ba78cec1a
commit eab71473c3
25 changed files with 2713 additions and 1479 deletions

View File

@ -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

View File

@ -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()

View File

@ -0,0 +1,45 @@
# Copyright (C) 2014,2015 VA Linux Systems Japan K.K.
# Copyright (C) 2014 Fumihiko Kakuma <kakuma at valinux co jp>
# Copyright (C) 2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
# 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()

View File

@ -0,0 +1,89 @@
# Copyright (C) 2014,2015 VA Linux Systems Japan K.K.
# Copyright (C) 2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
# 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)

View File

@ -0,0 +1,133 @@
# Copyright (C) 2014,2015 VA Linux Systems Japan K.K.
# Copyright (C) 2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
# 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)

View File

@ -0,0 +1,59 @@
# Copyright (C) 2014,2015 VA Linux Systems Japan K.K.
# Copyright (C) 2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
# 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)

View File

@ -0,0 +1,246 @@
# Copyright (C) 2014,2015 VA Linux Systems Japan K.K.
# Copyright (C) 2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
# 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)

View File

@ -0,0 +1,33 @@
# Copyright (C) 2015 VA Linux Systems Japan K.K.
# Copyright (C) 2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
# 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)

View File

@ -0,0 +1,74 @@
# Copyright (C) 2014,2015 VA Linux Systems Japan K.K.
# Copyright (C) 2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
# 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()

View File

@ -0,0 +1,30 @@
# Copyright (C) 2014,2015 VA Linux Systems Japan K.K.
# Copyright (C) 2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
# 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)

View File

@ -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")
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)
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))
# 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

View File

@ -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")
addresses = {ip for ip in addresses
if netaddr.IPNetwork(ip).version == 4}
# 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))
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 = OVSNeutronAgent(**agent_config)
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(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()

View File

@ -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 = [

View File

@ -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

View File

@ -0,0 +1,160 @@
# Copyright (C) 2014,2015 VA Linux Systems Japan K.K.
# Copyright (C) 2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
# 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)

View File

@ -0,0 +1,213 @@
# Copyright (C) 2014,2015 VA Linux Systems Japan K.K.
# Copyright (C) 2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
# 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)

View File

@ -0,0 +1,97 @@
# Copyright (C) 2014,2015 VA Linux Systems Japan K.K.
# Copyright (C) 2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
# 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)

View File

@ -0,0 +1,259 @@
# Copyright (C) 2014,2015 VA Linux Systems Japan K.K.
# Copyright (C) 2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
# 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)

View File

@ -0,0 +1,54 @@
# Copyright (C) 2014,2015 VA Linux Systems Japan K.K.
# Copyright (C) 2014 Fumihiko Kakuma <kakuma at valinux co jp>
# Copyright (C) 2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
# 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'

View File

@ -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