Implementation of 2nd phase of provider extension for openswitch

Enhances openvswitch plugin to support flat networks and VLANs on
multiple physical networks via the provider extension. Implements
blueprint provider-networks.

See http://wiki.openstack.org/ConfigureOpenvswitch for
configuration and usage details.

A devstack patch to support the updated openvswitch configuration
variables is at https://review.openstack.org/#/c/11418/.

Change-Id: Ic86b6f3b2e354c7d60bc2c330b334c23d349bc29
This commit is contained in:
Bob Kukura 2012-08-15 02:43:17 -04:00
parent 7632e67750
commit c099e06a9a
13 changed files with 887 additions and 629 deletions

View File

@ -12,29 +12,38 @@ sql_connection = sqlite://
reconnect_interval = 2 reconnect_interval = 2
[OVS] [OVS]
# This enables the new OVSQuantumTunnelAgent which enables tunneling # (ListOpt) Comma-separated list of
# between hybervisors. Leave it set to False or omit for legacy behavior. # <physical_network>:<vlan_min>:<vlan_max> tuples enumerating ranges
enable_tunneling = False # of VLAN IDs on named physical networks that are available for
# allocation.
# network_vlan_ranges = default:1000:2999
# (ListOpt) Comma-separated list of <tun_min>:<tun_max> tuples
# enumerating ranges of GRE tunnel IDs that are available for
# allocation.
# tunnel_id_ranges =
# Do not change this parameter unless you have a good reason to. # 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. # 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 # The integration bridge acts as a virtual "patch port". All VM VIFs are
# attached to this bridge and then "patched" according to their network # attached to this bridge and then "patched" according to their network
# connectivity. # connectivity.
integration_bridge = br-int # integration_bridge = br-int
# Only used if enable-tunneling (above) is True. # Only used if tunnel_id_ranges (above) is not empty.
# In most cases, the default value should be fine. # In most cases, the default value should be fine.
tunnel_bridge = br-tun # tunnel_bridge = br-tun
# Uncomment this line if enable-tunneling is True above. # (ListOpt) Comma-separated list of <physical_network>:<bridge> tuples
# mapping physical network names to agent's node-specific OVS bridge
# names. Each bridge must exist, and should have physical network
# interface configured as a port.
# bridge_mappings = default:br-eth1
# Uncomment this line if tunnel_id_ranges (above) is not empty.
# Set local-ip to be the local IP address of this hypervisor. # Set local-ip to be the local IP address of this hypervisor.
# local_ip = 10.0.0.3 # local_ip = 10.0.0.3
# Uncomment if you want to use custom VLAN range.
# vlan_min = 1
# vlan_max = 4094
[AGENT] [AGENT]
# Agent's polling interval in seconds # Agent's polling interval in seconds
polling_interval = 2 polling_interval = 2
@ -47,25 +56,26 @@ root_helper = sudo
# Sample Configurations. # Sample Configurations.
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
# #
# 1. Without tunneling. # 1. With VLANs on eth1.
# [DATABASE] # [DATABASE]
# sql_connection = mysql://root:nova@127.0.0.1:3306/ovs_quantum # sql_connection = mysql://root:nova@127.0.0.1:3306/ovs_quantum
# [OVS] # [OVS]
# enable_tunneling = False # network_vlan_ranges = default:2000:3999
# tunnel_id_ranges =
# integration_bridge = br-int # integration_bridge = br-int
# bridge_mappings = default:br-eth1
# [AGENT] # [AGENT]
# root_helper = sudo # root_helper = sudo
# Add the following setting, if you want to log to a file # Add the following setting, if you want to log to a file
# log_file = /var/log/quantum/ovs_quantum_agent.log
# #
# 2. With tunneling. # 2. With tunneling.
# [DATABASE] # [DATABASE]
# sql_connection = mysql://root:nova@127.0.0.1:3306/ovs_quantum # sql_connection = mysql://root:nova@127.0.0.1:3306/ovs_quantum
# [OVS] # [OVS]
# enable_tunneling = True # network_vlan_ranges =
# tunnel_id_ranges = 1:1000
# integration_bridge = br-int # integration_bridge = br-int
# tunnel_bridge = br-tun # tunnel_bridge = br-tun
# remote-ip-file = /opt/stack/remote-ips.txt
# local_ip = 10.0.0.3 # local_ip = 10.0.0.3
# [AGENT] # [AGENT]
# root_helper = sudo # root_helper = sudo

View File

@ -71,6 +71,11 @@ class OVSBridge:
self.run_vsctl(["--", "--if-exists", "del-br", self.br_name]) self.run_vsctl(["--", "--if-exists", "del-br", self.br_name])
self.run_vsctl(["add-br", self.br_name]) self.run_vsctl(["add-br", self.br_name])
def add_port(self, port_name):
self.run_vsctl(["--", "--may-exist", "add-port", self.br_name,
port_name])
return self.get_port_ofport(port_name)
def delete_port(self, port_name): def delete_port(self, port_name):
self.run_vsctl(["--", "--if-exists", "del-port", self.br_name, self.run_vsctl(["--", "--if-exists", "del-port", self.br_name,
port_name]) port_name])

View File

@ -127,6 +127,11 @@ class VlanIdInUse(InUse):
"%(physical_network)s is in use.") "%(physical_network)s is in use.")
class TunnelIdInUse(InUse):
message = _("Unable to create the network. "
"The tunnel ID %(tunnel_id)s is in use.")
class ResourceExhausted(QuantumException): class ResourceExhausted(QuantumException):
pass pass

View File

