blueprint quantum-ovs-tunnel-agent

Enhance existing Quantum OVS Plugin with a tunneling agent that
enables Hypervisors to be connected via GRE tunnels. The new agent
can be enabled/disabled via configuration file and provides backwards
compatibility with existing non-tunneling OVS Agent.

Change-Id: Id3b79430726b162fcb84f99df152d88a5766328f
This commit is contained in:
Dave Lapsley 2012-02-21 02:41:14 -05:00
parent f2a28b36fe
commit 2b4016b8e6
7 changed files with 637 additions and 13 deletions

View File

@ -1,7 +1,53 @@
[DATABASE]
# This line MUST be changed to actually run the plugin.
# Example: sql_connection = mysql://root:nova@127.0.0.1:3306/ovs_quantum
sql_connection = sqlite://
# Replace 127.0.0.1 above with the IP address of the database used by the
# main quantum server. (Leave it as is if the database runs on this host.)
sql_connection=sqlite://
[OVS]
# This enables the new OVSQuantumTunnelAgent which enables tunneling
# between hybervisors. Leave it set to False or omit for legacy behavior.
enable-tunneling = False
# Do not change this parameter unless you have a good reason to.
# This is the name of the OVS integration bridge. There is one per hypervisor.
# The integration bridge acts as a virtual "patch port". All VM VIFs are
# attached to this bridge and then "patched" according to their network
# connectivity.
integration-bridge = br-int
# Uncomment this line if enable-tunneling is True above.
# In most cases, the default value should be fine.
# tunnel-bridge = br-tun
# Uncomment this line if enable-tunneling is True above.
# This file contains a list of IP addresses (one per line) that point to
# hypervisors to which tunnels should be connected. It is best to use
# an absolute path to this file.
# remote-ip-file = /opt/stack/remote-ips.txt
# Uncomment this line if enable-tunneling is True above.
# Set local-ip to be the local IP address of this hypervisor.
# local-ip = 10.0.0.3
#-----------------------------------------------------------------------------
# Sample Configurations.
#-----------------------------------------------------------------------------
#
# 1. Without tunneling.
# [DATABASE]
# sql_connection = mysql://root:nova@127.0.0.1:3306/ovs_quantum
# [OVS]
# enable-tunneling = False
# integration-bridge = br-int
#
# 2. With tunneling.
# [DATABASE]
# sql_connection = mysql://root:nova@127.0.0.1:3306/ovs_quantum
# [OVS]
# enable-tunneling = True
# integration-bridge = br-int
# tunnel-bridge = br-tun
# remote-ip-file = /opt/stack/remote-ips.txt
# local-ip = 10.0.0.3

View File

