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:
parent
a945d1a304
commit
c812b9dc03
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
0
quantum/plugins/openvswitch/run_tests.py
Normal file → Executable file
202
quantum/plugins/openvswitch/tests/unit/test_tunnel.py
Normal file
202
quantum/plugins/openvswitch/tests/unit/test_tunnel.py
Normal 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()
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user