From 155e7a0450a55125ed2e5ad3b580f7c480fe9adb Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Mon, 4 Jun 2012 22:07:27 -0700 Subject: [PATCH] Add common dir for shared agent code, add OVS lib. bp quantum-agent-common Adds a common directory that can be used for code shared by agents for different plugins. Also seeds this directory with an OVS library, removing that code from the openvswitch plugin itself. This code can then be leveraged by other plugins (e.g., Ryu) who have similar code. Also add a suite of mox-based tests for OVS lib. Also add more powerful OVS flow expression builder as suggested by salv-orlando, plus additional flow expression testing. Note: the expectation is that this directory will be used for much of the agent functionality that is similar to what Nova's nova/network/linux_net.py file included, such as iptables manipulation, dhcp manipulation, etc. People should be careful about changing code in this directory in a non-backward compatible way, as other plugins may be using the code as well. Change-Id: I8fd15ec6b8016e85a3f02e0d756a3fd61b1cab15 --- quantum/agent/__init__.py | 16 ++ quantum/agent/linux/__init__.py | 16 ++ quantum/agent/linux/ovs_lib.py | 214 +++++++++++++++ .../openvswitch/agent/ovs_quantum_agent.py | 194 ++------------ .../openvswitch/tests/unit/test_tunnel.py | 31 +-- quantum/tests/unit/test_ovs_lib.py | 250 ++++++++++++++++++ 6 files changed, 525 insertions(+), 196 deletions(-) create mode 100644 quantum/agent/__init__.py create mode 100644 quantum/agent/linux/__init__.py create mode 100644 quantum/agent/linux/ovs_lib.py create mode 100644 quantum/tests/unit/test_ovs_lib.py diff --git a/quantum/agent/__init__.py b/quantum/agent/__init__.py new file mode 100644 index 0000000000..304bb14ea8 --- /dev/null +++ b/quantum/agent/__init__.py @@ -0,0 +1,16 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 OpenStack LLC +# 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. diff --git a/quantum/agent/linux/__init__.py b/quantum/agent/linux/__init__.py new file mode 100644 index 0000000000..304bb14ea8 --- /dev/null +++ b/quantum/agent/linux/__init__.py @@ -0,0 +1,16 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 OpenStack LLC +# 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. diff --git a/quantum/agent/linux/ovs_lib.py b/quantum/agent/linux/ovs_lib.py new file mode 100644 index 0000000000..af099e50fa --- /dev/null +++ b/quantum/agent/linux/ovs_lib.py @@ -0,0 +1,214 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright 2011 Nicira Networks, 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. +# @author: Somik Behera, Nicira Networks, Inc. +# @author: Brad Hall, Nicira Networks, Inc. +# @author: Dan Wendlandt, Nicira Networks, Inc. +# @author: Dave Lapsley, Nicira Networks, Inc. + +import logging +import shlex +import signal +import subprocess + +LOG = logging.getLogger(__name__) + + +class VifPort: + def __init__(self, port_name, ofport, vif_id, vif_mac, switch): + self.port_name = port_name + self.ofport = ofport + self.vif_id = vif_id + self.vif_mac = vif_mac + self.switch = switch + + def __str__(self): + return ("iface-id=" + self.vif_id + ", vif_mac=" + + self.vif_mac + ", port_name=" + self.port_name + + ", ofport=" + str(self.ofport) + ", bridge_name = " + + self.switch.br_name) + + +class OVSBridge: + def __init__(self, br_name, root_helper): + self.br_name = br_name + self.root_helper = root_helper + + def run_cmd(self, args): + cmd = shlex.split(self.root_helper) + args + LOG.debug("## running command: " + " ".join(cmd)) + p = subprocess.Popen(cmd, stdout=subprocess.PIPE) + retval = p.communicate()[0] + if p.returncode == -(signal.SIGALRM): + LOG.debug("## timeout running command: " + " ".join(cmd)) + return retval + + def run_vsctl(self, args): + full_args = ["ovs-vsctl", "--timeout=2"] + args + return self.run_cmd(full_args) + + def reset_bridge(self): + self.run_vsctl(["--", "--if-exists", "del-br", self.br_name]) + self.run_vsctl(["add-br", self.br_name]) + + def delete_port(self, port_name): + self.run_vsctl(["--", "--if-exists", "del-port", self.br_name, + port_name]) + + def set_db_attribute(self, table_name, record, column, value): + args = ["set", table_name, record, "%s=%s" % (column, value)] + self.run_vsctl(args) + + def clear_db_attribute(self, table_name, record, column): + args = ["clear", table_name, record, column] + self.run_vsctl(args) + + def run_ofctl(self, cmd, args): + full_args = ["ovs-ofctl", cmd, self.br_name] + args + return self.run_cmd(full_args) + + def count_flows(self): + flow_list = self.run_ofctl("dump-flows", []).split("\n")[1:] + return len(flow_list) - 1 + + def remove_all_flows(self): + self.run_ofctl("del-flows", []) + + def get_port_ofport(self, port_name): + return self.db_get_val("Interface", port_name, "ofport") + + def _build_flow_expr_arr(self, **kwargs): + flow_expr_arr = [] + is_delete_expr = kwargs.get('delete', False) + print "kwargs = %s" % kwargs + if not is_delete_expr: + prefix = ("hard_timeout=%s,idle_timeout=%s,priority=%s" + % (kwargs.get('hard_timeout', '0'), + kwargs.get('idle_timeout', '0'), + kwargs.get('priority', '1'))) + flow_expr_arr.append(prefix) + elif 'priority' in kwargs: + raise Exception("Cannot match priority on flow deletion") + + in_port = ('in_port' in kwargs and ",in_port=%s" % + kwargs['in_port'] or '') + dl_type = ('dl_type' in kwargs and ",dl_type=%s" % + kwargs['dl_type'] or '') + dl_vlan = ('dl_vlan' in kwargs and ",dl_vlan=%s" % + kwargs['dl_vlan'] or '') + dl_src = 'dl_src' in kwargs and ",dl_src=%s" % kwargs['dl_src'] or '' + dl_dst = 'dl_dst' in kwargs and ",dl_dst=%s" % kwargs['dl_dst'] or '' + nw_src = 'nw_src' in kwargs and ",nw_src=%s" % kwargs['nw_src'] or '' + nw_dst = 'nw_dst' in kwargs and ",nw_dst=%s" % kwargs['nw_dst'] or '' + tun_id = 'tun_id' in kwargs and ",tun_id=%s" % kwargs['tun_id'] or '' + proto = 'proto' in kwargs and ",%s" % kwargs['proto'] or '' + ip = ('nw_src' in kwargs or 'nw_dst' in kwargs) and ',ip' or '' + match = (in_port + dl_type + dl_vlan + dl_src + dl_dst + + (ip or proto) + nw_src + nw_dst + tun_id) + if match: + match = match[1:] # strip leading comma + flow_expr_arr.append(match) + return flow_expr_arr + + def add_flow(self, **kwargs): + if "actions" not in kwargs: + raise Exception("must specify one or more actions") + if "priority" not in kwargs: + kwargs["priority"] = "0" + + flow_expr_arr = self._build_flow_expr_arr(**kwargs) + flow_expr_arr.append("actions=%s" % (kwargs["actions"])) + flow_str = ",".join(flow_expr_arr) + self.run_ofctl("add-flow", [flow_str]) + + def delete_flows(self, **kwargs): + kwargs['delete'] = True + flow_expr_arr = self._build_flow_expr_arr(**kwargs) + if "actions" in kwargs: + flow_expr_arr.append("actions=%s" % (kwargs["actions"])) + flow_str = ",".join(flow_expr_arr) + self.run_ofctl("del-flows", [flow_str]) + + def add_tunnel_port(self, port_name, remote_ip): + self.run_vsctl(["add-port", self.br_name, port_name]) + self.set_db_attribute("Interface", port_name, "type", "gre") + self.set_db_attribute("Interface", port_name, "options:remote_ip", + remote_ip) + self.set_db_attribute("Interface", port_name, "options:in_key", "flow") + self.set_db_attribute("Interface", port_name, "options:out_key", + "flow") + return self.get_port_ofport(port_name) + + def add_patch_port(self, local_name, remote_name): + self.run_vsctl(["add-port", self.br_name, local_name]) + self.set_db_attribute("Interface", local_name, "type", "patch") + self.set_db_attribute("Interface", local_name, "options:peer", + remote_name) + return self.get_port_ofport(local_name) + + def db_get_map(self, table, record, column): + str = self.run_vsctl(["get", table, record, column]).rstrip("\n\r") + return self.db_str_to_map(str) + + def db_get_val(self, table, record, column): + return self.run_vsctl(["get", table, record, column]).rstrip("\n\r") + + def db_str_to_map(self, full_str): + list = full_str.strip("{}").split(", ") + ret = {} + for e in list: + if e.find("=") == -1: + continue + arr = e.split("=") + ret[arr[0]] = arr[1].strip("\"") + return ret + + def get_port_name_list(self): + res = self.run_vsctl(["list-ports", self.br_name]) + return res.split("\n")[0:-1] + + def get_port_stats(self, port_name): + return self.db_get_map("Interface", port_name, "statistics") + + def get_xapi_iface_id(self, xs_vif_uuid): + return self.run_cmd([ + "xe", + "vif-param-get", + "param-name=other-config", + "param-key=nicira-iface-id", + "uuid=%s" % xs_vif_uuid, + ]).strip() + + # returns a VIF object for each VIF port + def get_vif_ports(self): + edge_ports = [] + port_names = self.get_port_name_list() + for name in port_names: + external_ids = self.db_get_map("Interface", name, "external_ids") + ofport = self.db_get_val("Interface", name, "ofport") + if "iface-id" in external_ids and "attached-mac" in external_ids: + p = VifPort(name, ofport, external_ids["iface-id"], + external_ids["attached-mac"], self) + edge_ports.append(p) + elif ("xs-vif-uuid" in external_ids and + "attached-mac" in external_ids): + # if this is a xenserver and iface-id is not automatically + # synced to OVS from XAPI, we grab it from XAPI directly + iface_id = self.get_xapi_iface_id(external_ids["xs-vif-uuid"]) + p = VifPort(name, ofport, iface_id, + external_ids["attached-mac"], self) + edge_ports.append(p) + + return edge_ports diff --git a/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py b/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py index 687953e0dd..21488f5bfc 100755 --- a/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py +++ b/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py @@ -21,18 +21,16 @@ import logging from optparse import OptionParser -import shlex import signal -import subprocess import sys import time import sqlalchemy from sqlalchemy.ext import sqlsoup +from quantum.agent.linux import ovs_lib from quantum.plugins.openvswitch.common import config - logging.basicConfig() LOG = logging.getLogger(__name__) @@ -50,165 +48,6 @@ DEFAULT_RECONNECT_INTERVAL = 2 # A class to represent a VIF (i.e., a port that has 'iface-id' and 'vif-mac' # attributes set). -class VifPort: - def __init__(self, port_name, ofport, vif_id, vif_mac, switch): - self.port_name = port_name - self.ofport = ofport - self.vif_id = vif_id - self.vif_mac = vif_mac - self.switch = switch - - def __str__(self): - return ("iface-id=" + self.vif_id + ", vif_mac=" + - self.vif_mac + ", port_name=" + self.port_name + - ", ofport=" + self.ofport + ", bridge name = " + - self.switch.br_name) - - -class OVSBridge: - def __init__(self, br_name, root_helper): - self.br_name = br_name - self.root_helper = root_helper - - def run_cmd(self, args): - cmd = shlex.split(self.root_helper) + args - LOG.debug("## running command: " + " ".join(cmd)) - p = subprocess.Popen(cmd, stdout=subprocess.PIPE) - retval = p.communicate()[0] - if p.returncode == -(signal.SIGALRM): - LOG.debug("## timeout running command: " + " ".join(cmd)) - return retval - - def run_vsctl(self, args): - full_args = ["ovs-vsctl", "--timeout=2"] + args - return self.run_cmd(full_args) - - def reset_bridge(self): - self.run_vsctl(["--", "--if-exists", "del-br", self.br_name]) - self.run_vsctl(["add-br", self.br_name]) - - def delete_port(self, port_name): - self.run_vsctl(["--", "--if-exists", "del-port", self.br_name, - port_name]) - - def set_db_attribute(self, table_name, record, column, value): - args = ["set", table_name, record, "%s=%s" % (column, value)] - self.run_vsctl(args) - - def clear_db_attribute(self, table_name, record, column): - args = ["clear", table_name, record, column] - self.run_vsctl(args) - - def run_ofctl(self, cmd, args): - full_args = ["ovs-ofctl", cmd, self.br_name] + args - return self.run_cmd(full_args) - - def count_flows(self): - flow_list = self.run_ofctl("dump-flows", []).split("\n")[1:] - return len(flow_list) - 1 - - def remove_all_flows(self): - self.run_ofctl("del-flows", []) - - def get_port_ofport(self, port_name): - return self.db_get_val("Interface", port_name, "ofport") - - def add_flow(self, **dict): - if "actions" not in dict: - raise Exception("must specify one or more actions") - if "priority" not in dict: - dict["priority"] = "0" - - flow_str = "priority=%s" % dict["priority"] - if "match" in dict: - flow_str += "," + dict["match"] - flow_str += ",actions=%s" % (dict["actions"]) - self.run_ofctl("add-flow", [flow_str]) - - def delete_flows(self, **dict): - all_args = [] - if "priority" in dict: - all_args.append("priority=%s" % dict["priority"]) - if "match" in dict: - all_args.append(dict["match"]) - if "actions" in dict: - all_args.append("actions=%s" % (dict["actions"])) - flow_str = ",".join(all_args) - self.run_ofctl("del-flows", [flow_str]) - - def add_tunnel_port(self, port_name, remote_ip): - self.run_vsctl(["add-port", self.br_name, port_name]) - self.set_db_attribute("Interface", port_name, "type", "gre") - self.set_db_attribute("Interface", port_name, "options:remote_ip", - remote_ip) - self.set_db_attribute("Interface", port_name, "options:in_key", "flow") - self.set_db_attribute("Interface", port_name, "options:out_key", - "flow") - return self.get_port_ofport(port_name) - - def add_patch_port(self, local_name, remote_name): - self.run_vsctl(["add-port", self.br_name, local_name]) - self.set_db_attribute("Interface", local_name, "type", "patch") - self.set_db_attribute("Interface", local_name, "options:peer", - remote_name) - return self.get_port_ofport(local_name) - - def db_get_map(self, table, record, column): - str = self.run_vsctl(["get", table, record, column]).rstrip("\n\r") - return self.db_str_to_map(str) - - def db_get_val(self, table, record, column): - return self.run_vsctl(["get", table, record, column]).rstrip("\n\r") - - def db_str_to_map(self, full_str): - list = full_str.strip("{}").split(", ") - ret = {} - for e in list: - if e.find("=") == -1: - continue - arr = e.split("=") - ret[arr[0]] = arr[1].strip("\"") - return ret - - def get_port_name_list(self): - res = self.run_vsctl(["list-ports", self.br_name]) - return res.split("\n")[0:-1] - - def get_port_stats(self, port_name): - return self.db_get_map("Interface", port_name, "statistics") - - def get_xapi_iface_id(self, xs_vif_uuid): - return self.run_cmd([ - "xe", - "vif-param-get", - "param-name=other-config", - "param-key=nicira-iface-id", - "uuid=%s" % xs_vif_uuid, - ]).strip() - - # returns a VIF object for each VIF port - def get_vif_ports(self): - edge_ports = [] - port_names = self.get_port_name_list() - for name in port_names: - external_ids = self.db_get_map("Interface", name, "external_ids") - ofport = self.db_get_val("Interface", name, "ofport") - if "iface-id" in external_ids and "attached-mac" in external_ids: - p = VifPort(name, ofport, external_ids["iface-id"], - external_ids["attached-mac"], self) - edge_ports.append(p) - elif ("xs-vif-uuid" in external_ids and - "attached-mac" in external_ids): - # if this is a xenserver and iface-id is not automatically - # synced to OVS from XAPI, we grab it from XAPI directly - iface_id = self.get_xapi_iface_id(external_ids["xs-vif-uuid"]) - p = VifPort(name, ofport, iface_id, - external_ids["attached-mac"], self) - edge_ports.append(p) - - return edge_ports - - class LocalVLANMapping: def __init__(self, vlan, lsw_id, vif_ids=None): if vif_ids is None: @@ -263,14 +102,14 @@ class OVSQuantumAgent(object): def port_bound(self, port, vlan_id): self.int_br.set_db_attribute("Port", port.port_name, "tag", str(vlan_id)) - self.int_br.delete_flows(match="in_port=%s" % port.ofport) + self.int_br.delete_flows(in_port=port.ofport) def port_unbound(self, port, still_exists): if still_exists: self.int_br.clear_db_attribute("Port", port.port_name, "tag") def setup_integration_br(self, integ_br): - self.int_br = OVSBridge(integ_br, self.root_helper) + self.int_br = ovs_lib.OVSBridge(integ_br, self.root_helper) self.int_br.remove_all_flows() # switch all traffic using L2 learning self.int_br.add_flow(priority=1, actions="normal") @@ -328,7 +167,7 @@ class OVSQuantumAgent(object): self.int_br.set_db_attribute("Port", p.port_name, "tag", DEAD_VLAN_TAG) self.int_br.add_flow(priority=2, - match="in_port=%s" % p.ofport, + in_port=p.ofport, actions="drop") old_b = old_local_bindings.get(p.vif_id, None) @@ -435,12 +274,12 @@ class OVSQuantumTunnelAgent(object): self.local_vlan_map[net_uuid] = LocalVLANMapping(lvid, lsw_id) # outbound - self.tun_br.add_flow(priority=4, match="in_port=%s,dl_vlan=%s" % - (self.patch_int_ofport, lvid), + self.tun_br.add_flow(priority=4, in_port=self.patch_int_ofport, + dl_vlan=lvid, actions="strip_vlan,set_tunnel:%s,normal" % (lsw_id)) # inbound - self.tun_br.add_flow(priority=3, match="tun_id=%s" % lsw_id, + self.tun_br.add_flow(priority=3, tun_id=lsw_id, actions="mod_vlan_vid:%s,output:%s" % (lvid, self.patch_int_ofport)) @@ -451,15 +290,15 @@ class OVSQuantumTunnelAgent(object): :param lvm: a LocalVLANMapping object that tracks (vlan, lsw_id, vif_ids) mapping.''' LOG.info("reclaming vlan = %s from net-id = %s" % (lvm.vlan, net_uuid)) - self.tun_br.delete_flows(match="tun_id=%s" % lvm.lsw_id) - self.tun_br.delete_flows(match="dl_vlan=%s" % lvm.vlan) + self.tun_br.delete_flows(tun_id=lvm.lsw_id) + self.tun_br.delete_flows(dl_vlan=lvm.vlan) del self.local_vlan_map[net_uuid] self.available_local_vlans.add(lvm.vlan) def port_bound(self, port, net_uuid, lsw_id): '''Bind port to net_uuid/lsw_id. - :param port: a VifPort object. + :param port: a ovslib.VifPort object. :param net_uuid: the net_uuid this port is to be associated with. :param lsw_id: the logical switch this port is to be associated with. ''' @@ -470,7 +309,7 @@ class OVSQuantumTunnelAgent(object): self.int_br.set_db_attribute("Port", port.port_name, "tag", str(lvm.vlan)) - self.int_br.delete_flows(match="in_port=%s" % port.ofport) + self.int_br.delete_flows(in_port=port.ofport) def port_unbound(self, port, net_uuid): '''Unbind port. @@ -478,7 +317,7 @@ class OVSQuantumTunnelAgent(object): Removes corresponding local vlan mapping object if this is its last VIF. - :param port: a VifPort object. + :param port: a ovslib.VifPort object. :param net_uuid: the net_uuid this port is associated with.''' if net_uuid not in self.local_vlan_map: LOG.info('port_unbound() net_uuid %s not in local_vlan_map' % @@ -497,11 +336,10 @@ class OVSQuantumTunnelAgent(object): def port_dead(self, port): '''Once a port has no binding, put it on the "dead vlan". - :param port: a VifPort object.''' + :param port: a ovs_lib.VifPort object.''' self.int_br.set_db_attribute("Port", port.port_name, "tag", DEAD_VLAN_TAG) - self.int_br.add_flow(priority=2, - match="in_port=%s" % port.ofport, actions="drop") + self.int_br.add_flow(priority=2, in_port=port.ofport, actions="drop") def setup_integration_br(self, integ_br): '''Setup the integration bridge. @@ -509,7 +347,7 @@ class OVSQuantumTunnelAgent(object): Create patch ports and remove all existing flows. :param integ_br: the name of the integration bridge.''' - self.int_br = OVSBridge(integ_br, self.root_helper) + self.int_br = ovs_lib.OVSBridge(integ_br, self.root_helper) self.int_br.delete_port("patch-tun") self.patch_tun_ofport = self.int_br.add_patch_port("patch-tun", "patch-int") @@ -524,7 +362,7 @@ class OVSQuantumTunnelAgent(object): using a patch port. :param tun_br: the name of the tunnel bridge.''' - self.tun_br = OVSBridge(tun_br, self.root_helper) + self.tun_br = ovs_lib.OVSBridge(tun_br, self.root_helper) self.tun_br.reset_bridge() self.patch_int_ofport = self.tun_br.add_patch_port("patch-int", "patch-tun") diff --git a/quantum/plugins/openvswitch/tests/unit/test_tunnel.py b/quantum/plugins/openvswitch/tests/unit/test_tunnel.py index e1d70c47fc..b06a8fcdf0 100644 --- a/quantum/plugins/openvswitch/tests/unit/test_tunnel.py +++ b/quantum/plugins/openvswitch/tests/unit/test_tunnel.py @@ -22,7 +22,7 @@ import unittest import mox from quantum.plugins.openvswitch.agent import ovs_quantum_agent - +from quantum.agent.linux import ovs_lib # Useful global dummy variables. NET_UUID = '3faeebfe-5d37-11e1-a64b-000c29d5f0a7' @@ -32,7 +32,7 @@ LV_IDS = [42, 43] LVM = ovs_quantum_agent.LocalVLANMapping(LV_ID, LS_ID, LV_IDS) VIF_ID = '404deaec-5d37-11e1-a64b-000c29d5f0a8' VIF_MAC = '3c:09:24:1e:78:23' -VIF_PORT = ovs_quantum_agent.VifPort('port', 'ofport', VIF_ID, VIF_MAC, +VIF_PORT = ovs_lib.VifPort('port', 'ofport', VIF_ID, VIF_MAC, 'switch') @@ -57,8 +57,8 @@ class TunnelTest(unittest.TestCase): self.INT_OFPORT = 'PATCH_INT_OFPORT' self.TUN_OFPORT = 'PATCH_TUN_OFPORT' - self.mox.StubOutClassWithMocks(ovs_quantum_agent, 'OVSBridge') - self.mock_int_bridge = ovs_quantum_agent.OVSBridge(self.INT_BRIDGE, + self.mox.StubOutClassWithMocks(ovs_lib, 'OVSBridge') + self.mock_int_bridge = ovs_lib.OVSBridge(self.INT_BRIDGE, 'sudo') self.mock_int_bridge.delete_port('patch-tun') self.mock_int_bridge.add_patch_port( @@ -66,7 +66,7 @@ class TunnelTest(unittest.TestCase): self.mock_int_bridge.remove_all_flows() self.mock_int_bridge.add_flow(priority=1, actions='normal') - self.mock_tun_bridge = ovs_quantum_agent.OVSBridge(self.TUN_BRIDGE, + self.mock_tun_bridge = ovs_lib.OVSBridge(self.TUN_BRIDGE, 'sudo') self.mock_tun_bridge.reset_bridge() self.mock_tun_bridge.add_patch_port( @@ -87,14 +87,12 @@ class TunnelTest(unittest.TestCase): self.mox.VerifyAll() def testProvisionLocalVlan(self): - match_string = 'in_port=%s,dl_vlan=%s' % (self.INT_OFPORT, LV_ID) - action_string = 'set_tunnel:%s,normal' % LS_ID - self.mock_tun_bridge.add_flow(priority=4, match=match_string, - actions=action_string) + action_string = 'strip_vlan,set_tunnel:%s,normal' % LS_ID + self.mock_tun_bridge.add_flow(priority=4, in_port=self.INT_OFPORT, + dl_vlan=LV_ID, actions=action_string) - match_string = 'tun_id=%s' % LS_ID action_string = 'mod_vlan_vid:%s,output:%s' % (LV_ID, self.INT_OFPORT) - self.mock_tun_bridge.add_flow(priority=3, match=match_string, + self.mock_tun_bridge.add_flow(priority=3, tun_id=LS_ID, actions=action_string) self.mox.ReplayAll() @@ -108,11 +106,9 @@ class TunnelTest(unittest.TestCase): self.mox.VerifyAll() def testReclaimLocalVlan(self): - match_string = 'tun_id=%s' % LVM.lsw_id - self.mock_tun_bridge.delete_flows(match=match_string) + self.mock_tun_bridge.delete_flows(tun_id=LVM.lsw_id) - match_string = 'dl_vlan=%s' % LVM.vlan - self.mock_tun_bridge.delete_flows(match=match_string) + self.mock_tun_bridge.delete_flows(dl_vlan=LVM.vlan) self.mox.ReplayAll() a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE, @@ -128,7 +124,7 @@ class TunnelTest(unittest.TestCase): def testPortBound(self): self.mock_int_bridge.set_db_attribute('Port', VIF_PORT.port_name, 'tag', str(LVM.vlan)) - self.mock_int_bridge.delete_flows(match='in_port=%s' % VIF_PORT.ofport) + self.mock_int_bridge.delete_flows(in_port=VIF_PORT.ofport) self.mox.ReplayAll() a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE, @@ -154,8 +150,7 @@ class TunnelTest(unittest.TestCase): self.mock_int_bridge.set_db_attribute( 'Port', VIF_PORT.port_name, 'tag', ovs_quantum_agent.DEAD_VLAN_TAG) - match_string = 'in_port=%s' % VIF_PORT.ofport - self.mock_int_bridge.add_flow(priority=2, match=match_string, + self.mock_int_bridge.add_flow(priority=2, in_port=VIF_PORT.ofport, actions='drop') self.mox.ReplayAll() diff --git a/quantum/tests/unit/test_ovs_lib.py b/quantum/tests/unit/test_ovs_lib.py new file mode 100644 index 0000000000..e9e1ebbb65 --- /dev/null +++ b/quantum/tests/unit/test_ovs_lib.py @@ -0,0 +1,250 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012, Nicira, Inc. +# +# 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. +# @author: Dan Wendlandt, Nicira, Inc. + +import unittest +import uuid + +import mox + +from quantum.agent.linux import ovs_lib + + +class OVS_Lib_Test(unittest.TestCase): + """ + A test suite to excercise the OVS libraries shared by Quantum agents. + Note: these tests do not actually execute ovs-* utilities, and thus + can run on any system. That does, however, limit their scope. + """ + + def setUp(self): + self.BR_NAME = "br-int" + self.TO = "--timeout=2" + + self.mox = mox.Mox() + self.br = ovs_lib.OVSBridge(self.BR_NAME, 'sudo') + self.mox.StubOutWithMock(self.br, "run_cmd") + + def tearDown(self): + self.mox.UnsetStubs() + + def test_vifport(self): + """create and stringify vif port, confirm no exceptions""" + self.mox.ReplayAll() + + pname = "vif1.0" + ofport = 5 + vif_id = str(uuid.uuid4()) + mac = "ca:fe:de:ad:be:ef" + + # test __init__ + port = ovs_lib.VifPort(pname, ofport, vif_id, mac, self.br) + self.assertEqual(port.port_name, pname) + self.assertEqual(port.ofport, ofport) + self.assertEqual(port.vif_id, vif_id) + self.assertEqual(port.vif_mac, mac) + self.assertEqual(port.switch.br_name, self.BR_NAME) + + # test __str__ + foo = str(port) + + self.mox.VerifyAll() + + def test_reset_bridge(self): + self.br.run_cmd(["ovs-vsctl", self.TO, "--", + "--if-exists", "del-br", self.BR_NAME]) + self.br.run_cmd(["ovs-vsctl", self.TO, "add-br", self.BR_NAME]) + self.mox.ReplayAll() + + self.br.reset_bridge() + self.mox.VerifyAll() + + def test_delete_port(self): + pname = "tap5" + self.br.run_cmd(["ovs-vsctl", self.TO, "--", "--if-exists", + "del-port", self.BR_NAME, pname]) + + self.mox.ReplayAll() + self.br.delete_port(pname) + self.mox.VerifyAll() + + def test_add_flow(self): + ofport = "99" + vid = 4000 + lsw_id = 18 + self.br.run_cmd(["ovs-ofctl", "add-flow", self.BR_NAME, + "hard_timeout=0,idle_timeout=0," + "priority=2,dl_src=ca:fe:de:ad:be:ef" + ",actions=strip_vlan,output:0"]) + self.br.run_cmd(["ovs-ofctl", "add-flow", self.BR_NAME, + "hard_timeout=0,idle_timeout=0," + "priority=1,actions=normal"]) + self.br.run_cmd(["ovs-ofctl", "add-flow", self.BR_NAME, + "hard_timeout=0,idle_timeout=0," + "priority=2,actions=drop"]) + self.br.run_cmd(["ovs-ofctl", "add-flow", self.BR_NAME, + "hard_timeout=0,idle_timeout=0," + "priority=2,in_port=%s,actions=drop" % ofport]) + self.br.run_cmd(["ovs-ofctl", "add-flow", self.BR_NAME, + "hard_timeout=0,idle_timeout=0," + "priority=4,in_port=%s,dl_vlan=%s," + "actions=strip_vlan,set_tunnel:%s,normal" + % (ofport, vid, lsw_id)]) + self.br.run_cmd(["ovs-ofctl", "add-flow", self.BR_NAME, + "hard_timeout=0,idle_timeout=0," + "priority=3,tun_id=%s,actions=" + "mod_vlan_vid:%s,output:%s" + % (lsw_id, vid, ofport)]) + self.mox.ReplayAll() + + self.br.add_flow(priority=2, dl_src="ca:fe:de:ad:be:ef", + actions="strip_vlan,output:0") + self.br.add_flow(priority=1, actions="normal") + self.br.add_flow(priority=2, actions="drop") + self.br.add_flow(priority=2, in_port=ofport, actions="drop") + + self.br.add_flow(priority=4, in_port=ofport, dl_vlan=vid, + actions="strip_vlan,set_tunnel:%s,normal" % + (lsw_id)) + self.br.add_flow(priority=3, tun_id=lsw_id, + actions="mod_vlan_vid:%s,output:%s" % + (vid, ofport)) + self.mox.VerifyAll() + + def test_get_port_ofport(self): + pname = "tap99" + ofport = "6" + self.br.run_cmd(["ovs-vsctl", self.TO, "get", "Interface", + pname, "ofport"]).AndReturn(ofport) + self.mox.ReplayAll() + + self.assertEqual(self.br.get_port_ofport(pname), ofport) + self.mox.VerifyAll() + + def test_count_flows(self): + self.br.run_cmd(["ovs-ofctl", "dump-flows", self.BR_NAME]).\ + AndReturn("ignore\nflow-1\n") + self.mox.ReplayAll() + + # counts the number of flows as total lines of output - 2 + self.assertEqual(self.br.count_flows(), 1) + self.mox.VerifyAll() + + def test_delete_flow(self): + ofport = "5" + lsw_id = 40 + vid = 39 + self.br.run_cmd(["ovs-ofctl", "del-flows", self.BR_NAME, + "in_port=" + ofport]) + self.br.run_cmd(["ovs-ofctl", "del-flows", self.BR_NAME, + "tun_id=%s" % lsw_id]) + self.br.run_cmd(["ovs-ofctl", "del-flows", self.BR_NAME, + "dl_vlan=%s" % vid]) + self.mox.ReplayAll() + + self.br.delete_flows(in_port=ofport) + self.br.delete_flows(tun_id=lsw_id) + self.br.delete_flows(dl_vlan=vid) + self.mox.VerifyAll() + + def test_add_tunnel_port(self): + pname = "tap99" + ip = "9.9.9.9" + ofport = "6" + + self.br.run_cmd(["ovs-vsctl", self.TO, "add-port", + self.BR_NAME, pname]) + self.br.run_cmd(["ovs-vsctl", self.TO, "set", "Interface", + pname, "type=gre"]) + self.br.run_cmd(["ovs-vsctl", self.TO, "set", "Interface", + pname, "options:remote_ip=" + ip]) + self.br.run_cmd(["ovs-vsctl", self.TO, "set", "Interface", + pname, "options:in_key=flow"]) + self.br.run_cmd(["ovs-vsctl", self.TO, "set", "Interface", + pname, "options:out_key=flow"]) + self.br.run_cmd(["ovs-vsctl", self.TO, "get", "Interface", + pname, "ofport"]).AndReturn(ofport) + self.mox.ReplayAll() + + self.assertEqual(self.br.add_tunnel_port(pname, ip), ofport) + self.mox.VerifyAll() + + def test_add_patch_port(self): + pname = "tap99" + peer = "bar10" + ofport = "6" + + self.br.run_cmd(["ovs-vsctl", self.TO, "add-port", + self.BR_NAME, pname]) + self.br.run_cmd(["ovs-vsctl", self.TO, "set", "Interface", + pname, "type=patch"]) + self.br.run_cmd(["ovs-vsctl", self.TO, "set", "Interface", + pname, "options:peer=" + peer]) + self.br.run_cmd(["ovs-vsctl", self.TO, "get", "Interface", + pname, "ofport"]).AndReturn(ofport) + self.mox.ReplayAll() + + self.assertEqual(self.br.add_patch_port(pname, peer), ofport) + self.mox.VerifyAll() + + def _test_get_vif_ports(self, is_xen=False): + pname = "tap99" + ofport = "6" + vif_id = str(uuid.uuid4()) + mac = "ca:fe:de:ad:be:ef" + + self.br.run_cmd(["ovs-vsctl", self.TO, "list-ports", self.BR_NAME]).\ + AndReturn("%s\n" % pname) + + if is_xen: + external_ids = ('{xs-vif-uuid="%s", attached-mac="%s"}' + % (vif_id, mac)) + else: + external_ids = ('{iface-id="%s", attached-mac="%s"}' + % (vif_id, mac)) + + self.br.run_cmd(["ovs-vsctl", self.TO, "get", "Interface", + pname, "external_ids"]).AndReturn(external_ids) + self.br.run_cmd(["ovs-vsctl", self.TO, "get", "Interface", + pname, "ofport"]).AndReturn(ofport) + if is_xen: + self.br.run_cmd(["xe", "vif-param-get", "param-name=other-config", + "param-key=nicira-iface-id", "uuid=" + vif_id]).\ + AndReturn(vif_id) + self.mox.ReplayAll() + + ports = self.br.get_vif_ports() + self.assertEqual(1, len(ports)) + self.assertEqual(ports[0].port_name, pname) + self.assertEqual(ports[0].ofport, ofport) + self.assertEqual(ports[0].vif_id, vif_id) + self.assertEqual(ports[0].vif_mac, mac) + self.assertEqual(ports[0].switch.br_name, self.BR_NAME) + self.mox.VerifyAll() + + def test_get_vif_ports_nonxen(self): + self._test_get_vif_ports(False) + + def test_get_vif_ports_xen(self): + self._test_get_vif_ports(True) + + def test_clear_db_attribute(self): + pname = "tap77" + self.br.run_cmd(["ovs-vsctl", self.TO, "clear", "Port", + pname, "tag"]) + self.mox.ReplayAll() + self.br.clear_db_attribute("Port", pname, "tag") + self.mox.VerifyAll()