@ -17,6 +17,7 @@
# @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 ConfigParser
import logging as LOG
@ -29,9 +30,15 @@ from sqlalchemy.ext.sqlsoup import SqlSoup
from subprocess import *
# Global constants.
OP_STATUS_UP = "UP"
OP_STATUS_DOWN = "DOWN"
# A placeholder for dead vlans.
DEAD_VLAN_TAG = "4095"
REFRESH_INTERVAL = 2
# A class to represent a VIF (i.e., a port that has 'iface-id' and 'vif-mac'
# attributes set).
@ -114,6 +121,23 @@ class OVSBridge:
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)
@ -169,14 +193,26 @@ class OVSBridge:
return edge_ports
class OVSQuantumAgent:
class LocalVLANMapping:
def __init__(self, vlan, lsw_id, vif_ids=None):
if vif_ids is None:
vif_ids = []
self.vlan = vlan
self.lsw_id = lsw_id
self.vif_ids = vif_ids
def __str__(self):
return "lv-id = %s ls-id = %s" % (self.vlan, self.lsw_id)
class OVSQuantumAgent(object):
def __init__(self, integ_br):
self.setup_integration_br(integ_br)
def port_bound(self, port, vlan_id):
self.int_br.set_db_attribute("Port", port.port_name, "tag",
str(vlan_id))
str(vlan_id))
self.int_br.delete_flows(match="in_port=%s" % port.ofport)
def port_unbound(self, port, still_exists):
@ -223,7 +259,7 @@ class OVSQuantumAgent:
else:
# no binding, put him on the 'dead vlan'
self.int_br.set_db_attribute("Port", p.port_name, "tag",
"4095")
DEAD_VLAN_TAG)
self.int_br.add_flow(priority=2,
match="in_port=%s" % p.ofport, actions="drop")
@ -241,14 +277,14 @@ class OVSQuantumAgent:
# If we don't have a binding we have to stick it on
# the dead vlan
net_id = all_bindings[p.vif_id].network_id
vlan_id = vlan_bindings.get(net_id, "4095")
vlan_id = vlan_bindings.get(net_id, DEAD_VLAN_TAG)
self.port_bound(p, vlan_id)
if p.vif_id in all_bindings:
all_bindings[p.vif_id].op_status = OP_STATUS_UP
LOG.info("Adding binding to net-id = %s " \
"for %s on vlan %s" % (new_b, str(p), vlan_id))
for vif_id in old_vif_ports.keys():
for vif_id in old_vif_ports:
if vif_id not in new_vif_ports:
LOG.info("Port Disappeared: %s" % vif_id)
if vif_id in old_local_bindings:
@ -260,7 +296,295 @@ class OVSQuantumAgent:
old_vif_ports = new_vif_ports
old_local_bindings = new_local_bindings
db.commit()
time.sleep(2)
time.sleep(REFRESH_INTERVAL)
class OVSQuantumTunnelAgent(object):
'''Implements OVS-based tunneling.
Two local bridges are created: an integration bridge (defaults to 'br-int')
and a tunneling bridge (defaults to 'br-tun').
All VM VIFs are plugged into the integration bridge. VMs for a given tenant
share a common "local" VLAN (i.e. not propagated externally). The VLAN id
of this local VLAN is mapped to a Logical Switch (LS) identifier and is
used to differentiate tenant traffic on inter-HV tunnels.
A mesh of tunnels is created to other Hypervisors in the cloud. These
tunnels originate and terminate on the tunneling bridge of each hypervisor.
Port patching is done to connect local VLANs on the integration bridge
to inter-hypervisor tunnels on the tunnel bridge.
'''
# Lower bound on available vlans.
MIN_VLAN_TAG = 1
# Upper bound on available vlans.
MAX_VLAN_TAG = 4094
def __init__(self, integ_br, tun_br, remote_ip_file, local_ip):
'''Constructor.
:param integ_br: name of the integration bridge.
:param tun_br: name of the tunnel bridge.
:param remote_ip_file: name of file containing list of hypervisor IPs.
:param local_ip: local IP address of this hypervisor.'''
self.available_local_vlans = set(
xrange(OVSQuantumTunnelAgent.MIN_VLAN_TAG,
OVSQuantumTunnelAgent.MAX_VLAN_TAG))
self.setup_integration_br(integ_br)
self.local_vlan_map = {}
self.setup_tunnel_br(tun_br, remote_ip_file, local_ip)
def provision_local_vlan(self, net_uuid, lsw_id):
'''Provisions a local VLAN.
:param net_uuid: the uuid of the network associated with this vlan.
:param lsw_id: the logical switch id of this vlan.'''
if not self.available_local_vlans:
raise Exception("No local VLANs available for ls-id = %s" % lsw_id)
lvid = self.available_local_vlans.pop()
LOG.info("Assigning %s as local vlan for net-id=%s" % (lvid, net_uuid))
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),
actions="set_tunnel:%s,normal" % (lsw_id))
# inbound
self.tun_br.add_flow(priority=3, match="tun_id=%s" % lsw_id,
actions="mod_vlan_vid:%s,output:%s" % (lvid,
self.patch_int_ofport))
def reclaim_local_vlan(self, net_uuid, lvm):
'''Reclaim a local VLAN.
:param net_uuid: the network uuid associated with this vlan.
: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)
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 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.
'''
if net_uuid not in self.local_vlan_map:
self.provision_local_vlan(net_uuid, lsw_id)
lvm = self.local_vlan_map[net_uuid]
lvm.vif_ids.append(port.vif_id)
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)
def port_unbound(self, port, net_uuid):
'''Unbind port.
Removes corresponding local vlan mapping object if this is its last
VIF.
:param port: a 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'
% net_uuid)
return
lvm = self.local_vlan_map[net_uuid]
if port.vif_id in lvm.vif_ids:
lvm.vif_ids.remove(port.vif_id)
else:
LOG.info('port_unbound: vid_id %s not in list' % port.vif_id)
if not lvm.vif_ids:
self.reclaim_local_vlan(net_uuid, lvm)
def port_dead(self, port):
'''Once a port has no binding, put it on the "dead vlan".
:param port: a 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")
def setup_integration_br(self, integ_br):
'''Setup the integration bridge.
Create patch ports and remove all existing flows.
:param integ_br: the name of the integration bridge.'''
self.int_br = OVSBridge(integ_br)
self.int_br.delete_port("patch-tun")
self.patch_tun_ofport = self.int_br.add_patch_port("patch-tun",
"patch-int")
self.int_br.remove_all_flows()
# switch all traffic using L2 learning
self.int_br.add_flow(priority=1, actions="normal")
def setup_tunnel_br(self, tun_br, remote_ip_file, local_ip):
'''Setup the tunnel bridge.
Reads in list of IP addresses. Creates GRE tunnels to each of these
addresses and then clears out existing flows. local_ip is the address
of the local node. A tunnel is not created to this IP address.
:param tun_br: the name of the tunnel bridge.
:param remote_ip_file: path to file that contains list of destination
IP addresses.
:param local_ip: the ip address of this node.'''
self.tun_br = OVSBridge(tun_br)
self.tun_br.reset_bridge()
self.patch_int_ofport = self.tun_br.add_patch_port("patch-int",
"patch-tun")
try:
with open(remote_ip_file, 'r') as f:
remote_ip_list = f.readlines()
clean_ips = (x.rstrip() for x in remote_ip_list)
tunnel_ips = (x for x in clean_ips if x != local_ip and x)
for i, remote_ip in enumerate(tunnel_ips):
self.tun_br.add_tunnel_port("gre-" + str(i), remote_ip)
except Exception, e:
LOG.error("Error configuring tunnels: '%s' %s"
% (remote_ip_file, str(e)))
raise
self.tun_br.remove_all_flows()
# default drop
self.tun_br.add_flow(priority=1, actions="drop")
def get_db_port_bindings(self, db):
'''Get database port bindings from central Quantum database.
The central quantum database 'ovs_quantum' resides on the openstack
mysql server.
:returns: a dictionary containing port bindings.'''
ports = []
try:
ports = db.ports.all()
except Exception, e:
LOG.info("Exception accessing db.ports: %s" % e)
return dict([(port.interface_id, port) for port in ports])
def get_db_vlan_bindings(self, db):
'''Get database vlan bindings from central Quantum database.
The central quantum database 'ovs_quantum' resides on the openstack
mysql server.
:returns: a dictionary containing vlan bindings.'''
lsw_id_binds = []
try:
lsw_id_binds.extend(db.vlan_bindings.all())
except Exception, e:
LOG.info("Exception accessing db.vlan_bindings: %s" % e)
return dict([(bind.network_id, bind.vlan_id)
for bind in lsw_id_binds])
def daemon_loop(self, db):
'''Main processing loop (not currently used).
:param db: reference to database layer.
'''
old_local_bindings = {}
old_vif_ports = {}
while True:
# Get bindings from db.
all_bindings = self.get_db_port_bindings(db)
all_bindings_vif_port_ids = set(all_bindings.keys())
lsw_id_bindings = self.get_db_vlan_bindings(db)
# Get bindings from OVS bridge.
vif_ports = self.int_br.get_vif_ports()
new_vif_ports = dict([(p.vif_id, p) for p in vif_ports])
new_vif_ports_ids = set(new_vif_ports.keys())
old_vif_ports_ids = set(old_vif_ports.keys())
dead_vif_ports_ids = new_vif_ports_ids - all_bindings_vif_port_ids
dead_vif_ports = [new_vif_ports[p] for p in dead_vif_ports_ids]
disappeared_vif_ports_ids = old_vif_ports_ids - new_vif_ports_ids
new_local_bindings_ids = all_bindings_vif_port_ids.intersection(
new_vif_ports_ids)
new_local_bindings = dict([(p, all_bindings.get(p))
for p in new_vif_ports_ids])
new_bindings = set((p, old_local_bindings.get(p),
new_local_bindings.get(p)) for p in new_vif_ports_ids)
changed_bindings = set([b for b in new_bindings
if b[2] != b[1]])
LOG.debug('all_bindings: %s' % all_bindings)
LOG.debug('lsw_id_bindings: %s' % lsw_id_bindings)
LOG.debug('old_vif_ports_ids: %s' % old_vif_ports_ids)
LOG.debug('dead_vif_ports_ids: %s' % dead_vif_ports_ids)
LOG.debug('old_vif_ports_ids: %s' % old_vif_ports_ids)
LOG.debug('new_local_bindings_ids: %s' % new_local_bindings_ids)
LOG.debug('new_local_bindings: %s' % new_local_bindings)
LOG.debug('new_bindings: %s' % new_bindings)
LOG.debug('changed_bindings: %s' % changed_bindings)
# Take action.
for p in dead_vif_ports:
LOG.info("No quantum binding for port " + str(p)
+ "putting on dead vlan")
self.port_dead(p)
for b in changed_bindings:
port_id, old_port, new_port = b
p = new_vif_ports[port_id]
if old_port:
old_net_uuid = old_port.network_id
LOG.info("Removing binding to net-id = " +
old_net_uuid + " for " + str(p)
+ " added to dead vlan")
self.port_unbound(p, old_net_uuid)
if not new_port:
self.port_dead(p)
if new_port:
new_net_uuid = new_port.network_id
if new_net_uuid not in lsw_id_bindings:
LOG.warn("No ls-id binding found for net-id '%s'" %
new_net_uuid)
continue
lsw_id = lsw_id_bindings[new_net_uuid]
try:
self.port_bound(p, new_net_uuid, lsw_id)
LOG.info("Port " + str(p) + " on net-id = "
+ new_net_uuid + " bound to " +
str(self.local_vlan_map[new_net_uuid]))
except Exception, e:
LOG.info("Unable to bind Port " + str(p) +
" on netid = " + new_net_uuid + " to "
+ str(self.local_vlan_map[new_net_uuid]))
for vif_id in disappeared_vif_ports_ids:
LOG.info("Port Disappeared: " + vif_id)
old_port = old_local_bindings.get(vif_id)
if old_port:
try:
self.port_unbound(old_vif_ports[vif_id],
old_port.network_id)
except Exception:
LOG.info("Unable to unbind Port " + str(p) +
" on net-id = " + old_port.network_uuid)
old_vif_ports = new_vif_ports
old_local_bindings = new_local_bindings
time.sleep(REFRESH_INTERVAL)
def main():
@ -285,17 +609,67 @@ def main():
try:
config.read(config_file)
except Exception, e:
LOG.error("Unable to parse config file \"%s\": %s" % (config_file,
str(e)))
LOG.error("Unable to parse config file \"%s\": %s"
% (config_file, str(e)))
raise e
integ_br = config.get("OVS", "integration-bridge")
# Determine which agent type to use.
enable_tunneling = False
try:
enable_tunneling = config.getboolean("OVS", "enable-tunneling")
except Exception, e:
pass
options = {"sql_connection": config.get("DATABASE", "sql_connection")}
# Get common parameters.
try:
integ_br = config.get("OVS", "integration-bridge")
if not len(integ_br):
raise Exception('Empty integration-bridge in configuration file.')
db_connection_url = config.get("DATABASE", "sql_connection")
if not len(db_connection_url):
raise Exception('Empty db_connection_url in configuration file.')
except Exception, e:
LOG.error("Error parsing common params in config_file: '%s': %s"
% (config_file, str(e)))
sys.exit(1)
if enable_tunneling:
# Get parameters for OVSQuantumTunnelAgent
try:
# Mandatory parameter.
tun_br = config.get("OVS", "tunnel-bridge")
if not len(tun_br):
raise Exception('Empty tunnel-bridge in configuration file.')
# Mandatory parameter.
remote_ip_file = config.get("OVS", "remote-ip-file")
if not len(remote_ip_file):
raise Exception('Empty remote-ip-file in configuration file.')
# Mandatory parameter.
remote_ip_file = config.get("OVS", "remote-ip-file")
local_ip = config.get("OVS", "local-ip")
if not len(local_ip):
raise Exception('Empty local-ip in configuration file.')
except Exception, e:
LOG.error("Error parsing tunnel params in config_file: '%s': %s"
% (config_file, str(e)))
sys.exit(1)
plugin = OVSQuantumTunnelAgent(integ_br, tun_br, remote_ip_file,
local_ip)
else:
# Get parameters for OVSQuantumAgent.
plugin = OVSQuantumAgent(integ_br)
# Start everything.
options = {"sql_connection": db_connection_url}
db = SqlSoup(options["sql_connection"])
LOG.info("Connecting to database \"%s\" on %s" %
(db.engine.url.database, db.engine.url.host))
plugin = OVSQuantumAgent(integ_br)
plugin.daemon_loop(db)
sys.exit(0)

