diff --git a/quantum/agent/__init__.py b/quantum/agent/__init__.py new file mode 100644 index 00000000000..304bb14ea8a --- /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 00000000000..304bb14ea8a --- /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 00000000000..af099e50fa8 --- /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 687953e0dde..21488f5bfc9 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 e1d70c47fcd..b06a8fcdf0c 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 00000000000..e9e1ebbb65b --- /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()