@ -28,9 +28,10 @@ import eventlet
from sqlalchemy.ext import sqlsoup from sqlalchemy.ext import sqlsoup
from quantum.agent import rpc as agent_rpc from quantum.agent import rpc as agent_rpc
from quantum.agent.linux import ip_lib
from quantum.agent.linux import ovs_lib from quantum.agent.linux import ovs_lib
from quantum.agent.linux import utils from quantum.agent.linux import utils
from quantum.common import constants from quantum.common import constants as q_const
from quantum.common import config as logging_config from quantum.common import config as logging_config
from quantum.common import topics from quantum.common import topics
from quantum.openstack.common import cfg from quantum.openstack.common import cfg
@ -38,6 +39,7 @@ from quantum.openstack.common import context
from quantum.openstack.common import rpc from quantum.openstack.common import rpc
from quantum.openstack.common.rpc import dispatcher from quantum.openstack.common.rpc import dispatcher
from quantum.plugins.openvswitch.common import config from quantum.plugins.openvswitch.common import config
from quantum.plugins.openvswitch.common import constants
logging.basicConfig() logging.basicConfig()
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -49,15 +51,20 @@ DEAD_VLAN_TAG = "4095"
# 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 LocalVLANMapping: class LocalVLANMapping:
def __init__(self, vlan, lsw_id, vif_ids=None): def __init__(self, vlan, network_type, physical_network, physical_id,
vif_ids=None):
if vif_ids is None: if vif_ids is None:
vif_ids = [] vif_ids = []
self.vlan = vlan self.vlan = vlan
self.lsw_id = lsw_id self.network_type = network_type
self.physical_network = physical_network
self.physical_id = physical_id
self.vif_ids = vif_ids self.vif_ids = vif_ids
def __str__(self): def __str__(self):
return "lv-id = %s ls-id = %s" % (self.vlan, self.lsw_id) return ("lv-id = %s type = %s phys-net = %s phys-id = %s" %
(self.vlan, self.network_type, self.physical_network,
self.physical_id))
class Port(object): class Port(object):
@ -142,266 +149,30 @@ class OVSRpcCallbacks():
class OVSQuantumAgent(object): class OVSQuantumAgent(object):
'''Implements OVS-based tunneling, VLANs and flat networks.
def __init__(self, integ_br, root_helper, polling_interval, Two local bridges are created: an integration bridge (defaults to
reconnect_interval, rpc): 'br-int') and a tunneling bridge (defaults to 'br-tun'). An
self.root_helper = root_helper additional bridge is created for each physical network interface
self.setup_integration_br(integ_br) used for VLANs and/or flat networks.
self.polling_interval = polling_interval
self.reconnect_interval = reconnect_interval
self.rpc = rpc
if rpc:
self.setup_rpc(integ_br)
def setup_rpc(self, integ_br): All VM VIFs are plugged into the integration bridge. VM VIFs on a
mac = utils.get_interface_mac(integ_br) given virtual network share a common "local" VLAN (i.e. not
self.agent_id = '%s' % (mac.replace(":", "")) propagated externally). The VLAN id of this local VLAN is mapped
self.topic = topics.AGENT to the physical networking details realizing that virtual network.
self.plugin_rpc = agent_rpc.PluginApi(topics.PLUGIN)
# RPC network init For virtual networks realized as GRE tunnels, a Logical Switch
self.context = context.RequestContext('quantum', 'quantum', (LS) identifier and is used to differentiate tenant traffic on
is_admin=False) inter-HV tunnels. A mesh of tunnels is created to other
# Handle updates from service Hypervisors in the cloud. These tunnels originate and terminate on
self.callbacks = OVSRpcCallbacks(self.context, self.int_br) the tunneling bridge of each hypervisor. Port patching is done to
self.dispatcher = self.callbacks.create_rpc_dispatcher() connect local VLANs on the integration bridge to inter-hypervisor
# Define the listening consumers for the agent tunnels on the tunnel bridge.
consumers = [[topics.PORT, topics.UPDATE],
[topics.NETWORK, topics.DELETE]]
self.connection = agent_rpc.create_consumers(self.dispatcher,
self.topic,
consumers)
def port_bound(self, port, vlan_id): For each virtual networks realized as a VLANs or flat network, a
self.int_br.set_db_attribute("Port", port.port_name, veth is used to connect the local VLAN on the integration bridge
"tag", str(vlan_id)) with the physical network bridge, with flow rules adding,
self.int_br.delete_flows(in_port=port.ofport) modifying, or stripping VLAN tags as necessary.
def port_unbound(self, port, still_exists):
if still_exists:
self.int_br.clear_db_attribute("Port", port.port_name, "tag")
def setup_integration_br(self, integ_br):
self.int_br = ovs_lib.OVSBridge(integ_br, self.root_helper)
self.int_br.remove_all_flows()
# switch all traffic using L2 learning
self.int_br.add_flow(priority=1, actions="normal")
def db_loop(self, db_connection_url):
'''Main processing loop for Non-Tunneling Agent.
:param options: database information - in the event need to reconnect
'''
self.local_vlan_map = {}
old_local_bindings = {}
old_vif_ports = {}
db_connected = False
while True:
if not db_connected:
time.sleep(self.reconnect_interval)
db = sqlsoup.SqlSoup(db_connection_url)
db_connected = True
LOG.info("Connecting to database \"%s\" on %s" %
(db.engine.url.database, db.engine.url.host))
all_bindings = {}
try:
ports = db.ports.all()
except Exception, e:
LOG.info("Unable to get port bindings! Exception: %s" % e)
db_connected = False
continue
for port in ports:
all_bindings[port.id] = port
vlan_bindings = {}
try:
vlan_binds = db.vlan_bindings.all()
except Exception, e:
LOG.info("Unable to get vlan bindings! Exception: %s" % e)
db_connected = False
continue
for bind in vlan_binds:
vlan_bindings[bind.network_id] = bind.vlan_id
new_vif_ports = {}
new_local_bindings = {}
vif_ports = self.int_br.get_vif_ports()
for p in vif_ports:
new_vif_ports[p.vif_id] = p
if p.vif_id in all_bindings:
net_id = all_bindings[p.vif_id].network_id
new_local_bindings[p.vif_id] = net_id
else:
# no binding, put him on the 'dead vlan'
self.int_br.set_db_attribute("Port", p.port_name, "tag",
DEAD_VLAN_TAG)
self.int_br.add_flow(priority=2,
in_port=p.ofport,
actions="drop")
old_b = old_local_bindings.get(p.vif_id, None)
new_b = new_local_bindings.get(p.vif_id, None)
if old_b != new_b:
if old_b is not None:
LOG.info("Removing binding to net-id = %s for %s"
% (old_b, str(p)))
self.port_unbound(p, True)
if p.vif_id in all_bindings:
all_bindings[p.vif_id].status = (
constants.PORT_STATUS_DOWN)
if new_b is not None:
# 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, DEAD_VLAN_TAG)
self.port_bound(p, vlan_id)
if p.vif_id in all_bindings:
all_bindings[p.vif_id].status = (
constants.PORT_STATUS_ACTIVE)
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:
if vif_id not in new_vif_ports:
LOG.info("Port Disappeared: %s" % vif_id)
if vif_id in old_local_bindings:
old_b = old_local_bindings[vif_id]
self.port_unbound(old_vif_ports[vif_id], False)
if vif_id in all_bindings:
all_bindings[vif_id].status = (
constants.PORT_STATUS_DOWN)
old_vif_ports = new_vif_ports
old_local_bindings = new_local_bindings
try:
db.commit()
except Exception, e:
LOG.info("Unable to commit to database! Exception: %s" % e)
db.rollback()
old_local_bindings = {}
old_vif_ports = {}
time.sleep(self.polling_interval)
def update_ports(self, registered_ports):
ports = self.int_br.get_vif_port_set()
if ports == registered_ports:
return
added = ports - registered_ports
removed = registered_ports - ports
return {'current': ports,
'added': added,
'removed': removed}
def treat_devices_added(self, devices):
resync = False
for device in devices:
LOG.info("Port %s added", device)
try:
details = self.plugin_rpc.get_device_details(self.context,
device,
self.agent_id)
except Exception as e:
LOG.debug("Unable to get port details for %s: %s", device, e)
resync = True
continue
if 'port_id' in details:
LOG.info("Port %s updated. Details: %s", device, details)
port = self.int_br.get_vif_port_by_id(details['port_id'])
if port:
if details['admin_state_up']:
self.port_bound(port, details['vlan_id'])
else:
self.port_unbound(port, True)
else:
LOG.debug("Device %s not defined on plugin", device)
return resync
def treat_devices_removed(self, devices):
resync = False
for device in devices:
LOG.info("Attachment %s removed", device)
try:
details = self.plugin_rpc.update_device_down(self.context,
device,
self.agent_id)
except Exception as e:
LOG.debug("port_removed failed for %s: %s", device, e)
resync = True
if details['exists']:
LOG.info("Port %s updated.", device)
# Nothing to do regarding local networking
else:
LOG.debug("Device %s not defined on plugin", device)
return resync
def process_network_ports(self, port_info):
resync_a = False
resync_b = False
if 'added' in port_info:
resync_a = self.treat_devices_added(port_info['added'])
if 'removed' in port_info:
resync_b = self.treat_devices_removed(port_info['removed'])
# If one of the above opertaions fails => resync with plugin
return (resync_a | resync_b)
def rpc_loop(self):
sync = True
ports = set()
while True:
start = time.time()
if sync:
LOG.info("Agent out of sync with plugin!")
ports.clear()
sync = False
port_info = self.update_ports(ports)
# notify plugin about port deltas
if port_info:
LOG.debug("Agent loop has new devices!")
# If treat devices fails - indicates must resync with plugin
sync = self.process_network_ports(port_info)
ports = port_info['current']
# sleep till end of polling interval
elapsed = (time.time() - start)
if (elapsed < self.polling_interval):
time.sleep(self.polling_interval - elapsed)
else:
LOG.debug("Loop iteration exceeded interval (%s vs. %s)!",
self.polling_interval, elapsed)
def daemon_loop(self, db_connection_url):
if self.rpc:
self.rpc_loop()
else:
self.db_loop(db_connection_url)
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. # Lower bound on available vlans.
@ -410,13 +181,15 @@ class OVSQuantumTunnelAgent(object):
# Upper bound on available vlans. # Upper bound on available vlans.
MAX_VLAN_TAG = 4094 MAX_VLAN_TAG = 4094
def __init__(self, integ_br, tun_br, local_ip, root_helper, def __init__(self, integ_br, tun_br, local_ip,
bridge_mappings, root_helper,
polling_interval, reconnect_interval, rpc): polling_interval, reconnect_interval, rpc):
'''Constructor. '''Constructor.
:param integ_br: name of the integration bridge. :param integ_br: name of the integration bridge.
:param tun_br: name of the tunnel bridge. :param tun_br: name of the tunnel bridge.
:param local_ip: local IP address of this hypervisor. :param local_ip: local IP address of this hypervisor.
:param bridge_mappings: mappings from phyiscal interface to bridge.
:param root_helper: utility to use when running shell cmds. :param root_helper: utility to use when running shell cmds.
:param polling_interval: interval (secs) to poll DB. :param polling_interval: interval (secs) to poll DB.
:param reconnect_internal: retry interval (secs) on DB error. :param reconnect_internal: retry interval (secs) on DB error.
@ -424,9 +197,10 @@ class OVSQuantumTunnelAgent(object):
''' '''
self.root_helper = root_helper self.root_helper = root_helper
self.available_local_vlans = set( self.available_local_vlans = set(
xrange(OVSQuantumTunnelAgent.MIN_VLAN_TAG, xrange(OVSQuantumAgent.MIN_VLAN_TAG,
OVSQuantumTunnelAgent.MAX_VLAN_TAG)) OVSQuantumAgent.MAX_VLAN_TAG))
self.setup_integration_br(integ_br) self.setup_integration_br(integ_br)
self.setup_physical_bridges(bridge_mappings)
self.local_vlan_map = {} self.local_vlan_map = {}
self.polling_interval = polling_interval self.polling_interval = polling_interval
@ -435,6 +209,7 @@ class OVSQuantumTunnelAgent(object):
self.local_ip = local_ip self.local_ip = local_ip
self.tunnel_count = 0 self.tunnel_count = 0
self.setup_tunnel_br(tun_br) self.setup_tunnel_br(tun_br)
self.rpc = rpc self.rpc = rpc
if rpc: if rpc:
self.setup_rpc(integ_br) self.setup_rpc(integ_br)
@ -455,31 +230,66 @@ class OVSQuantumTunnelAgent(object):
# Define the listening consumers for the agent # Define the listening consumers for the agent
consumers = [[topics.PORT, topics.UPDATE], consumers = [[topics.PORT, topics.UPDATE],
[topics.NETWORK, topics.DELETE], [topics.NETWORK, topics.DELETE],
[config.TUNNEL, topics.UPDATE]] [constants.TUNNEL, topics.UPDATE]]
self.connection = agent_rpc.create_consumers(self.dispatcher, self.connection = agent_rpc.create_consumers(self.dispatcher,
self.topic, self.topic,
consumers) consumers)
def provision_local_vlan(self, net_uuid, lsw_id): def provision_local_vlan(self, net_uuid, network_type, physical_network,
physical_id):
'''Provisions a local VLAN. '''Provisions a local VLAN.
:param net_uuid: the uuid of the network associated with this vlan. :param net_uuid: the uuid of the network associated with this vlan.
:param lsw_id: the logical switch id of this vlan.''' :param network_type: the type of the network ('gre', 'vlan', 'flat')
:param physical_network: the physical network for 'vlan' or 'flat'
:param physical_id: the VLAN ID for 'vlan' or tunnel ID for 'tunnel'
'''
if not self.available_local_vlans: if not self.available_local_vlans:
raise Exception("No local VLANs available for ls-id = %s" % lsw_id) raise Exception("No local VLAN available for net-id=%s" % net_uuid)
lvid = self.available_local_vlans.pop() lvid = self.available_local_vlans.pop()
LOG.info("Assigning %s as local vlan for net-id=%s" % (lvid, net_uuid)) LOG.info("Assigning %s as local vlan for net-id=%s" % (lvid, net_uuid))
self.local_vlan_map[net_uuid] = LocalVLANMapping(lvid, lsw_id) self.local_vlan_map[net_uuid] = LocalVLANMapping(lvid, network_type,
physical_network,
physical_id)
# outbound if network_type == constants.TYPE_GRE:
self.tun_br.add_flow(priority=4, in_port=self.patch_int_ofport, # outbound
dl_vlan=lvid, self.tun_br.add_flow(priority=4, in_port=self.patch_int_ofport,
actions="set_tunnel:%s,normal" % lsw_id) dl_vlan=lvid,
# inbound bcast/mcast actions="set_tunnel:%s,normal" % physical_id)
self.tun_br.add_flow(priority=3, tun_id=lsw_id, # inbound bcast/mcast
dl_dst="01:00:00:00:00:00/01:00:00:00:00:00", self.tun_br.add_flow(priority=3, tun_id=physical_id,
actions="mod_vlan_vid:%s,output:%s" % dl_dst="01:00:00:00:00:00/01:00:00:00:00:00",
(lvid, self.patch_int_ofport)) actions="mod_vlan_vid:%s,output:%s" %
(lvid, self.patch_int_ofport))
elif network_type == constants.TYPE_FLAT:
# outbound
br = self.phys_brs[physical_network]
br.add_flow(priority=4,
in_port=self.phys_ofports[physical_network],
dl_vlan=lvid,
actions="strip_vlan,normal")
# inbound
self.int_br.add_flow(priority=3,
in_port=self.int_ofports[physical_network],
dl_vlan=0xffff,
actions="mod_vlan_vid:%s,normal" % lvid)
elif network_type == constants.TYPE_VLAN:
# outbound
br = self.phys_brs[physical_network]
br.add_flow(priority=4,
in_port=self.phys_ofports[physical_network],
dl_vlan=lvid,
actions="mod_vlan_vid:%s,normal" % physical_id)
# inbound
self.int_br.add_flow(priority=3,
in_port=self.int_ofports[physical_network],
dl_vlan=physical_id,
actions="mod_vlan_vid:%s,normal" % lvid)
else:
LOG.error("provisioning unknown network type %s for net-id=%s" %
(network_type, net_uuid))
def reclaim_local_vlan(self, net_uuid, lvm): def reclaim_local_vlan(self, net_uuid, lvm):
'''Reclaim a local VLAN. '''Reclaim a local VLAN.
@ -488,27 +298,57 @@ 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(tun_id=lvm.lsw_id)
self.tun_br.delete_flows(dl_vlan=lvm.vlan) if lvm.network_type == constants.TYPE_GRE:
self.tun_br.delete_flows(tun_id=lvm.physical_id)
self.tun_br.delete_flows(dl_vlan=lvm.vlan)
elif network_type == constants.TYPE_FLAT:
# outbound
br = self.phys_brs[lvm.physical_network]
br.delete_flows(in_port=self.phys_ofports[lvm.physical_network],
dl_vlan=lvm.vlan)
# inbound
br = self.int_br
br.delete_flows(in_port=self.int_ofports[lvm.physical_network],
dl_vlan=0xffff)
elif network_type == constants.TYPE_VLAN:
# outbound
br = self.phys_brs[lvm.physical_network]
br.delete_flows(in_port=self.phys_ofports[lvm.physical_network],
dl_vlan=lvm.vlan)
# inbound
br = self.int_br
br.delete_flows(in_port=self.int_ofports[lvm.physical_network],
dl_vlan=lvm.physical_id)
else:
LOG.error("reclaiming unknown network type %s for net-id=%s" %
(lvm.network_type, net_uuid))
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,
network_type, physical_network, physical_id):
'''Bind port to net_uuid/lsw_id and install flow for inbound traffic '''Bind port to net_uuid/lsw_id and install flow for inbound traffic
to vm. to vm.
:param port: a ovslib.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 network_type: the type of the network ('gre', 'vlan', 'flat')
:param physical_network: the physical network for 'vlan' or 'flat'
:param physical_id: the VLAN ID for 'vlan' or tunnel ID for 'tunnel'
''' '''
if net_uuid not in self.local_vlan_map: if net_uuid not in self.local_vlan_map:
self.provision_local_vlan(net_uuid, lsw_id) self.provision_local_vlan(net_uuid, network_type,
physical_network, physical_id)
lvm = self.local_vlan_map[net_uuid] lvm = self.local_vlan_map[net_uuid]
lvm.vif_ids.append(port.vif_id) lvm.vif_ids.append(port.vif_id)
# inbound unicast if network_type == constants.TYPE_GRE:
self.tun_br.add_flow(priority=3, tun_id=lsw_id, dl_dst=port.vif_mac, # inbound unicast
actions="mod_vlan_vid:%s,normal" % lvm.vlan) self.tun_br.add_flow(priority=3, tun_id=physical_id,
dl_dst=port.vif_mac,
actions="mod_vlan_vid:%s,normal" % lvm.vlan)
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))
@ -529,6 +369,8 @@ class OVSQuantumTunnelAgent(object):
return return
lvm = self.local_vlan_map[net_uuid] lvm = self.local_vlan_map[net_uuid]
# REVISIT(rkukura): Does inbound unicast flow need to be removed here?
if port.vif_id in lvm.vif_ids: if port.vif_id in lvm.vif_ids:
lvm.vif_ids.remove(port.vif_id) lvm.vif_ids.remove(port.vif_id)
else: else:
@ -573,11 +415,57 @@ class OVSQuantumTunnelAgent(object):
self.tun_br.remove_all_flows() self.tun_br.remove_all_flows()
self.tun_br.add_flow(priority=1, actions="drop") self.tun_br.add_flow(priority=1, actions="drop")
def setup_physical_bridges(self, bridge_mappings):
'''Setup the physical network bridges.
Creates phyiscal network bridges and links them to the
integration bridge using veths.
:param bridge_mappings: map physical network names to bridge names.'''
self.phys_brs = {}
self.int_ofports = {}
self.phys_ofports = {}
ip_wrapper = ip_lib.IPWrapper(self.root_helper)
for physical_network, bridge in bridge_mappings.iteritems():
# setup physical bridge
if not ip_lib.device_exists(bridge, self.root_helper):
LOG.error("Bridge %s for physical network %s does not exist" %
(bridge, physical_network))
sys.exit(1)
br = ovs_lib.OVSBridge(bridge, self.root_helper)
br.remove_all_flows()
br.add_flow(priority=1, actions="normal")
self.phys_brs[physical_network] = br
# create veth to patch physical bridge with integration bridge
int_veth_name = constants.VETH_INTEGRATION_PREFIX + bridge
self.int_br.delete_port(int_veth_name)
phys_veth_name = constants.VETH_PHYSICAL_PREFIX + bridge
br.delete_port(phys_veth_name)
if ip_lib.device_exists(int_veth_name, self.root_helper):
ip_lib.IPDevice(int_veth_name, self.root_helper).link.delete()
int_veth, phys_veth = ip_wrapper.add_veth(int_veth_name,
phys_veth_name)
self.int_ofports[physical_network] = self.int_br.add_port(int_veth)
self.phys_ofports[physical_network] = br.add_port(phys_veth)
# block all untranslated traffic over veth between bridges
self.int_br.add_flow(priority=2,
in_port=self.int_ofports[physical_network],
actions="drop")
br.add_flow(priority=2,
in_port=self.phys_ofports[physical_network],
actions="drop")
# enable veth to pass traffic
int_veth.link.set_up()
phys_veth.link.set_up()
def manage_tunnels(self, tunnel_ips, old_tunnel_ips, db): def manage_tunnels(self, tunnel_ips, old_tunnel_ips, db):
if self.local_ip in tunnel_ips: if self.local_ip in tunnel_ips:
tunnel_ips.remove(self.local_ip) tunnel_ips.remove(self.local_ip)
else: else:
db.tunnel_ips.insert(ip_address=self.local_ip) db.ovs_tunnel_ips.insert(ip_address=self.local_ip)
new_tunnel_ips = tunnel_ips - old_tunnel_ips new_tunnel_ips = tunnel_ips - old_tunnel_ips
if new_tunnel_ips: if new_tunnel_ips:
@ -614,10 +502,11 @@ class OVSQuantumTunnelAgent(object):
all_bindings = dict((p.id, Port(p)) all_bindings = dict((p.id, Port(p))
for p in db.ports.all()) for p in db.ports.all())
all_bindings_vif_port_ids = set(all_bindings) all_bindings_vif_port_ids = set(all_bindings)
lsw_id_bindings = dict((bind.network_id, bind.vlan_id) net_bindings = dict((bind.network_id, bind)
for bind in db.vlan_bindings.all()) for bind in
db.ovs_network_bindings.all())
tunnel_ips = set(x.ip_address for x in db.tunnel_ips.all()) tunnel_ips = set(x.ip_address for x in db.ovs_tunnel_ips.all())
self.manage_tunnels(tunnel_ips, old_tunnel_ips, db) self.manage_tunnels(tunnel_ips, old_tunnel_ips, db)
# Get bindings from OVS bridge. # Get bindings from OVS bridge.
@ -642,7 +531,7 @@ class OVSQuantumTunnelAgent(object):
if b[2] != b[1]]) if b[2] != b[1]])
LOG.debug('all_bindings: %s', all_bindings) LOG.debug('all_bindings: %s', all_bindings)
LOG.debug('lsw_id_bindings: %s', lsw_id_bindings) LOG.debug('net_bindings: %s', net_bindings)
LOG.debug('new_vif_ports_ids: %s', new_vif_ports_ids) LOG.debug('new_vif_ports_ids: %s', new_vif_ports_ids)
LOG.debug('dead_vif_ports_ids: %s', dead_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('old_vif_ports_ids: %s', old_vif_ports_ids)
@ -669,21 +558,24 @@ class OVSQuantumTunnelAgent(object):
self.port_unbound(p, old_net_uuid) self.port_unbound(p, old_net_uuid)
if p.vif_id in all_bindings: if p.vif_id in all_bindings:
all_bindings[p.vif_id].status = ( all_bindings[p.vif_id].status = (
constants.PORT_STATUS_DOWN) q_const.PORT_STATUS_DOWN)
if not new_port: if not new_port:
self.port_dead(p) self.port_dead(p)
if new_port: if new_port:
new_net_uuid = new_port.network_id new_net_uuid = new_port.network_id
if new_net_uuid not in lsw_id_bindings: if new_net_uuid not in net_bindings:
LOG.warn("No ls-id binding found for net-id '%s'" % LOG.warn("No network binding found for net-id"
new_net_uuid) " '%s'" % new_net_uuid)
continue continue
lsw_id = lsw_id_bindings[new_net_uuid] bind = net_bindings[new_net_uuid]
self.port_bound(p, new_net_uuid, lsw_id) self.port_bound(p, new_net_uuid,
bind.network_type,
bind.physical_network,
bind.physical_id)
all_bindings[p.vif_id].status = ( all_bindings[p.vif_id].status = (
constants.PORT_STATUS_ACTIVE) q_const.PORT_STATUS_ACTIVE)
LOG.info("Port %s on net-id = %s bound to %s " % ( LOG.info("Port %s on net-id = %s bound to %s " % (
str(p), new_net_uuid, str(p), new_net_uuid,
str(self.local_vlan_map[new_net_uuid]))) str(self.local_vlan_map[new_net_uuid])))
@ -692,7 +584,7 @@ class OVSQuantumTunnelAgent(object):
LOG.info("Port Disappeared: " + vif_id) LOG.info("Port Disappeared: " + vif_id)
if vif_id in all_bindings: if vif_id in all_bindings:
all_bindings[vif_id].status = ( all_bindings[vif_id].status = (
constants.PORT_STATUS_DOWN) q_const.PORT_STATUS_DOWN)
old_port = old_local_bindings.get(vif_id) old_port = old_local_bindings.get(vif_id)
if old_port: if old_port:
self.port_unbound(old_vif_ports[vif_id], self.port_unbound(old_vif_ports[vif_id],
@ -739,7 +631,9 @@ class OVSQuantumTunnelAgent(object):
if port: if port:
if details['admin_state_up']: if details['admin_state_up']:
self.port_bound(port, details['network_id'], self.port_bound(port, details['network_id'],
details['vlan_id']) details['network_type'],
details['physical_network'],
details['physical_id'])
else: else:
self.port_unbound(port, details['network_id']) self.port_unbound(port, details['network_id'])
else: else:
@ -835,27 +729,32 @@ def main():
# (TODO) gary - swap with common logging # (TODO) gary - swap with common logging
logging_config.setup_logging(cfg.CONF) logging_config.setup_logging(cfg.CONF)
# Determine which agent type to use.
enable_tunneling = cfg.CONF.OVS.enable_tunneling
integ_br = cfg.CONF.OVS.integration_bridge integ_br = cfg.CONF.OVS.integration_bridge
db_connection_url = cfg.CONF.DATABASE.sql_connection db_connection_url = cfg.CONF.DATABASE.sql_connection
polling_interval = cfg.CONF.AGENT.polling_interval polling_interval = cfg.CONF.AGENT.polling_interval
reconnect_interval = cfg.CONF.DATABASE.reconnect_interval reconnect_interval = cfg.CONF.DATABASE.reconnect_interval
root_helper = cfg.CONF.AGENT.root_helper root_helper = cfg.CONF.AGENT.root_helper
rpc = cfg.CONF.AGENT.rpc rpc = cfg.CONF.AGENT.rpc
tun_br = cfg.CONF.OVS.tunnel_bridge
local_ip = cfg.CONF.OVS.local_ip
if enable_tunneling: bridge_mappings = {}
# Get parameters for OVSQuantumTunnelAgent for mapping in cfg.CONF.OVS.bridge_mappings:
tun_br = cfg.CONF.OVS.tunnel_bridge mapping = mapping.strip()
# Mandatory parameter. if mapping != '':
local_ip = cfg.CONF.OVS.local_ip try:
plugin = OVSQuantumTunnelAgent(integ_br, tun_br, local_ip, root_helper, physical_network, bridge = mapping.split(':')
polling_interval, reconnect_interval, bridge_mappings[physical_network] = bridge
rpc) LOG.debug("physical network %s mapped to bridge %s" %
else: (physical_network, bridge))
# Get parameters for OVSQuantumAgent. except ValueError as ex:
plugin = OVSQuantumAgent(integ_br, root_helper, polling_interval, LOG.error("Invalid bridge mapping: \'%s\' - %s" %
reconnect_interval, rpc) (mapping, ex))
sys.exit(1)
plugin = OVSQuantumAgent(integ_br, tun_br, local_ip, bridge_mappings,
root_helper, polling_interval,
reconnect_interval, rpc)
# Start everything. # Start everything.
plugin.daemon_loop(db_connection_url) plugin.daemon_loop(db_connection_url)

View File

@ -17,8 +17,9 @@
from quantum.openstack.common import cfg from quantum.openstack.common import cfg
# Topic for tunnel notifications between the plugin and agent DEFAULT_BRIDGE_MAPPINGS = ['default:br-eth1']
TUNNEL = 'tunnel' DEFAULT_VLAN_RANGES = ['default:1000:2999']
DEFAULT_TUNNEL_RANGES = []
database_opts = [ database_opts = [
cfg.StrOpt('sql_connection', default='sqlite://'), cfg.StrOpt('sql_connection', default='sqlite://'),
@ -27,18 +28,24 @@ database_opts = [
] ]
ovs_opts = [ ovs_opts = [
cfg.BoolOpt('enable_tunneling', default=False),
cfg.StrOpt('integration_bridge', default='br-int'), cfg.StrOpt('integration_bridge', default='br-int'),
cfg.StrOpt('tunnel_bridge', default='br-tun'), cfg.StrOpt('tunnel_bridge', default='br-tun'),
cfg.StrOpt('local_ip', default='10.0.0.3'), cfg.StrOpt('local_ip', default='10.0.0.3'),
cfg.IntOpt('vlan_min', default=1), cfg.ListOpt('bridge_mappings',
cfg.IntOpt('vlan_max', default=4094), default=DEFAULT_BRIDGE_MAPPINGS,
help="List of <physical_network>:<bridge>"),
cfg.ListOpt('network_vlan_ranges',
default=DEFAULT_VLAN_RANGES,
help="List of <physical_network>:<vlan_min>:<vlan_max> "
"or <physical_network>"),
cfg.ListOpt('tunnel_id_ranges',
default=DEFAULT_TUNNEL_RANGES,
help="List of <tun_min>:<tun_max>"),
] ]
agent_opts = [ agent_opts = [
cfg.IntOpt('polling_interval', default=2), cfg.IntOpt('polling_interval', default=2),
cfg.StrOpt('root_helper', default='sudo'), cfg.StrOpt('root_helper', default='sudo'),
cfg.StrOpt('log_file', default=None),
cfg.BoolOpt('rpc', default=True), cfg.BoolOpt('rpc', default=True),
] ]

View File

@ -0,0 +1,30 @@
# Copyright (c) 2012 OpenStack, LLC.
#
# 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.
# Special vlan_id value in ovs_vlan_allocations table indicating flat network
FLAT_VLAN_ID = -1
# Topic for tunnel notifications between the plugin and agent
TUNNEL = 'tunnel'
# Values for network_type
TYPE_FLAT = 'flat'
TYPE_VLAN = 'vlan'
TYPE_GRE = 'gre'
# Name prefixes for veth device pair linking the integration bridge
# with the physical bridge for a physical network
VETH_INTEGRATION_PREFIX = 'int-'
VETH_PHYSICAL_PREFIX = 'phy-'

View File

@ -30,146 +30,242 @@ from quantum.plugins.openvswitch import ovs_models_v2
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
def get_vlans(): def initialize():
session = db.get_session() options = {"sql_connection": "%s" % cfg.CONF.DATABASE.sql_connection}
try: options.update({"sql_max_retries": cfg.CONF.DATABASE.sql_max_retries})
bindings = (session.query(ovs_models_v2.VlanBinding). options.update({"reconnect_interval":
all()) cfg.CONF.DATABASE.reconnect_interval})
except exc.NoResultFound: options.update({"base": models_v2.model_base.BASEV2})
return [] db.configure_db(options)
return [(binding.vlan_id, binding.network_id) for binding in bindings]
def get_vlan(net_id, session=None): def get_network_binding(session, network_id):
session = session or db.get_session() session = session or db.get_session()
try: try:
binding = (session.query(ovs_models_v2.VlanBinding). binding = (session.query(ovs_models_v2.NetworkBinding).
filter_by(network_id=net_id). filter_by(network_id=network_id).
one()) one())
return binding
except exc.NoResultFound: except exc.NoResultFound:
return return
return binding.vlan_id
def add_vlan_binding(vlan_id, net_id, session): def add_network_binding(session, network_id, network_type,
physical_network, physical_id):
with session.begin(subtransactions=True): with session.begin(subtransactions=True):
binding = ovs_models_v2.VlanBinding(vlan_id, net_id) binding = ovs_models_v2.NetworkBinding(network_id, network_type,
physical_network,
physical_id)
session.add(binding) session.add(binding)
return binding
def remove_vlan_binding(net_id): def sync_vlan_allocations(network_vlan_ranges):
"""Synchronize vlan_allocations table with configured VLAN ranges"""
session = db.get_session()
with session.begin():
# process vlan ranges for each physical network separately
for physical_network, vlan_ranges in network_vlan_ranges.iteritems():
# determine current configured allocatable vlans for this
# physical network
vlan_ids = set()
for vlan_range in vlan_ranges:
vlan_ids |= set(xrange(vlan_range[0], vlan_range[1] + 1))
# remove from table unallocated vlans not currently allocatable
try:
allocs = (session.query(ovs_models_v2.VlanAllocation).
filter_by(physical_network=physical_network).
all())
for alloc in allocs:
try:
# see if vlan is allocatable
vlan_ids.remove(alloc.vlan_id)
except KeyError:
# it's not allocatable, so check if its allocated
if not alloc.allocated:
# it's not, so remove it from table
LOG.debug("removing vlan %s on physical network "
"%s from pool" %
(alloc.vlan_id, physical_network))
session.delete(alloc)
except exc.NoResultFound:
pass
# add missing allocatable vlans to table
for vlan_id in sorted(vlan_ids):
alloc = ovs_models_v2.VlanAllocation(physical_network, vlan_id)
session.add(alloc)
def get_vlan_allocation(physical_network, vlan_id):
session = db.get_session() session = db.get_session()
try: try:
binding = (session.query(ovs_models_v2.VlanBinding). alloc = (session.query(ovs_models_v2.VlanAllocation).
filter_by(network_id=net_id). filter_by(physical_network=physical_network,
one()) vlan_id=vlan_id).
session.delete(binding) one())
return alloc
except exc.NoResultFound: except exc.NoResultFound:
pass return
session.flush()
def update_vlan_id_pool(): def reserve_vlan(session):
"""Update vlan_ids based on current configuration.""" with session.begin(subtransactions=True):
alloc = (session.query(ovs_models_v2.VlanAllocation).
filter_by(allocated=False).
first())
if alloc:
LOG.debug("reserving vlan %s on physical network %s from pool" %
(alloc.vlan_id, alloc.physical_network))
alloc.allocated = True
return (alloc.physical_network, alloc.vlan_id)
raise q_exc.NoNetworkAvailable()
# determine current dynamically-allocated range
vlans = set(xrange(cfg.CONF.OVS.vlan_min, def reserve_specific_vlan(session, physical_network, vlan_id):
cfg.CONF.OVS.vlan_max + 1)) with session.begin(subtransactions=True):
try:
alloc = (session.query(ovs_models_v2.VlanAllocation).
filter_by(physical_network=physical_network,
vlan_id=vlan_id).
one())
if alloc.allocated:
raise q_exc.VlanIdInUse(vlan_id=vlan_id,
physical_network=physical_network)
LOG.debug("reserving specific vlan %s on physical network %s "
"from pool" % (vlan_id, physical_network))
alloc.allocated = True
except exc.NoResultFound:
LOG.debug("reserving specific vlan %s on physical network %s "
"outside pool" % (vlan_id, physical_network))
alloc = ovs_models_v2.VlanAllocation(physical_network, vlan_id)
alloc.allocated = True
session.add(alloc)
def release_vlan(session, physical_network, vlan_id, network_vlan_ranges):
with session.begin(subtransactions=True):
try:
alloc = (session.query(ovs_models_v2.VlanAllocation).
filter_by(physical_network=physical_network,
vlan_id=vlan_id).
one())
alloc.allocated = False
inside = False
for vlan_range in network_vlan_ranges.get(physical_network, []):
if vlan_id >= vlan_range[0] and vlan_id <= vlan_range[1]:
inside = True
break
if not inside:
session.delete(alloc)
LOG.debug("releasing vlan %s on physical network %s %s pool" %
(vlan_id, physical_network,
inside and "to" or "outside"))
except exc.NoResultFound:
LOG.warning("vlan_id %s on physical network %s not found" %
(vlan_id, physical_network))
def sync_tunnel_allocations(tunnel_id_ranges):
"""Synchronize tunnel_allocations table with configured tunnel ranges"""
# determine current configured allocatable tunnels
tunnel_ids = set()
for tunnel_id_range in tunnel_id_ranges:
tun_min, tun_max = tunnel_id_range
if tun_max + 1 - tun_min > 1000000:
LOG.error("Skipping unreasonable tunnel ID range %s:%s" %
tunnel_id_range)
else:
tunnel_ids |= set(xrange(tun_min, tun_max + 1))
session = db.get_session() session = db.get_session()
with session.begin(subtransactions=True): with session.begin():
# remove unused vlan_ids outside current range # remove from table unallocated tunnels not currently allocatable
try: try:
records = (session.query(ovs_models_v2.VlanID). allocs = (session.query(ovs_models_v2.TunnelAllocation).
all()) all())
for record in records: for alloc in allocs:
try: try:
vlans.remove(record.vlan_id) # see if tunnel is allocatable
tunnel_ids.remove(alloc.tunnel_id)
except KeyError: except KeyError:
if not record.vlan_used: # it's not allocatable, so check if its allocated
LOG.debug("removing vlan %s from pool" if not alloc.allocated:
% record.vlan_id) # it's not, so remove it from table
session.delete(record) LOG.debug("removing tunnel %s from pool" %
alloc.tunnel_id)
session.delete(alloc)
except exc.NoResultFound: except exc.NoResultFound:
pass pass
# add missing vlan_ids # add missing allocatable tunnels to table
for vlan in vlans: for tunnel_id in sorted(tunnel_ids):
record = ovs_models_v2.VlanID(vlan) alloc = ovs_models_v2.TunnelAllocation(tunnel_id)
session.add(record) session.add(alloc)
def get_vlan_id(vlan_id): def get_tunnel_allocation(tunnel_id):
"""Get state of specified vlan"""
session = db.get_session() session = db.get_session()
try: try:
record = (session.query(ovs_models_v2.VlanID). alloc = (session.query(ovs_models_v2.TunnelAllocation).
filter_by(vlan_id=vlan_id). filter_by(tunnel_id=tunnel_id).
one()) one())
return record return alloc
except exc.NoResultFound: except exc.NoResultFound:
return None return
def reserve_vlan_id(session): def reserve_tunnel(session):
"""Reserve an unused vlan_id"""
with session.begin(subtransactions=True): with session.begin(subtransactions=True):
record = (session.query(ovs_models_v2.VlanID). alloc = (session.query(ovs_models_v2.TunnelAllocation).
filter_by(vlan_used=False). filter_by(allocated=False).
first()) first())
if not record: if alloc:
raise q_exc.NoNetworkAvailable() LOG.debug("reserving tunnel %s from pool" % alloc.tunnel_id)
LOG.debug("reserving vlan %s from pool" % record.vlan_id) alloc.allocated = True
record.vlan_used = True return alloc.tunnel_id
return record.vlan_id raise q_exc.NoNetworkAvailable()
def reserve_specific_vlan_id(vlan_id, session): def reserve_specific_tunnel(session, tunnel_id):
"""Reserve a specific vlan_id"""
if vlan_id < 1 or vlan_id > 4094:
msg = _("Specified VLAN %s outside legal range (1-4094)") % vlan_id
raise q_exc.InvalidInput(error_message=msg)
with session.begin(subtransactions=True): with session.begin(subtransactions=True):
try: try:
record = (session.query(ovs_models_v2.VlanID). alloc = (session.query(ovs_models_v2.TunnelAllocation).
filter_by(vlan_id=vlan_id). filter_by(tunnel_id=tunnel_id).
one()) one())
if record.vlan_used: if alloc.allocated:
# REVISIT(rkukura) pass phyiscal_network raise q_exc.TunnelIdInUse(tunnel_id=tunnel_id)
raise q_exc.VlanIdInUse(vlan_id=vlan_id, LOG.debug("reserving specific tunnel %s from pool" % tunnel_id)
physical_network='default') alloc.allocated = True
LOG.debug("reserving specific vlan %s from pool" % vlan_id)
record.vlan_used = True
except exc.NoResultFound: except exc.NoResultFound:
LOG.debug("reserving specific vlan %s outside pool" % vlan_id) LOG.debug("reserving specific tunnel %s outside pool" % tunnel_id)
record = ovs_models_v2.VlanID(vlan_id) alloc = ovs_models_v2.TunnelAllocation(tunnel_id)
record.vlan_used = True alloc.allocated = True
session.add(record) session.add(alloc)
def release_vlan_id(vlan_id): def release_tunnel(session, tunnel_id, tunnel_id_ranges):
"""Set the vlan state to be unused, and delete if not in range"""
session = db.get_session()
with session.begin(subtransactions=True): with session.begin(subtransactions=True):
try: try:
record = (session.query(ovs_models_v2.VlanID). alloc = (session.query(ovs_models_v2.TunnelAllocation).
filter_by(vlan_id=vlan_id). filter_by(tunnel_id=tunnel_id).
one()) one())
record.vlan_used = False alloc.allocated = False
if (vlan_id >= cfg.CONF.OVS.vlan_min and inside = False
vlan_id <= cfg.CONF.OVS.vlan_max): for tunnel_id_range in tunnel_id_ranges:
LOG.debug("releasing vlan %s to pool" % vlan_id) if (tunnel_id >= tunnel_id_range[0]
else: and tunnel_id <= tunnel_id_range[1]):
LOG.debug("removing vlan %s outside pool" % vlan_id) inside = True
session.delete(record) break
if not inside:
session.delete(alloc)
LOG.debug("releasing tunnel %s %s pool" %
(tunnel_id, inside and "to" or "outside"))
except exc.NoResultFound: except exc.NoResultFound:
LOG.error("vlan id %s not found in release_vlan_id" % vlan_id) LOG.warning("tunnel_id %s not found" % tunnel_id)
def get_port(port_id): def get_port(port_id):
@ -192,19 +288,19 @@ def set_port_status(port_id, status):
raise q_exc.PortNotFound(port_id=port_id) raise q_exc.PortNotFound(port_id=port_id)
def get_tunnels(): def get_tunnel_endpoints():
session = db.get_session() session = db.get_session()
try: try:
tunnels = session.query(ovs_models_v2.TunnelInfo).all() tunnels = session.query(ovs_models_v2.TunnelEndpoint).all()
except exc.NoResultFound: except exc.NoResultFound:
return [] return []
return [{'id': tunnel.id, return [{'id': tunnel.id,
'ip_address': tunnel.ip_address} for tunnel in tunnels] 'ip_address': tunnel.ip_address} for tunnel in tunnels]
def generate_tunnel_id(session): def _generate_tunnel_id(session):
try: try:
tunnels = session.query(ovs_models_v2.TunnelInfo).all() tunnels = session.query(ovs_models_v2.TunnelEndpoint).all()
except exc.NoResultFound: except exc.NoResultFound:
return 0 return 0
tunnel_ids = ([tunnel['id'] for tunnel in tunnels]) tunnel_ids = ([tunnel['id'] for tunnel in tunnels])
@ -215,15 +311,14 @@ def generate_tunnel_id(session):
return id + 1 return id + 1
def add_tunnel(ip): def add_tunnel_endpoint(ip):
session = db.get_session() session = db.get_session()
try: try:
tunnel = (session.query(ovs_models_v2.TunnelInfo). tunnel = (session.query(ovs_models_v2.TunnelEndpoint).
filter_by(ip_address=ip).one()) filter_by(ip_address=ip).one())
except exc.NoResultFound: except exc.NoResultFound:
# Generate an id for the tunnel id = _generate_tunnel_id(session)
id = generate_tunnel_id(session) tunnel = ovs_models_v2.TunnelEndpoint(ip, id)
tunnel = ovs_models_v2.TunnelInfo(ip, id)
session.add(tunnel) session.add(tunnel)
session.flush() session.flush()
return tunnel return tunnel

View File

@ -22,41 +22,69 @@ from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
from quantum.db.models_v2 import model_base from quantum.db.models_v2 import model_base
class VlanID(model_base.BASEV2): class VlanAllocation(model_base.BASEV2):
"""Represents a vlan_id usage""" """Represents allocation state of vlan_id on physical network"""
__tablename__ = 'vlan_ids' __tablename__ = 'ovs_vlan_allocations'
vlan_id = Column(Integer, nullable=False, primary_key=True) physical_network = Column(String(64), nullable=False, primary_key=True)
vlan_used = Column(Boolean, nullable=False) vlan_id = Column(Integer, nullable=False, primary_key=True,
autoincrement=False)
allocated = Column(Boolean, nullable=False)
def __init__(self, vlan_id): def __init__(self, physical_network, vlan_id):
self.physical_network = physical_network
self.vlan_id = vlan_id self.vlan_id = vlan_id
self.vlan_used = False self.allocated = False
def __repr__(self): def __repr__(self):
return "<VlanID(%d,%s)>" % (self.vlan_id, self.vlan_used) return "<VlanAllocation(%s,%d,%s)>" % (self.physical_network,
self.vlan_id, self.allocated)
class VlanBinding(model_base.BASEV2): class TunnelAllocation(model_base.BASEV2):
"""Represents a binding of network_id to vlan_id.""" """Represents allocation state of tunnel_id"""
__tablename__ = 'vlan_bindings' __tablename__ = 'ovs_tunnel_allocations'
network_id = Column(String(36), ForeignKey('networks.id', tunnel_id = Column(Integer, nullable=False, primary_key=True,
ondelete="CASCADE"), autoincrement=False)
allocated = Column(Boolean, nullable=False)
def __init__(self, tunnel_id):
self.tunnel_id = tunnel_id
self.allocated = False
def __repr__(self):
return "<TunnelAllocation(%d,%s)>" % (self.tunnel_id, self.allocated)
class NetworkBinding(model_base.BASEV2):
"""Represents binding of virtual network to physical realization"""
__tablename__ = 'ovs_network_bindings'
network_id = Column(String(36),
ForeignKey('networks.id', ondelete="CASCADE"),
primary_key=True) primary_key=True)
vlan_id = Column(Integer, nullable=False) network_type = Column(String(32), nullable=False) # 'gre', 'vlan', 'flat'
physical_network = Column(String(64))
physical_id = Column(Integer) # tunnel_id or vlan_id
def __init__(self, vlan_id, network_id): def __init__(self, network_id, network_type, physical_network,
physical_id):
self.network_id = network_id self.network_id = network_id
self.vlan_id = vlan_id self.network_type = network_type
self.physical_network = physical_network
self.physical_id = physical_id
def __repr__(self): def __repr__(self):
return "<VlanBinding(%s,%s)>" % (self.vlan_id, self.network_id) return "<NetworkBinding(%s,%s,%s,%d)>" % (self.network_id,
self.network_type,
self.physical_network,
self.physical_id)
class TunnelIP(model_base.BASEV2): class TunnelIP(model_base.BASEV2):
"""Represents a remote IP in tunnel mode.""" """Represents tunnel endpoint in DB mode"""
__tablename__ = 'tunnel_ips' __tablename__ = 'ovs_tunnel_ips'
ip_address = Column(String(255), primary_key=True) ip_address = Column(String(255), primary_key=True)
@ -67,9 +95,9 @@ class TunnelIP(model_base.BASEV2):
return "<TunnelIP(%s)>" % (self.ip_address) return "<TunnelIP(%s)>" % (self.ip_address)
class TunnelInfo(model_base.BASEV2): class TunnelEndpoint(model_base.BASEV2):
"""Represents remote tunnel information in tunnel mode.""" """Represents tunnel endpoint in RPC mode"""
__tablename__ = 'tunnel_info' __tablename__ = 'ovs_tunnel_endpoints'
ip_address = Column(String(64), primary_key=True) ip_address = Column(String(64), primary_key=True)
id = Column(Integer, nullable=False) id = Column(Integer, nullable=False)
@ -79,4 +107,4 @@ class TunnelInfo(model_base.BASEV2):
self.id = id self.id = id
def __repr__(self): def __repr__(self):
return "<TunnelInfo(%s,%s)>" % (self.ip_address, self.id) return "<TunnelEndpoint(%s,%s)>" % (self.ip_address, self.id)

View File

@ -22,22 +22,22 @@
import logging import logging
import os import os
import sys
from quantum.api.v2 import attributes from quantum.api.v2 import attributes
from quantum.common import constants from quantum.common import constants as q_const
from quantum.common import exceptions as q_exc from quantum.common import exceptions as q_exc
from quantum.common import topics from quantum.common import topics
from quantum.db import api as db
from quantum.db import db_base_plugin_v2 from quantum.db import db_base_plugin_v2
from quantum.db import dhcp_rpc_base from quantum.db import dhcp_rpc_base
from quantum.db import l3_db from quantum.db import l3_db
from quantum.db import models_v2
from quantum.openstack.common import context from quantum.openstack.common import context
from quantum.openstack.common import cfg from quantum.openstack.common import cfg
from quantum.openstack.common import rpc from quantum.openstack.common import rpc
from quantum.openstack.common.rpc import dispatcher from quantum.openstack.common.rpc import dispatcher
from quantum.openstack.common.rpc import proxy from quantum.openstack.common.rpc import proxy
from quantum.plugins.openvswitch.common import config from quantum.plugins.openvswitch.common import config
from quantum.plugins.openvswitch.common import constants
from quantum.plugins.openvswitch import ovs_db_v2 from quantum.plugins.openvswitch import ovs_db_v2
from quantum import policy from quantum import policy
@ -50,8 +50,8 @@ class OVSRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin):
# Set RPC API version to 1.0 by default. # Set RPC API version to 1.0 by default.
RPC_API_VERSION = '1.0' RPC_API_VERSION = '1.0'
def __init__(self, context, notifier): def __init__(self, rpc_context, notifier):
self.context = context self.rpc_context = rpc_context
self.notifier = notifier self.notifier = notifier
def create_rpc_dispatcher(self): def create_rpc_dispatcher(self):
@ -62,27 +62,29 @@ class OVSRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin):
''' '''
return dispatcher.RpcDispatcher([self]) return dispatcher.RpcDispatcher([self])
def get_device_details(self, context, **kwargs): def get_device_details(self, rpc_context, **kwargs):
"""Agent requests device details""" """Agent requests device details"""
agent_id = kwargs.get('agent_id') agent_id = kwargs.get('agent_id')
device = kwargs.get('device') device = kwargs.get('device')
LOG.debug("Device %s details requested from %s", device, agent_id) LOG.debug("Device %s details requested from %s", device, agent_id)
port = ovs_db_v2.get_port(device) port = ovs_db_v2.get_port(device)
if port: if port:
vlan_id = ovs_db_v2.get_vlan(port['network_id']) binding = ovs_db_v2.get_network_binding(None, port['network_id'])
entry = {'device': device, entry = {'device': device,
'vlan_id': vlan_id,
'network_id': port['network_id'], 'network_id': port['network_id'],
'port_id': port['id'], 'port_id': port['id'],
'admin_state_up': port['admin_state_up']} 'admin_state_up': port['admin_state_up'],
'network_type': binding.network_type,
'physical_id': binding.physical_id,
'physical_network': binding.physical_network}
# Set the port status to UP # Set the port status to UP
ovs_db_v2.set_port_status(port['id'], constants.PORT_STATUS_ACTIVE) ovs_db_v2.set_port_status(port['id'], q_const.PORT_STATUS_ACTIVE)
else: else:
entry = {'device': device} entry = {'device': device}
LOG.debug("%s can not be found in database", device) LOG.debug("%s can not be found in database", device)
return entry return entry
def update_device_down(self, context, **kwargs): def update_device_down(self, rpc_context, **kwargs):
"""Device no longer exists on agent""" """Device no longer exists on agent"""
# (TODO) garyk - live migration and port status # (TODO) garyk - live migration and port status
agent_id = kwargs.get('agent_id') agent_id = kwargs.get('agent_id')
@ -93,14 +95,14 @@ class OVSRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin):
entry = {'device': device, entry = {'device': device,
'exists': True} 'exists': True}
# Set port status to DOWN # Set port status to DOWN
ovs_db_v2.set_port_status(port['id'], constants.PORT_STATUS_DOWN) ovs_db_v2.set_port_status(port['id'], q_const.PORT_STATUS_DOWN)
else: else:
entry = {'device': device, entry = {'device': device,
'exists': False} 'exists': False}
LOG.debug("%s can not be found in database", device) LOG.debug("%s can not be found in database", device)
return entry return entry
def tunnel_sync(self, context, **kwargs): def tunnel_sync(self, rpc_context, **kwargs):
"""Update new tunnel. """Update new tunnel.
Updates the datbase with the tunnel IP. All listening agents will also Updates the datbase with the tunnel IP. All listening agents will also
@ -108,12 +110,12 @@ class OVSRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin):
""" """
tunnel_ip = kwargs.get('tunnel_ip') tunnel_ip = kwargs.get('tunnel_ip')
# Update the database with the IP # Update the database with the IP
tunnel = ovs_db_v2.add_tunnel(tunnel_ip) tunnel = ovs_db_v2.add_tunnel_endpoint(tunnel_ip)
tunnels = ovs_db_v2.get_tunnels() tunnels = ovs_db_v2.get_tunnel_endpoints()
entry = dict() entry = dict()
entry['tunnels'] = tunnels entry['tunnels'] = tunnels
# Notify all other listening agents # Notify all other listening agents
self.notifier.tunnel_update(self.context, tunnel.ip_address, self.notifier.tunnel_update(self.rpc_context, tunnel.ip_address,
tunnel.id) tunnel.id)
# Return the list of tunnels IP's to the agent # Return the list of tunnels IP's to the agent
return entry return entry
@ -139,7 +141,7 @@ class AgentNotifierApi(proxy.RpcProxy):
topics.PORT, topics.PORT,
topics.UPDATE) topics.UPDATE)
self.topic_tunnel_update = topics.get_topic_name(topic, self.topic_tunnel_update = topics.get_topic_name(topic,
config.TUNNEL, constants.TUNNEL,
topics.UPDATE) topics.UPDATE)
def network_delete(self, context, network_id): def network_delete(self, context, network_id):
@ -186,34 +188,67 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
supported_extension_aliases = ["provider", "os-quantum-router"] supported_extension_aliases = ["provider", "os-quantum-router"]
def __init__(self, configfile=None): def __init__(self, configfile=None):
self.enable_tunneling = cfg.CONF.OVS.enable_tunneling ovs_db_v2.initialize()
options = {"sql_connection": cfg.CONF.DATABASE.sql_connection} self._parse_network_vlan_ranges()
options.update({'base': models_v2.model_base.BASEV2}) ovs_db_v2.sync_vlan_allocations(self.network_vlan_ranges)
sql_max_retries = cfg.CONF.DATABASE.sql_max_retries self._parse_tunnel_id_ranges()
options.update({"sql_max_retries": sql_max_retries}) ovs_db_v2.sync_tunnel_allocations(self.tunnel_id_ranges)
reconnect_interval = cfg.CONF.DATABASE.reconnect_interval
options.update({"reconnect_interval": reconnect_interval})
db.configure_db(options)
# update the vlan_id table based on current configuration
ovs_db_v2.update_vlan_id_pool()
self.agent_rpc = cfg.CONF.AGENT.rpc self.agent_rpc = cfg.CONF.AGENT.rpc
self.setup_rpc() self.setup_rpc()
def setup_rpc(self): def setup_rpc(self):
# RPC support # RPC support
self.topic = topics.PLUGIN self.topic = topics.PLUGIN
self.context = context.RequestContext('quantum', 'quantum', self.rpc_context = context.RequestContext('quantum', 'quantum',
is_admin=False) is_admin=False)
self.conn = rpc.create_connection(new=True) self.conn = rpc.create_connection(new=True)
self.notifier = AgentNotifierApi(topics.AGENT) self.notifier = AgentNotifierApi(topics.AGENT)
self.callbacks = OVSRpcCallbacks(self.context, self.notifier) self.callbacks = OVSRpcCallbacks(self.rpc_context, self.notifier)
self.dispatcher = self.callbacks.create_rpc_dispatcher() self.dispatcher = self.callbacks.create_rpc_dispatcher()
self.conn.create_consumer(self.topic, self.dispatcher, self.conn.create_consumer(self.topic, self.dispatcher,
fanout=False) fanout=False)
# Consume from all consumers in a thread # Consume from all consumers in a thread
self.conn.consume_in_thread() self.conn.consume_in_thread()
def _parse_network_vlan_ranges(self):
self.network_vlan_ranges = {}
for entry in cfg.CONF.OVS.network_vlan_ranges:
entry = entry.strip()
if ':' in entry:
try:
physical_network, vlan_min, vlan_max = entry.split(':')
self._add_network_vlan_range(physical_network.strip(),
int(vlan_min),
int(vlan_max))
except ValueError as ex:
LOG.error("Invalid network VLAN range: \'%s\' - %s" %
(entry, ex))
sys.exit(1)
else:
self._add_network(entry)
LOG.debug("network VLAN ranges: %s" % self.network_vlan_ranges)
def _add_network_vlan_range(self, physical_network, vlan_min, vlan_max):
self._add_network(physical_network)
self.network_vlan_ranges[physical_network].append((vlan_min, vlan_max))
def _add_network(self, physical_network):
if physical_network not in self.network_vlan_ranges:
self.network_vlan_ranges[physical_network] = []
def _parse_tunnel_id_ranges(self):
self.tunnel_id_ranges = []
for entry in cfg.CONF.OVS.tunnel_id_ranges:
entry = entry.strip()
try:
tun_min, tun_max = entry.split(':')
self.tunnel_id_ranges.append((int(tun_min), int(tun_max)))
except ValueError as ex:
LOG.error("Invalid tunnel ID range: \'%s\' - %s" %
(entry, ex))
sys.exit(1)
LOG.debug("tunnel ID ranges: %s" % self.tunnel_id_ranges)
# TODO(rkukura) Use core mechanism for attribute authorization # TODO(rkukura) Use core mechanism for attribute authorization
# when available. # when available.
@ -229,9 +264,18 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
def _extend_network_dict(self, context, network): def _extend_network_dict(self, context, network):
if self._check_provider_view_auth(context, network): if self._check_provider_view_auth(context, network):
if not self.enable_tunneling: binding = ovs_db_v2.get_network_binding(context.session,
network['provider:vlan_id'] = ovs_db_v2.get_vlan( network['id'])
network['id'], context.session) network['provider:network_type'] = binding.network_type
if binding.network_type == constants.TYPE_GRE:
network['provider:physical_network'] = None
network['provider:vlan_id'] = None
elif binding.network_type == constants.TYPE_FLAT:
network['provider:physical_network'] = binding.physical_network
network['provider:vlan_id'] = None
elif binding.network_type == constants.TYPE_VLAN:
network['provider:physical_network'] = binding.physical_network
network['provider:vlan_id'] = binding.physical_id
def _process_provider_create(self, context, attrs): def _process_provider_create(self, context, attrs):
network_type = attrs.get('provider:network_type') network_type = attrs.get('provider:network_type')
@ -251,16 +295,13 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
if not network_type_set: if not network_type_set:
msg = _("provider:network_type required") msg = _("provider:network_type required")
raise q_exc.InvalidInput(error_message=msg) raise q_exc.InvalidInput(error_message=msg)
elif network_type == 'flat': elif network_type == constants.TYPE_FLAT:
msg = _("plugin does not support flat networks") if vlan_id_set:
raise q_exc.InvalidInput(error_message=msg) msg = _("provider:vlan_id specified for flat network")
# REVISIT(rkukura) to be enabled in phase 3 raise q_exc.InvalidInput(error_message=msg)
# if vlan_id_set: else:
# msg = _("provider:vlan_id specified for flat network") vlan_id = constants.FLAT_VLAN_ID
# raise q_exc.InvalidInput(error_message=msg) elif network_type == constants.TYPE_VLAN:
# else:
# vlan_id = db.FLAT_VLAN_ID
elif network_type == 'vlan':
if not vlan_id_set: if not vlan_id_set:
msg = _("provider:vlan_id required") msg = _("provider:vlan_id required")
raise q_exc.InvalidInput(error_message=msg) raise q_exc.InvalidInput(error_message=msg)
@ -269,18 +310,15 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
raise q_exc.InvalidInput(error_message=msg) raise q_exc.InvalidInput(error_message=msg)
if physical_network_set: if physical_network_set:
msg = _("plugin does not support specifying physical_network") if physical_network not in self.network_vlan_ranges:
msg = _("unknown provider:physical_network %s" %
physical_network)
raise q_exc.InvalidInput(error_message=msg)
elif 'default' in self.network_vlan_ranges:
physical_network = 'default'
else:
msg = _("provider:physical_network required")
raise q_exc.InvalidInput(error_message=msg) raise q_exc.InvalidInput(error_message=msg)
# REVISIT(rkukura) to be enabled in phase 3
# if physical_network not in self.physical_networks:
# msg = _("unknown provider:physical_network %s" %
# physical_network)
# raise q_exc.InvalidInput(error_message=msg)
#elif 'default' in self.physical_networks:
# physical_network = 'default'
#else:
# msg = _("provider:physical_network required")
# raise q_exc.InvalidInput(error_message=msg)
return (network_type, physical_network, vlan_id) return (network_type, physical_network, vlan_id)
@ -304,38 +342,58 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
def create_network(self, context, network): def create_network(self, context, network):
(network_type, physical_network, (network_type, physical_network,
vlan_id) = self._process_provider_create(context, physical_id) = self._process_provider_create(context,
network['network']) network['network'])
net = super(OVSQuantumPluginV2, self).create_network(context, network) session = context.session
try: with session.begin(subtransactions=True):
if not network_type: if not network_type:
vlan_id = ovs_db_v2.reserve_vlan_id(context.session) try:
(physical_network,
physical_id) = ovs_db_v2.reserve_vlan(session)
network_type = constants.TYPE_VLAN
except q_exc.NoNetworkAvailable:
physical_id = ovs_db_v2.reserve_tunnel(session)
network_type = constants.TYPE_GRE
else: else:
ovs_db_v2.reserve_specific_vlan_id(vlan_id, context.session) ovs_db_v2.reserve_specific_vlan(session, physical_network,
except Exception: physical_id)
super(OVSQuantumPluginV2, self).delete_network(context, net['id']) net = super(OVSQuantumPluginV2, self).create_network(context,
raise network)
ovs_db_v2.add_network_binding(session, net['id'], network_type,
physical_network, physical_id)
self._extend_network_dict(context, net)
# note - exception will rollback entire transaction
LOG.debug("Created network: %s" % net['id']) LOG.debug("Created network: %s" % net['id'])
ovs_db_v2.add_vlan_binding(vlan_id, str(net['id']), context.session)
self._extend_network_dict(context, net)
return net return net
def update_network(self, context, id, network): def update_network(self, context, id, network):
self._check_provider_update(context, network['network']) self._check_provider_update(context, network['network'])
net = super(OVSQuantumPluginV2, self).update_network(context, id, session = context.session
network) with session.begin(subtransactions=True):
self._extend_network_dict(context, net) net = super(OVSQuantumPluginV2, self).update_network(context, id,
network)
self._extend_network_dict(context, net)
return net return net
def delete_network(self, context, id): def delete_network(self, context, id):
vlan_id = ovs_db_v2.get_vlan(id) session = context.session
result = super(OVSQuantumPluginV2, self).delete_network(context, id) with session.begin(subtransactions=True):
ovs_db_v2.release_vlan_id(vlan_id) binding = ovs_db_v2.get_network_binding(session, id)
result = super(OVSQuantumPluginV2, self).delete_network(context,
id)
if binding.network_type == constants.TYPE_GRE:
ovs_db_v2.release_tunnel(session, binding.physical_id,
self.tunnel_id_ranges)
else:
ovs_db_v2.release_vlan(session, binding.physical_network,
binding.physical_id,
self.network_vlan_ranges)
# the network_binding record is deleted via cascade from
# the network record, so explicit removal is not necessary
if self.agent_rpc: if self.agent_rpc:
self.notifier.network_delete(self.context, id) self.notifier.network_delete(self.rpc_context, id)
return result return result
def get_network(self, context, id, fields=None): def get_network(self, context, id, fields=None):
@ -358,8 +416,11 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
port = super(OVSQuantumPluginV2, self).update_port(context, id, port) port = super(OVSQuantumPluginV2, self).update_port(context, id, port)
if self.agent_rpc: if self.agent_rpc:
if original_port['admin_state_up'] != port['admin_state_up']: if original_port['admin_state_up'] != port['admin_state_up']:
vlan_id = ovs_db_v2.get_vlan(port['network_id']) binding = ovs_db_v2.get_network_binding(None,
self.notifier.port_update(self.context, port, vlan_id) port['network_id'])
# REVISIT(rkukura): needs other binding data as well
self.notifier.port_update(self.rpc_context, port,
binding.physical_id)
return port return port
def delete_port(self, context, id): def delete_port(self, context, id):

View File

@ -17,96 +17,204 @@ import unittest2
from quantum.common import exceptions as q_exc from quantum.common import exceptions as q_exc
from quantum.db import api as db from quantum.db import api as db
from quantum.db import models_v2
from quantum.plugins.openvswitch.common import config
from quantum.openstack.common import cfg
from quantum.plugins.openvswitch import ovs_db_v2 from quantum.plugins.openvswitch import ovs_db_v2
PHYS_NET = 'physnet1'
VLAN_MIN = 10 VLAN_MIN = 10
VLAN_MAX = 19 VLAN_MAX = 19
VLAN_RANGES = {PHYS_NET: [(VLAN_MIN, VLAN_MAX)]}
UPDATED_VLAN_RANGES = {PHYS_NET: [(VLAN_MIN + 5, VLAN_MAX + 5)]}
TUN_MIN = 100
TUN_MAX = 109
TUNNEL_RANGES = [(TUN_MIN, TUN_MAX)]
UPDATED_TUNNEL_RANGES = [(TUN_MIN + 5, TUN_MAX + 5)]
TEST_NETWORK_ID = 'abcdefghijklmnopqrstuvwxyz'
class OVSVlanIdsTest(unittest2.TestCase): class VlanAllocationsTest(unittest2.TestCase):
def setUp(self): def setUp(self):
cfg.CONF.set_override('vlan_min', VLAN_MIN, group='OVS') ovs_db_v2.initialize()
cfg.CONF.set_override('vlan_max', VLAN_MAX, group='OVS') ovs_db_v2.sync_vlan_allocations(VLAN_RANGES)
self.session = db.get_session()
options = {"sql_connection": cfg.CONF.DATABASE.sql_connection}
options.update({'base': models_v2.model_base.BASEV2})
sql_max_retries = cfg.CONF.DATABASE.sql_max_retries
options.update({"sql_max_retries": sql_max_retries})
reconnect_interval = cfg.CONF.DATABASE.reconnect_interval
options.update({"reconnect_interval": reconnect_interval})
db.configure_db(options)
ovs_db_v2.update_vlan_id_pool()
def tearDown(self): def tearDown(self):
db.clear_db() db.clear_db()
cfg.CONF.reset()
def test_update_vlan_id_pool(self): def test_sync_vlan_allocations(self):
self.assertIsNone(ovs_db_v2.get_vlan_id(VLAN_MIN - 1)) self.assertIsNone(ovs_db_v2.get_vlan_allocation(PHYS_NET,
self.assertFalse(ovs_db_v2.get_vlan_id(VLAN_MIN).vlan_used) VLAN_MIN - 1))
self.assertFalse(ovs_db_v2.get_vlan_id(VLAN_MIN + 1).vlan_used) self.assertFalse(ovs_db_v2.get_vlan_allocation(PHYS_NET,
self.assertFalse(ovs_db_v2.get_vlan_id(VLAN_MAX).vlan_used) VLAN_MIN).allocated)
self.assertIsNone(ovs_db_v2.get_vlan_id(VLAN_MAX + 1)) self.assertFalse(ovs_db_v2.get_vlan_allocation(PHYS_NET,
VLAN_MIN + 1).allocated)
self.assertFalse(ovs_db_v2.get_vlan_allocation(PHYS_NET,
VLAN_MAX).allocated)
self.assertIsNone(ovs_db_v2.get_vlan_allocation(PHYS_NET,
VLAN_MAX + 1))
cfg.CONF.set_override('vlan_min', VLAN_MIN + 5, group='OVS') ovs_db_v2.sync_vlan_allocations(UPDATED_VLAN_RANGES)
cfg.CONF.set_override('vlan_max', VLAN_MAX + 5, group='OVS')
ovs_db_v2.update_vlan_id_pool()
self.assertIsNone(ovs_db_v2.get_vlan_id(VLAN_MIN + 5 - 1)) self.assertIsNone(ovs_db_v2.get_vlan_allocation(PHYS_NET,
self.assertFalse(ovs_db_v2.get_vlan_id(VLAN_MIN + 5).vlan_used) VLAN_MIN + 5 - 1))
self.assertFalse(ovs_db_v2.get_vlan_id(VLAN_MIN + 5 + 1).vlan_used) self.assertFalse(ovs_db_v2.get_vlan_allocation(PHYS_NET,
self.assertFalse(ovs_db_v2.get_vlan_id(VLAN_MAX + 5).vlan_used) VLAN_MIN + 5).
self.assertIsNone(ovs_db_v2.get_vlan_id(VLAN_MAX + 5 + 1)) allocated)
self.assertFalse(ovs_db_v2.get_vlan_allocation(PHYS_NET,
VLAN_MIN + 5 + 1).
allocated)
self.assertFalse(ovs_db_v2.get_vlan_allocation(PHYS_NET,
VLAN_MAX + 5 - 1).
allocated)
self.assertFalse(ovs_db_v2.get_vlan_allocation(PHYS_NET,
VLAN_MAX + 5).
allocated)
self.assertIsNone(ovs_db_v2.get_vlan_allocation(PHYS_NET,
VLAN_MAX + 5 + 1))
def test_vlan_id_pool(self): def test_vlan_pool(self):
session = db.get_session()
vlan_ids = set() vlan_ids = set()
for x in xrange(VLAN_MIN, VLAN_MAX + 1): for x in xrange(VLAN_MIN, VLAN_MAX + 1):
vlan_id = ovs_db_v2.reserve_vlan_id(db.get_session()) physical_network, vlan_id = ovs_db_v2.reserve_vlan(self.session)
self.assertEqual(physical_network, PHYS_NET)
self.assertGreaterEqual(vlan_id, VLAN_MIN) self.assertGreaterEqual(vlan_id, VLAN_MIN)
self.assertLessEqual(vlan_id, VLAN_MAX) self.assertLessEqual(vlan_id, VLAN_MAX)
vlan_ids.add(vlan_id) vlan_ids.add(vlan_id)
with self.assertRaises(q_exc.NoNetworkAvailable): with self.assertRaises(q_exc.NoNetworkAvailable):
vlan_id = ovs_db_v2.reserve_vlan_id(session) physical_network, vlan_id = ovs_db_v2.reserve_vlan(self.session)
ovs_db_v2.release_vlan(self.session, PHYS_NET, vlan_ids.pop(),
VLAN_RANGES)
physical_network, vlan_id = ovs_db_v2.reserve_vlan(self.session)
self.assertEqual(physical_network, PHYS_NET)
self.assertGreaterEqual(vlan_id, VLAN_MIN)
self.assertLessEqual(vlan_id, VLAN_MAX)
vlan_ids.add(vlan_id)
for vlan_id in vlan_ids: for vlan_id in vlan_ids:
ovs_db_v2.release_vlan_id(vlan_id) ovs_db_v2.release_vlan(self.session, PHYS_NET, vlan_id,
VLAN_RANGES)
def test_invalid_specific_vlan_id(self): def test_specific_vlan_inside_pool(self):
session = db.get_session()
with self.assertRaises(q_exc.InvalidInput):
vlan_id = ovs_db_v2.reserve_specific_vlan_id(0, session)
with self.assertRaises(q_exc.InvalidInput):
vlan_id = ovs_db_v2.reserve_specific_vlan_id(4095, session)
def test_specific_vlan_id_inside_pool(self):
session = db.get_session()
vlan_id = VLAN_MIN + 5 vlan_id = VLAN_MIN + 5
self.assertFalse(ovs_db_v2.get_vlan_id(vlan_id).vlan_used) self.assertFalse(ovs_db_v2.get_vlan_allocation(PHYS_NET,
ovs_db_v2.reserve_specific_vlan_id(vlan_id, session) vlan_id).allocated)
self.assertTrue(ovs_db_v2.get_vlan_id(vlan_id).vlan_used) ovs_db_v2.reserve_specific_vlan(self.session, PHYS_NET, vlan_id)
self.assertTrue(ovs_db_v2.get_vlan_allocation(PHYS_NET,
vlan_id).allocated)
with self.assertRaises(q_exc.VlanIdInUse): with self.assertRaises(q_exc.VlanIdInUse):
ovs_db_v2.reserve_specific_vlan_id(vlan_id, session) ovs_db_v2.reserve_specific_vlan(self.session, PHYS_NET, vlan_id)
ovs_db_v2.release_vlan_id(vlan_id) ovs_db_v2.release_vlan(self.session, PHYS_NET, vlan_id, VLAN_RANGES)
self.assertFalse(ovs_db_v2.get_vlan_id(vlan_id).vlan_used) self.assertFalse(ovs_db_v2.get_vlan_allocation(PHYS_NET,
vlan_id).allocated)
def test_specific_vlan_id_outside_pool(self): def test_specific_vlan_outside_pool(self):
session = db.get_session()
vlan_id = VLAN_MAX + 5 vlan_id = VLAN_MAX + 5
self.assertIsNone(ovs_db_v2.get_vlan_id(vlan_id)) self.assertIsNone(ovs_db_v2.get_vlan_allocation(PHYS_NET, vlan_id))
ovs_db_v2.reserve_specific_vlan_id(vlan_id, session) ovs_db_v2.reserve_specific_vlan(self.session, PHYS_NET, vlan_id)
self.assertTrue(ovs_db_v2.get_vlan_id(vlan_id).vlan_used) self.assertTrue(ovs_db_v2.get_vlan_allocation(PHYS_NET,
vlan_id).allocated)
with self.assertRaises(q_exc.VlanIdInUse): with self.assertRaises(q_exc.VlanIdInUse):
ovs_db_v2.reserve_specific_vlan_id(vlan_id, session) ovs_db_v2.reserve_specific_vlan(self.session, PHYS_NET, vlan_id)
ovs_db_v2.release_vlan_id(vlan_id) ovs_db_v2.release_vlan(self.session, PHYS_NET, vlan_id, VLAN_RANGES)
self.assertIsNone(ovs_db_v2.get_vlan_id(vlan_id)) self.assertIsNone(ovs_db_v2.get_vlan_allocation(PHYS_NET, vlan_id))
class TunnelAllocationsTest(unittest2.TestCase):
def setUp(self):
ovs_db_v2.initialize()
ovs_db_v2.sync_tunnel_allocations(TUNNEL_RANGES)
self.session = db.get_session()
def tearDown(self):
db.clear_db()
def test_sync_tunnel_allocations(self):
self.assertIsNone(ovs_db_v2.get_tunnel_allocation(TUN_MIN - 1))
self.assertFalse(ovs_db_v2.get_tunnel_allocation(TUN_MIN).allocated)
self.assertFalse(ovs_db_v2.get_tunnel_allocation(TUN_MIN + 1).
allocated)
self.assertFalse(ovs_db_v2.get_tunnel_allocation(TUN_MAX).allocated)
self.assertIsNone(ovs_db_v2.get_tunnel_allocation(TUN_MAX + 1))
ovs_db_v2.sync_tunnel_allocations(UPDATED_TUNNEL_RANGES)
self.assertIsNone(ovs_db_v2.get_tunnel_allocation(TUN_MIN + 5 - 1))
self.assertFalse(ovs_db_v2.get_tunnel_allocation(TUN_MIN + 5).
allocated)
self.assertFalse(ovs_db_v2.get_tunnel_allocation(TUN_MIN + 5 + 1).
allocated)
self.assertFalse(ovs_db_v2.get_tunnel_allocation(TUN_MAX + 5 - 1).
allocated)
self.assertFalse(ovs_db_v2.get_tunnel_allocation(TUN_MAX + 5).
allocated)
self.assertIsNone(ovs_db_v2.get_tunnel_allocation(TUN_MAX + 5 + 1))
def test_tunnel_pool(self):
tunnel_ids = set()
for x in xrange(TUN_MIN, TUN_MAX + 1):
tunnel_id = ovs_db_v2.reserve_tunnel(self.session)
self.assertGreaterEqual(tunnel_id, TUN_MIN)
self.assertLessEqual(tunnel_id, TUN_MAX)
tunnel_ids.add(tunnel_id)
with self.assertRaises(q_exc.NoNetworkAvailable):
tunnel_id = ovs_db_v2.reserve_tunnel(self.session)
ovs_db_v2.release_tunnel(self.session, tunnel_ids.pop(), TUNNEL_RANGES)
tunnel_id = ovs_db_v2.reserve_tunnel(self.session)
self.assertGreaterEqual(tunnel_id, TUN_MIN)
self.assertLessEqual(tunnel_id, TUN_MAX)
tunnel_ids.add(tunnel_id)
for tunnel_id in tunnel_ids:
ovs_db_v2.release_tunnel(self.session, tunnel_id, TUNNEL_RANGES)
def test_specific_tunnel_inside_pool(self):
tunnel_id = TUN_MIN + 5
self.assertFalse(ovs_db_v2.get_tunnel_allocation(tunnel_id).allocated)
ovs_db_v2.reserve_specific_tunnel(self.session, tunnel_id)
self.assertTrue(ovs_db_v2.get_tunnel_allocation(tunnel_id).allocated)
with self.assertRaises(q_exc.TunnelIdInUse):
ovs_db_v2.reserve_specific_tunnel(self.session, tunnel_id)
ovs_db_v2.release_tunnel(self.session, tunnel_id, TUNNEL_RANGES)
self.assertFalse(ovs_db_v2.get_tunnel_allocation(tunnel_id).allocated)
def test_specific_tunnel_outside_pool(self):
tunnel_id = TUN_MAX + 5
self.assertIsNone(ovs_db_v2.get_tunnel_allocation(tunnel_id))
ovs_db_v2.reserve_specific_tunnel(self.session, tunnel_id)
self.assertTrue(ovs_db_v2.get_tunnel_allocation(tunnel_id).allocated)
with self.assertRaises(q_exc.TunnelIdInUse):
ovs_db_v2.reserve_specific_tunnel(self.session, tunnel_id)
ovs_db_v2.release_tunnel(self.session, tunnel_id, TUNNEL_RANGES)
self.assertIsNone(ovs_db_v2.get_tunnel_allocation(tunnel_id))
class NetworkBindingsTest(unittest2.TestCase):
def setUp(self):
ovs_db_v2.initialize()
self.session = db.get_session()
def tearDown(self):
db.clear_db()
def test_add_network_binding(self):
self.assertIsNone(ovs_db_v2.get_network_binding(self.session,
TEST_NETWORK_ID))
ovs_db_v2.add_network_binding(self.session, TEST_NETWORK_ID, 'vlan',
PHYS_NET, 1234)
binding = ovs_db_v2.get_network_binding(self.session, TEST_NETWORK_ID)
self.assertIsNotNone(binding)
self.assertEqual(binding.network_id, TEST_NETWORK_ID)
self.assertEqual(binding.network_type, 'vlan')
self.assertEqual(binding.physical_network, PHYS_NET)
self.assertEqual(binding.physical_id, 1234)

View File

@ -22,7 +22,6 @@ from quantum.plugins.openvswitch.common import config
class ConfigurationTest(unittest.TestCase): class ConfigurationTest(unittest.TestCase):
def test_defaults(self): def test_defaults(self):
self.assertFalse(cfg.CONF.OVS.enable_tunneling)
self.assertEqual('br-int', cfg.CONF.OVS.integration_bridge) self.assertEqual('br-int', cfg.CONF.OVS.integration_bridge)
self.assertEqual('br-tun', cfg.CONF.OVS.tunnel_bridge) self.assertEqual('br-tun', cfg.CONF.OVS.tunnel_bridge)
self.assertEqual('sqlite://', cfg.CONF.DATABASE.sql_connection) self.assertEqual('sqlite://', cfg.CONF.DATABASE.sql_connection)
@ -30,3 +29,14 @@ class ConfigurationTest(unittest.TestCase):
self.assertEqual(2, cfg.CONF.DATABASE.reconnect_interval) self.assertEqual(2, cfg.CONF.DATABASE.reconnect_interval)
self.assertEqual(2, cfg.CONF.AGENT.polling_interval) self.assertEqual(2, cfg.CONF.AGENT.polling_interval)
self.assertEqual('sudo', cfg.CONF.AGENT.root_helper) self.assertEqual('sudo', cfg.CONF.AGENT.root_helper)
mappings = cfg.CONF.OVS.bridge_mappings
self.assertEqual(1, len(mappings))
self.assertEqual('default:br-eth1', mappings[0])
ranges = cfg.CONF.OVS.network_vlan_ranges
self.assertEqual(1, len(ranges))
self.assertEqual('default:1000:2999', ranges[0])
ranges = cfg.CONF.OVS.tunnel_id_ranges
self.assertEqual(0, len(ranges))

View File

@ -26,7 +26,7 @@ from quantum.common import topics
from quantum.openstack.common import context from quantum.openstack.common import context
from quantum.openstack.common import rpc from quantum.openstack.common import rpc
from quantum.plugins.openvswitch import ovs_quantum_plugin as povs from quantum.plugins.openvswitch import ovs_quantum_plugin as povs
from quantum.plugins.openvswitch.common import config from quantum.plugins.openvswitch.common import constants
class rpcApiTestCase(unittest2.TestCase): class rpcApiTestCase(unittest2.TestCase):
@ -81,7 +81,7 @@ class rpcApiTestCase(unittest2.TestCase):
rpcapi = povs.AgentNotifierApi(topics.AGENT) rpcapi = povs.AgentNotifierApi(topics.AGENT)
self._test_ovs_api(rpcapi, self._test_ovs_api(rpcapi,
topics.get_topic_name(topics.AGENT, topics.get_topic_name(topics.AGENT,
config.TUNNEL, constants.TUNNEL,
topics.UPDATE), topics.UPDATE),
'tunnel_update', rpc_method='fanout_cast', 'tunnel_update', rpc_method='fanout_cast',
tunnel_ip='fake_ip', tunnel_id='fake_id') tunnel_ip='fake_ip', tunnel_id='fake_id')

View File

@ -28,7 +28,7 @@ NET_UUID = '3faeebfe-5d37-11e1-a64b-000c29d5f0a7'
LS_ID = '42' LS_ID = '42'
LV_ID = 42 LV_ID = 42
LV_IDS = [42, 43] LV_IDS = [42, 43]
LVM = ovs_quantum_agent.LocalVLANMapping(LV_ID, LS_ID, LV_IDS) LVM = ovs_quantum_agent.LocalVLANMapping(LV_ID, 'gre', None, 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'
OFPORT_NUM = 1 OFPORT_NUM = 1
@ -79,10 +79,10 @@ class TunnelTest(unittest.TestCase):
def testConstruct(self): def testConstruct(self):
self.mox.ReplayAll() self.mox.ReplayAll()
b = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE, b = ovs_quantum_agent.OVSQuantumAgent(self.INT_BRIDGE,
self.TUN_BRIDGE, self.TUN_BRIDGE,
'10.0.0.1', '10.0.0.1', {},
'sudo', 2, 2, False) 'sudo', 2, 2, False)
self.mox.VerifyAll() self.mox.VerifyAll()
def testProvisionLocalVlan(self): def testProvisionLocalVlan(self):
@ -96,24 +96,24 @@ class TunnelTest(unittest.TestCase):
self.mox.ReplayAll() self.mox.ReplayAll()
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE, a = ovs_quantum_agent.OVSQuantumAgent(self.INT_BRIDGE,
self.TUN_BRIDGE, self.TUN_BRIDGE,
'10.0.0.1', '10.0.0.1', {},
'sudo', 2, 2, False) 'sudo', 2, 2, False)
a.available_local_vlans = set([LV_ID]) a.available_local_vlans = set([LV_ID])
a.provision_local_vlan(NET_UUID, LS_ID) a.provision_local_vlan(NET_UUID, 'gre', None, LS_ID)
self.mox.VerifyAll() self.mox.VerifyAll()
def testReclaimLocalVlan(self): def testReclaimLocalVlan(self):
self.mock_tun_bridge.delete_flows(tun_id=LVM.lsw_id) self.mock_tun_bridge.delete_flows(tun_id=LVM.physical_id)
self.mock_tun_bridge.delete_flows(dl_vlan=LVM.vlan) self.mock_tun_bridge.delete_flows(dl_vlan=LVM.vlan)
self.mox.ReplayAll() self.mox.ReplayAll()
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE, a = ovs_quantum_agent.OVSQuantumAgent(self.INT_BRIDGE,
self.TUN_BRIDGE, self.TUN_BRIDGE,
'10.0.0.1', '10.0.0.1', {},
'sudo', 2, 2, False) 'sudo', 2, 2, False)
a.available_local_vlans = set() a.available_local_vlans = set()
a.local_vlan_map[NET_UUID] = LVM a.local_vlan_map[NET_UUID] = LVM
a.reclaim_local_vlan(NET_UUID, LVM) a.reclaim_local_vlan(NET_UUID, LVM)
@ -131,20 +131,20 @@ class TunnelTest(unittest.TestCase):
actions=action_string) actions=action_string)
self.mox.ReplayAll() self.mox.ReplayAll()
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE, a = ovs_quantum_agent.OVSQuantumAgent(self.INT_BRIDGE,
self.TUN_BRIDGE, self.TUN_BRIDGE,
'10.0.0.1', '10.0.0.1', {},
'sudo', 2, 2, False) 'sudo', 2, 2, False)
a.local_vlan_map[NET_UUID] = LVM a.local_vlan_map[NET_UUID] = LVM
a.port_bound(VIF_PORT, NET_UUID, LS_ID) a.port_bound(VIF_PORT, NET_UUID, 'gre', None, LS_ID)
self.mox.VerifyAll() self.mox.VerifyAll()
def testPortUnbound(self): def testPortUnbound(self):
self.mox.ReplayAll() self.mox.ReplayAll()
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE, a = ovs_quantum_agent.OVSQuantumAgent(self.INT_BRIDGE,
self.TUN_BRIDGE, self.TUN_BRIDGE,
'10.0.0.1', '10.0.0.1', {},
'sudo', 2, 2, False) 'sudo', 2, 2, False)
a.available_local_vlans = set([LV_ID]) a.available_local_vlans = set([LV_ID])
a.local_vlan_map[NET_UUID] = LVM a.local_vlan_map[NET_UUID] = LVM
a.port_unbound(VIF_PORT, NET_UUID) a.port_unbound(VIF_PORT, NET_UUID)
@ -158,10 +158,10 @@ class TunnelTest(unittest.TestCase):
actions='drop') actions='drop')
self.mox.ReplayAll() self.mox.ReplayAll()
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE, a = ovs_quantum_agent.OVSQuantumAgent(self.INT_BRIDGE,
self.TUN_BRIDGE, self.TUN_BRIDGE,
'10.0.0.1', '10.0.0.1', {},
'sudo', 2, 2, False) 'sudo', 2, 2, False)
a.available_local_vlans = set([LV_ID]) a.available_local_vlans = set([LV_ID])
a.local_vlan_map[NET_UUID] = LVM a.local_vlan_map[NET_UUID] = LVM
a.port_dead(VIF_PORT) a.port_dead(VIF_PORT)