View File

@ -16,6 +16,7 @@
# @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 ConfigParser
import logging as LOG

0
quantum/plugins/openvswitch/run_tests.py Normal file → Executable file
View File

View File

@ -0,0 +1,202 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 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: Dave Lapsley, Nicira Networks, Inc.
import logging
import mox
import os
import unittest
from agent import ovs_quantum_agent
LOG = logging.getLogger("quantum.plugins.openvswitch.tests.unit.test_tunnel")
LOG.setLevel(logging.INFO)
LOCAL_DIR = os.path.dirname(__file__)
REMOTE_IP_FILE = LOCAL_DIR + '/remote-ip-file.txt'
# Useful global dummy variables.
NET_UUID = '3faeebfe-5d37-11e1-a64b-000c29d5f0a7'
LS_ID = '42'
LV_ID = 42
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,
'switch')
class DummyPort:
def __init__(self, interface_id):
self.interface_id = interface_id
class DummyVlanBinding:
def __init__(self, network_id, vlan_id):
self.network_id = network_id
self.vlan_id = vlan_id
class TunnelTest(unittest.TestCase):
def setUp(self):
print LOCAL_DIR
self.mox = mox.Mox()
self.INT_BRIDGE = 'integration_bridge'
self.TUN_BRIDGE = 'tunnel_bridge'
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.mock_int_bridge.delete_port('patch-tun')
self.mock_int_bridge.add_patch_port(
'patch-tun', 'patch-int').AndReturn(self.TUN_OFPORT)
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.reset_bridge()
self.mock_tun_bridge.add_patch_port(
'patch-int', 'patch-tun').AndReturn(self.INT_OFPORT)
self.mock_tun_bridge.remove_all_flows()
self.mock_tun_bridge.add_flow(priority=1, actions='drop')
def tearDown(self):
self.mox.UnsetStubs()
def testConstruct(self):
self.mox.ReplayAll()
b = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
self.TUN_BRIDGE,
REMOTE_IP_FILE,
'10.0.0.1')
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)
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,
actions=action_string)
self.mox.ReplayAll()
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
self.TUN_BRIDGE,
REMOTE_IP_FILE,
'10.0.0.1')
a.available_local_vlans = set([LV_ID])
a.provision_local_vlan(NET_UUID, LS_ID)
self.mox.VerifyAll()
def testReclaimLocalVlan(self):
match_string = 'tun_id=%s' % 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(match=match_string)
self.mox.ReplayAll()
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
self.TUN_BRIDGE,
REMOTE_IP_FILE,
'10.0.0.1')
a.available_local_vlans = set()
a.local_vlan_map[NET_UUID] = LVM
a.reclaim_local_vlan(NET_UUID, LVM)
self.assertTrue(LVM.vlan in a.available_local_vlans)
self.mox.VerifyAll()
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.mox.ReplayAll()
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
self.TUN_BRIDGE,
REMOTE_IP_FILE,
'10.0.0.1')
a.local_vlan_map[NET_UUID] = LVM
a.port_bound(VIF_PORT, NET_UUID, LS_ID)
self.mox.VerifyAll()
def testPortUnbound(self):
self.mox.ReplayAll()
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
self.TUN_BRIDGE,
REMOTE_IP_FILE,
'10.0.0.1')
a.available_local_vlans = set([LV_ID])
a.local_vlan_map[NET_UUID] = LVM
a.port_unbound(VIF_PORT, NET_UUID)
self.mox.VerifyAll()
def testPortDead(self):
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,
actions='drop')
self.mox.ReplayAll()
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
self.TUN_BRIDGE,
REMOTE_IP_FILE,
'10.0.0.1')
a.available_local_vlans = set([LV_ID])
a.local_vlan_map[NET_UUID] = LVM
a.port_dead(VIF_PORT)
self.mox.VerifyAll()
def testDbBindings(self):
db = self.mox.CreateMockAnything()
db.ports = self.mox.CreateMockAnything()
interface_ids = ['interface-id-%d' % x for x in range(3)]
db.ports.all().AndReturn([DummyPort(x) for x in interface_ids])
db.vlan_bindings = self.mox.CreateMockAnything()
vlan_bindings = [
['network-id-%d' % x, 'vlan-id-%d' % x] for x in range(3)]
db.vlan_bindings.all().AndReturn(
[DummyVlanBinding(*x) for x in vlan_bindings])
self.mox.ReplayAll()
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
self.TUN_BRIDGE,
REMOTE_IP_FILE,
'10.0.0.1')
all_bindings = a.get_db_port_bindings(db)
lsw_id_bindings = a.get_db_vlan_bindings(db)
for interface_id, port in all_bindings.iteritems():
self.assertTrue(interface_id in interface_ids)
for network_id, vlan_id in lsw_id_bindings.iteritems():
self.assertTrue(network_id in [x[0] for x in vlan_bindings])
self.assertTrue(vlan_id in [x[1] for x in vlan_bindings])
self.mox.VerifyAll()

View File

@ -3,6 +3,7 @@ PasteDeploy==1.5.0
Routes>=1.12.3
eventlet>=0.9.12
lxml==2.3
mox==0.5.3
python-gflags==1.3
simplejson
sqlalchemy