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
f2a28b36fe
commit
2b4016b8e6
@ -1,7 +1,53 @@
|
|||||||
[DATABASE]
|
[DATABASE]
|
||||||
# This line MUST be changed to actually run the plugin.
|
# This line MUST be changed to actually run the plugin.
|
||||||
# Example: sql_connection = mysql://root:nova@127.0.0.1:3306/ovs_quantum
|
# Example: sql_connection = mysql://root:nova@127.0.0.1:3306/ovs_quantum
|
||||||
|
# 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://
|
sql_connection=sqlite://
|
||||||
|
|
||||||
[OVS]
|
[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
|
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: Somik Behera, Nicira Networks, Inc.
|
||||||
# @author: Brad Hall, Nicira Networks, Inc.
|
# @author: Brad Hall, Nicira Networks, Inc.
|
||||||
# @author: Dan Wendlandt, Nicira Networks, Inc.
|
# @author: Dan Wendlandt, Nicira Networks, Inc.
|
||||||
|
# @author: Dave Lapsley, Nicira Networks, Inc.
|
||||||
|
|
||||||
import ConfigParser
|
import ConfigParser
|
||||||
import logging as LOG
|
import logging as LOG
|
||||||
@ -29,9 +30,15 @@ from sqlalchemy.ext.sqlsoup import SqlSoup
|
|||||||
from subprocess import *
|
from subprocess import *
|
||||||
|
|
||||||
|
|
||||||
|
# Global constants.
|
||||||
OP_STATUS_UP = "UP"
|
OP_STATUS_UP = "UP"
|
||||||
OP_STATUS_DOWN = "DOWN"
|
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'
|
# A class to represent a VIF (i.e., a port that has 'iface-id' and 'vif-mac'
|
||||||
# attributes set).
|
# attributes set).
|
||||||
@ -114,6 +121,23 @@ class OVSBridge:
|
|||||||
flow_str = ",".join(all_args)
|
flow_str = ",".join(all_args)
|
||||||
self.run_ofctl("del-flows", [flow_str])
|
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):
|
def db_get_map(self, table, record, column):
|
||||||
str = self.run_vsctl(["get", table, record, column]).rstrip("\n\r")
|
str = self.run_vsctl(["get", table, record, column]).rstrip("\n\r")
|
||||||
return self.db_str_to_map(str)
|
return self.db_str_to_map(str)
|
||||||
@ -169,7 +193,19 @@ class OVSBridge:
|
|||||||
return edge_ports
|
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):
|
def __init__(self, integ_br):
|
||||||
self.setup_integration_br(integ_br)
|
self.setup_integration_br(integ_br)
|
||||||
@ -223,7 +259,7 @@ class OVSQuantumAgent:
|
|||||||
else:
|
else:
|
||||||
# no binding, put him on the 'dead vlan'
|
# no binding, put him on the 'dead vlan'
|
||||||
self.int_br.set_db_attribute("Port", p.port_name, "tag",
|
self.int_br.set_db_attribute("Port", p.port_name, "tag",
|
||||||
"4095")
|
DEAD_VLAN_TAG)
|
||||||
self.int_br.add_flow(priority=2,
|
self.int_br.add_flow(priority=2,
|
||||||
match="in_port=%s" % p.ofport, actions="drop")
|
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
|
# If we don't have a binding we have to stick it on
|
||||||
# the dead vlan
|
# the dead vlan
|
||||||
net_id = all_bindings[p.vif_id].network_id
|
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)
|
self.port_bound(p, vlan_id)
|
||||||
if p.vif_id in all_bindings:
|
if p.vif_id in all_bindings:
|
||||||
all_bindings[p.vif_id].op_status = OP_STATUS_UP
|
all_bindings[p.vif_id].op_status = OP_STATUS_UP
|
||||||
LOG.info("Adding binding to net-id = %s " \
|
LOG.info("Adding binding to net-id = %s " \
|
||||||
"for %s on vlan %s" % (new_b, str(p), vlan_id))
|
"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:
|
if vif_id not in new_vif_ports:
|
||||||
LOG.info("Port Disappeared: %s" % vif_id)
|
LOG.info("Port Disappeared: %s" % vif_id)
|
||||||
if vif_id in old_local_bindings:
|
if vif_id in old_local_bindings:
|
||||||
@ -260,7 +296,295 @@ class OVSQuantumAgent:
|
|||||||
old_vif_ports = new_vif_ports
|
old_vif_ports = new_vif_ports
|
||||||
old_local_bindings = new_local_bindings
|
old_local_bindings = new_local_bindings
|
||||||
db.commit()
|
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():
|
def main():
|
||||||
@ -285,17 +609,67 @@ def main():
|
|||||||
try:
|
try:
|
||||||
config.read(config_file)
|
config.read(config_file)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
LOG.error("Unable to parse config file \"%s\": %s" % (config_file,
|
LOG.error("Unable to parse config file \"%s\": %s"
|
||||||
str(e)))
|
% (config_file, str(e)))
|
||||||
|
raise e
|
||||||
|
|
||||||
|
# Determine which agent type to use.
|
||||||
|
enable_tunneling = False
|
||||||
|
try:
|
||||||
|
enable_tunneling = config.getboolean("OVS", "enable-tunneling")
|
||||||
|
except Exception, e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Get common parameters.
|
||||||
|
try:
|
||||||
integ_br = config.get("OVS", "integration-bridge")
|
integ_br = config.get("OVS", "integration-bridge")
|
||||||
|
if not len(integ_br):
|
||||||
|
raise Exception('Empty integration-bridge in configuration file.')
|
||||||
|
|
||||||
options = {"sql_connection": config.get("DATABASE", "sql_connection")}
|
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"])
|
db = SqlSoup(options["sql_connection"])
|
||||||
|
|
||||||
LOG.info("Connecting to database \"%s\" on %s" %
|
LOG.info("Connecting to database \"%s\" on %s" %
|
||||||
(db.engine.url.database, db.engine.url.host))
|
(db.engine.url.database, db.engine.url.host))
|
||||||
plugin = OVSQuantumAgent(integ_br)
|
|
||||||
plugin.daemon_loop(db)
|
plugin.daemon_loop(db)
|
||||||
|
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
# @author: Somik Behera, Nicira Networks, Inc.
|
# @author: Somik Behera, Nicira Networks, Inc.
|
||||||
# @author: Brad Hall, Nicira Networks, Inc.
|
# @author: Brad Hall, Nicira Networks, Inc.
|
||||||
# @author: Dan Wendlandt, Nicira Networks, Inc.
|
# @author: Dan Wendlandt, Nicira Networks, Inc.
|
||||||
|
# @author: Dave Lapsley, Nicira Networks, Inc.
|
||||||
|
|
||||||
import ConfigParser
|
import ConfigParser
|
||||||
import logging as LOG
|
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
|
Routes>=1.12.3
|
||||||
eventlet>=0.9.12
|
eventlet>=0.9.12
|
||||||
lxml==2.3
|
lxml==2.3
|
||||||
|
mox==0.5.3
|
||||||
python-gflags==1.3
|
python-gflags==1.3
|
||||||
simplejson
|
simplejson
|
||||||
sqlalchemy
|
sqlalchemy
|
||||||
|
Loading…
Reference in New Issue
Block a user