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
This commit is contained in:
Dan Wendlandt 2012-06-04 22:07:27 -07:00
parent 1cc89804a3
commit b336692147
6 changed files with 525 additions and 196 deletions

16
quantum/agent/__init__.py Normal file
View File

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

View File

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

View File

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

View File

@ -21,18 +21,16 @@
import logging import logging
from optparse import OptionParser from optparse import OptionParser
import shlex
import signal import signal
import subprocess
import sys import sys
import time import time
import sqlalchemy import sqlalchemy
from sqlalchemy.ext import sqlsoup from sqlalchemy.ext import sqlsoup
from quantum.agent.linux import ovs_lib
from quantum.plugins.openvswitch.common import config from quantum.plugins.openvswitch.common import config
logging.basicConfig() logging.basicConfig()
LOG = logging.getLogger(__name__) 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' # A class to represent a VIF (i.e., a port that has 'iface-id' and 'vif-mac'
# attributes set). # 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: class LocalVLANMapping:
def __init__(self, vlan, lsw_id, vif_ids=None): def __init__(self, vlan, lsw_id, vif_ids=None):
if vif_ids is None: if vif_ids is None:
@ -263,14 +102,14 @@ class OVSQuantumAgent(object):
def port_bound(self, port, vlan_id): def port_bound(self, port, vlan_id):
self.int_br.set_db_attribute("Port", port.port_name, self.int_br.set_db_attribute("Port", port.port_name,
"tag", str(vlan_id)) "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): def port_unbound(self, port, still_exists):
if still_exists: if still_exists:
self.int_br.clear_db_attribute("Port", port.port_name, "tag") self.int_br.clear_db_attribute("Port", port.port_name, "tag")
def setup_integration_br(self, integ_br): 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() self.int_br.remove_all_flows()
# switch all traffic using L2 learning # switch all traffic using L2 learning
self.int_br.add_flow(priority=1, actions="normal") 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", self.int_br.set_db_attribute("Port", p.port_name, "tag",
DEAD_VLAN_TAG) DEAD_VLAN_TAG)
self.int_br.add_flow(priority=2, self.int_br.add_flow(priority=2,
match="in_port=%s" % p.ofport, in_port=p.ofport,
actions="drop") actions="drop")
old_b = old_local_bindings.get(p.vif_id, None) 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) self.local_vlan_map[net_uuid] = LocalVLANMapping(lvid, lsw_id)
# outbound # outbound
self.tun_br.add_flow(priority=4, match="in_port=%s,dl_vlan=%s" % self.tun_br.add_flow(priority=4, in_port=self.patch_int_ofport,
(self.patch_int_ofport, lvid), dl_vlan=lvid,
actions="strip_vlan,set_tunnel:%s,normal" % actions="strip_vlan,set_tunnel:%s,normal" %
(lsw_id)) (lsw_id))
# inbound # 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" % actions="mod_vlan_vid:%s,output:%s" %
(lvid, self.patch_int_ofport)) (lvid, self.patch_int_ofport))
@ -451,15 +290,15 @@ class OVSQuantumTunnelAgent(object):
:param lvm: a LocalVLANMapping object that tracks (vlan, lsw_id, :param lvm: a LocalVLANMapping object that tracks (vlan, lsw_id,
vif_ids) mapping.''' vif_ids) mapping.'''
LOG.info("reclaming vlan = %s from net-id = %s" % (lvm.vlan, net_uuid)) 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(tun_id=lvm.lsw_id)
self.tun_br.delete_flows(match="dl_vlan=%s" % lvm.vlan) self.tun_br.delete_flows(dl_vlan=lvm.vlan)
del self.local_vlan_map[net_uuid] del self.local_vlan_map[net_uuid]
self.available_local_vlans.add(lvm.vlan) self.available_local_vlans.add(lvm.vlan)
def port_bound(self, port, net_uuid, lsw_id): def port_bound(self, port, net_uuid, lsw_id):
'''Bind port to 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 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. :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", self.int_br.set_db_attribute("Port", port.port_name, "tag",
str(lvm.vlan)) 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): def port_unbound(self, port, net_uuid):
'''Unbind port. '''Unbind port.
@ -478,7 +317,7 @@ class OVSQuantumTunnelAgent(object):
Removes corresponding local vlan mapping object if this is its last Removes corresponding local vlan mapping object if this is its last
VIF. VIF.
:param port: a VifPort object. :param port: a ovslib.VifPort object.
:param net_uuid: the net_uuid this port is associated with.''' :param net_uuid: the net_uuid this port is associated with.'''
if net_uuid not in self.local_vlan_map: if net_uuid not in self.local_vlan_map:
LOG.info('port_unbound() net_uuid %s not in 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): def port_dead(self, port):
'''Once a port has no binding, put it on the "dead vlan". '''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", self.int_br.set_db_attribute("Port", port.port_name, "tag",
DEAD_VLAN_TAG) DEAD_VLAN_TAG)
self.int_br.add_flow(priority=2, self.int_br.add_flow(priority=2, in_port=port.ofport, actions="drop")
match="in_port=%s" % port.ofport, actions="drop")
def setup_integration_br(self, integ_br): def setup_integration_br(self, integ_br):
'''Setup the integration bridge. '''Setup the integration bridge.
@ -509,7 +347,7 @@ class OVSQuantumTunnelAgent(object):
Create patch ports and remove all existing flows. Create patch ports and remove all existing flows.
:param integ_br: the name of the integration bridge.''' :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.int_br.delete_port("patch-tun")
self.patch_tun_ofport = self.int_br.add_patch_port("patch-tun", self.patch_tun_ofport = self.int_br.add_patch_port("patch-tun",
"patch-int") "patch-int")
@ -524,7 +362,7 @@ class OVSQuantumTunnelAgent(object):
using a patch port. using a patch port.
:param tun_br: the name of the tunnel bridge.''' :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.tun_br.reset_bridge()
self.patch_int_ofport = self.tun_br.add_patch_port("patch-int", self.patch_int_ofport = self.tun_br.add_patch_port("patch-int",
"patch-tun") "patch-tun")

View File

@ -22,7 +22,7 @@ import unittest
import mox import mox
from quantum.plugins.openvswitch.agent import ovs_quantum_agent from quantum.plugins.openvswitch.agent import ovs_quantum_agent
from quantum.agent.linux import ovs_lib
# Useful global dummy variables. # Useful global dummy variables.
NET_UUID = '3faeebfe-5d37-11e1-a64b-000c29d5f0a7' 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) LVM = ovs_quantum_agent.LocalVLANMapping(LV_ID, LS_ID, LV_IDS)
VIF_ID = '404deaec-5d37-11e1-a64b-000c29d5f0a8' VIF_ID = '404deaec-5d37-11e1-a64b-000c29d5f0a8'
VIF_MAC = '3c:09:24:1e:78:23' 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') 'switch')
@ -57,8 +57,8 @@ class TunnelTest(unittest.TestCase):
self.INT_OFPORT = 'PATCH_INT_OFPORT' self.INT_OFPORT = 'PATCH_INT_OFPORT'
self.TUN_OFPORT = 'PATCH_TUN_OFPORT' self.TUN_OFPORT = 'PATCH_TUN_OFPORT'
self.mox.StubOutClassWithMocks(ovs_quantum_agent, 'OVSBridge') self.mox.StubOutClassWithMocks(ovs_lib, 'OVSBridge')
self.mock_int_bridge = ovs_quantum_agent.OVSBridge(self.INT_BRIDGE, self.mock_int_bridge = ovs_lib.OVSBridge(self.INT_BRIDGE,
'sudo') 'sudo')
self.mock_int_bridge.delete_port('patch-tun') self.mock_int_bridge.delete_port('patch-tun')
self.mock_int_bridge.add_patch_port( 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.remove_all_flows()
self.mock_int_bridge.add_flow(priority=1, actions='normal') 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') 'sudo')
self.mock_tun_bridge.reset_bridge() self.mock_tun_bridge.reset_bridge()
self.mock_tun_bridge.add_patch_port( self.mock_tun_bridge.add_patch_port(
@ -87,14 +87,12 @@ class TunnelTest(unittest.TestCase):
self.mox.VerifyAll() self.mox.VerifyAll()
def testProvisionLocalVlan(self): def testProvisionLocalVlan(self):
match_string = 'in_port=%s,dl_vlan=%s' % (self.INT_OFPORT, LV_ID) action_string = 'strip_vlan,set_tunnel:%s,normal' % LS_ID
action_string = 'set_tunnel:%s,normal' % LS_ID self.mock_tun_bridge.add_flow(priority=4, in_port=self.INT_OFPORT,
self.mock_tun_bridge.add_flow(priority=4, match=match_string, dl_vlan=LV_ID, actions=action_string)
actions=action_string)
match_string = 'tun_id=%s' % LS_ID
action_string = 'mod_vlan_vid:%s,output:%s' % (LV_ID, self.INT_OFPORT) 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) actions=action_string)
self.mox.ReplayAll() self.mox.ReplayAll()
@ -108,11 +106,9 @@ class TunnelTest(unittest.TestCase):
self.mox.VerifyAll() self.mox.VerifyAll()
def testReclaimLocalVlan(self): def testReclaimLocalVlan(self):
match_string = 'tun_id=%s' % LVM.lsw_id self.mock_tun_bridge.delete_flows(tun_id=LVM.lsw_id)
self.mock_tun_bridge.delete_flows(match=match_string)
match_string = 'dl_vlan=%s' % LVM.vlan self.mock_tun_bridge.delete_flows(dl_vlan=LVM.vlan)
self.mock_tun_bridge.delete_flows(match=match_string)
self.mox.ReplayAll() self.mox.ReplayAll()
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE, a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
@ -128,7 +124,7 @@ class TunnelTest(unittest.TestCase):
def testPortBound(self): def testPortBound(self):
self.mock_int_bridge.set_db_attribute('Port', VIF_PORT.port_name, self.mock_int_bridge.set_db_attribute('Port', VIF_PORT.port_name,
'tag', str(LVM.vlan)) '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() self.mox.ReplayAll()
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE, a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
@ -154,8 +150,7 @@ class TunnelTest(unittest.TestCase):
self.mock_int_bridge.set_db_attribute( self.mock_int_bridge.set_db_attribute(
'Port', VIF_PORT.port_name, 'tag', ovs_quantum_agent.DEAD_VLAN_TAG) '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, in_port=VIF_PORT.ofport,
self.mock_int_bridge.add_flow(priority=2, match=match_string,
actions='drop') actions='drop')
self.mox.ReplayAll() self.mox.ReplayAll()

View File

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