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:
parent
7632e67750
commit
c099e06a9a
@ -12,29 +12,38 @@ sql_connection = sqlite://
|
||||
reconnect_interval = 2
|
||||
|
||||
[OVS]
|
||||
# This enables the new OVSQuantumTunnelAgent which enables tunneling
|
||||
# between hybervisors. Leave it set to False or omit for legacy behavior.
|
||||
enable_tunneling = False
|
||||
# (ListOpt) Comma-separated list of
|
||||
# <physical_network>:<vlan_min>:<vlan_max> tuples enumerating ranges
|
||||
# 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.
|
||||
# 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
|
||||
|
||||
# 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.
|
||||
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.
|
||||
# local_ip = 10.0.0.3
|
||||
|
||||
# Uncomment if you want to use custom VLAN range.
|
||||
# vlan_min = 1
|
||||
# vlan_max = 4094
|
||||
|
||||
[AGENT]
|
||||
# Agent's polling interval in seconds
|
||||
polling_interval = 2
|
||||
@ -47,25 +56,26 @@ root_helper = sudo
|
||||
# Sample Configurations.
|
||||
#-----------------------------------------------------------------------------
|
||||
#
|
||||
# 1. Without tunneling.
|
||||
# 1. With VLANs on eth1.
|
||||
# [DATABASE]
|
||||
# sql_connection = mysql://root:nova@127.0.0.1:3306/ovs_quantum
|
||||
# [OVS]
|
||||
# enable_tunneling = False
|
||||
# network_vlan_ranges = default:2000:3999
|
||||
# tunnel_id_ranges =
|
||||
# integration_bridge = br-int
|
||||
# bridge_mappings = default:br-eth1
|
||||
# [AGENT]
|
||||
# root_helper = sudo
|
||||
# Add the following setting, if you want to log to a file
|
||||
# log_file = /var/log/quantum/ovs_quantum_agent.log
|
||||
#
|
||||
# 2. With tunneling.
|
||||
# [DATABASE]
|
||||
# sql_connection = mysql://root:nova@127.0.0.1:3306/ovs_quantum
|
||||
# [OVS]
|
||||
# enable_tunneling = True
|
||||
# network_vlan_ranges =
|
||||
# tunnel_id_ranges = 1:1000
|
||||
# integration_bridge = br-int
|
||||
# tunnel_bridge = br-tun
|
||||
# remote-ip-file = /opt/stack/remote-ips.txt
|
||||
# local_ip = 10.0.0.3
|
||||
# [AGENT]
|
||||
# root_helper = sudo
|
||||
|
@ -71,6 +71,11 @@ class OVSBridge:
|
||||
self.run_vsctl(["--", "--if-exists", "del-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):
|
||||
self.run_vsctl(["--", "--if-exists", "del-port", self.br_name,
|
||||
port_name])
|
||||
|
@ -127,6 +127,11 @@ class VlanIdInUse(InUse):
|
||||
"%(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):
|
||||
pass
|
||||
|
||||
|
@ -28,9 +28,10 @@ import eventlet
|
||||
from sqlalchemy.ext import sqlsoup
|
||||
|
||||
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 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 topics
|
||||
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.rpc import dispatcher
|
||||
from quantum.plugins.openvswitch.common import config
|
||||
from quantum.plugins.openvswitch.common import constants
|
||||
|
||||
logging.basicConfig()
|
||||
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'
|
||||
# attributes set).
|
||||
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:
|
||||
vif_ids = []
|
||||
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
|
||||
|
||||
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):
|
||||
@ -142,266 +149,30 @@ class OVSRpcCallbacks():
|
||||
|
||||
|
||||
class OVSQuantumAgent(object):
|
||||
'''Implements OVS-based tunneling, VLANs and flat networks.
|
||||
|
||||
def __init__(self, integ_br, root_helper, polling_interval,
|
||||
reconnect_interval, rpc):
|
||||
self.root_helper = root_helper
|
||||
self.setup_integration_br(integ_br)
|
||||
self.polling_interval = polling_interval
|
||||
self.reconnect_interval = reconnect_interval
|
||||
self.rpc = rpc
|
||||
if rpc:
|
||||
self.setup_rpc(integ_br)
|
||||
Two local bridges are created: an integration bridge (defaults to
|
||||
'br-int') and a tunneling bridge (defaults to 'br-tun'). An
|
||||
additional bridge is created for each physical network interface
|
||||
used for VLANs and/or flat networks.
|
||||
|
||||
def setup_rpc(self, integ_br):
|
||||
mac = utils.get_interface_mac(integ_br)
|
||||
self.agent_id = '%s' % (mac.replace(":", ""))
|
||||
self.topic = topics.AGENT
|
||||
self.plugin_rpc = agent_rpc.PluginApi(topics.PLUGIN)
|
||||
All VM VIFs are plugged into the integration bridge. VM VIFs on a
|
||||
given virtual network share a common "local" VLAN (i.e. not
|
||||
propagated externally). The VLAN id of this local VLAN is mapped
|
||||
to the physical networking details realizing that virtual network.
|
||||
|
||||
# RPC network init
|
||||
self.context = context.RequestContext('quantum', 'quantum',
|
||||
is_admin=False)
|
||||
# Handle updates from service
|
||||
self.callbacks = OVSRpcCallbacks(self.context, self.int_br)
|
||||
self.dispatcher = self.callbacks.create_rpc_dispatcher()
|
||||
# Define the listening consumers for the agent
|
||||
consumers = [[topics.PORT, topics.UPDATE],
|
||||
[topics.NETWORK, topics.DELETE]]
|
||||
self.connection = agent_rpc.create_consumers(self.dispatcher,
|
||||
self.topic,
|
||||
consumers)
|
||||
For virtual networks realized as GRE tunnels, 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.
|
||||
|
||||
def port_bound(self, port, vlan_id):
|
||||
self.int_br.set_db_attribute("Port", port.port_name,
|
||||
"tag", str(vlan_id))
|
||||
self.int_br.delete_flows(in_port=port.ofport)
|
||||
|
||||
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.
|
||||
For each virtual networks realized as a VLANs or flat network, a
|
||||
veth is used to connect the local VLAN on the integration bridge
|
||||
with the physical network bridge, with flow rules adding,
|
||||
modifying, or stripping VLAN tags as necessary.
|
||||
'''
|
||||
|
||||
# Lower bound on available vlans.
|
||||
@ -410,13 +181,15 @@ class OVSQuantumTunnelAgent(object):
|
||||
# Upper bound on available vlans.
|
||||
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):
|
||||
'''Constructor.
|
||||
|
||||
:param integ_br: name of the integration bridge.
|
||||
:param tun_br: name of the tunnel bridge.
|
||||
: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 polling_interval: interval (secs) to poll DB.
|
||||
:param reconnect_internal: retry interval (secs) on DB error.
|
||||
@ -424,9 +197,10 @@ class OVSQuantumTunnelAgent(object):
|
||||
'''
|
||||
self.root_helper = root_helper
|
||||
self.available_local_vlans = set(
|
||||
xrange(OVSQuantumTunnelAgent.MIN_VLAN_TAG,
|
||||
OVSQuantumTunnelAgent.MAX_VLAN_TAG))
|
||||
xrange(OVSQuantumAgent.MIN_VLAN_TAG,
|
||||
OVSQuantumAgent.MAX_VLAN_TAG))
|
||||
self.setup_integration_br(integ_br)
|
||||
self.setup_physical_bridges(bridge_mappings)
|
||||
self.local_vlan_map = {}
|
||||
|
||||
self.polling_interval = polling_interval
|
||||
@ -435,6 +209,7 @@ class OVSQuantumTunnelAgent(object):
|
||||
self.local_ip = local_ip
|
||||
self.tunnel_count = 0
|
||||
self.setup_tunnel_br(tun_br)
|
||||
|
||||
self.rpc = rpc
|
||||
if rpc:
|
||||
self.setup_rpc(integ_br)
|
||||
@ -455,31 +230,66 @@ class OVSQuantumTunnelAgent(object):
|
||||
# Define the listening consumers for the agent
|
||||
consumers = [[topics.PORT, topics.UPDATE],
|
||||
[topics.NETWORK, topics.DELETE],
|
||||
[config.TUNNEL, topics.UPDATE]]
|
||||
[constants.TUNNEL, topics.UPDATE]]
|
||||
self.connection = agent_rpc.create_consumers(self.dispatcher,
|
||||
self.topic,
|
||||
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.
|
||||
|
||||
: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:
|
||||
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()
|
||||
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
|
||||
self.tun_br.add_flow(priority=4, in_port=self.patch_int_ofport,
|
||||
dl_vlan=lvid,
|
||||
actions="set_tunnel:%s,normal" % lsw_id)
|
||||
# inbound bcast/mcast
|
||||
self.tun_br.add_flow(priority=3, tun_id=lsw_id,
|
||||
dl_dst="01:00:00:00:00:00/01:00:00:00:00:00",
|
||||
actions="mod_vlan_vid:%s,output:%s" %
|
||||
(lvid, self.patch_int_ofport))
|
||||
if network_type == constants.TYPE_GRE:
|
||||
# outbound
|
||||
self.tun_br.add_flow(priority=4, in_port=self.patch_int_ofport,
|
||||
dl_vlan=lvid,
|
||||
actions="set_tunnel:%s,normal" % physical_id)
|
||||
# inbound bcast/mcast
|
||||
self.tun_br.add_flow(priority=3, tun_id=physical_id,
|
||||
dl_dst="01:00:00:00:00:00/01:00:00:00:00:00",
|
||||
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):
|
||||
'''Reclaim a local VLAN.
|
||||
@ -488,27 +298,57 @@ class OVSQuantumTunnelAgent(object):
|
||||
: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(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]
|
||||
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
|
||||
to vm.
|
||||
|
||||
:param port: a ovslib.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.
|
||||
: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:
|
||||
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.vif_ids.append(port.vif_id)
|
||||
|
||||
# inbound unicast
|
||||
self.tun_br.add_flow(priority=3, tun_id=lsw_id, dl_dst=port.vif_mac,
|
||||
actions="mod_vlan_vid:%s,normal" % lvm.vlan)
|
||||
if network_type == constants.TYPE_GRE:
|
||||
# inbound unicast
|
||||
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",
|
||||
str(lvm.vlan))
|
||||
@ -529,6 +369,8 @@ class OVSQuantumTunnelAgent(object):
|
||||
return
|
||||
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:
|
||||
lvm.vif_ids.remove(port.vif_id)
|
||||
else:
|
||||
@ -573,11 +415,57 @@ class OVSQuantumTunnelAgent(object):
|
||||
self.tun_br.remove_all_flows()
|
||||
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):
|
||||
if self.local_ip in tunnel_ips:
|
||||
tunnel_ips.remove(self.local_ip)
|
||||
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
|
||||
if new_tunnel_ips:
|
||||
@ -614,10 +502,11 @@ class OVSQuantumTunnelAgent(object):
|
||||
all_bindings = dict((p.id, Port(p))
|
||||
for p in db.ports.all())
|
||||
all_bindings_vif_port_ids = set(all_bindings)
|
||||
lsw_id_bindings = dict((bind.network_id, bind.vlan_id)
|
||||
for bind in db.vlan_bindings.all())
|
||||
net_bindings = dict((bind.network_id, bind)
|
||||
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)
|
||||
|
||||
# Get bindings from OVS bridge.
|
||||
@ -642,7 +531,7 @@ class OVSQuantumTunnelAgent(object):
|
||||
if b[2] != b[1]])
|
||||
|
||||
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('dead_vif_ports_ids: %s', dead_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)
|
||||
if p.vif_id in all_bindings:
|
||||
all_bindings[p.vif_id].status = (
|
||||
constants.PORT_STATUS_DOWN)
|
||||
q_const.PORT_STATUS_DOWN)
|
||||
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)
|
||||
if new_net_uuid not in net_bindings:
|
||||
LOG.warn("No network binding found for net-id"
|
||||
" '%s'" % new_net_uuid)
|
||||
continue
|
||||
|
||||
lsw_id = lsw_id_bindings[new_net_uuid]
|
||||
self.port_bound(p, new_net_uuid, lsw_id)
|
||||
bind = net_bindings[new_net_uuid]
|
||||
self.port_bound(p, new_net_uuid,
|
||||
bind.network_type,
|
||||
bind.physical_network,
|
||||
bind.physical_id)
|
||||
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 " % (
|
||||
str(p), new_net_uuid,
|
||||
str(self.local_vlan_map[new_net_uuid])))
|
||||
@ -692,7 +584,7 @@ class OVSQuantumTunnelAgent(object):
|
||||
LOG.info("Port Disappeared: " + vif_id)
|
||||
if vif_id in all_bindings:
|
||||
all_bindings[vif_id].status = (
|
||||
constants.PORT_STATUS_DOWN)
|
||||
q_const.PORT_STATUS_DOWN)
|
||||
old_port = old_local_bindings.get(vif_id)
|
||||
if old_port:
|
||||
self.port_unbound(old_vif_ports[vif_id],
|
||||
@ -739,7 +631,9 @@ class OVSQuantumTunnelAgent(object):
|
||||
if port:
|
||||
if details['admin_state_up']:
|
||||
self.port_bound(port, details['network_id'],
|
||||
details['vlan_id'])
|
||||
details['network_type'],
|
||||
details['physical_network'],
|
||||
details['physical_id'])
|
||||
else:
|
||||
self.port_unbound(port, details['network_id'])
|
||||
else:
|
||||
@ -835,27 +729,32 @@ def main():
|
||||
# (TODO) gary - swap with common logging
|
||||
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
|
||||
db_connection_url = cfg.CONF.DATABASE.sql_connection
|
||||
polling_interval = cfg.CONF.AGENT.polling_interval
|
||||
reconnect_interval = cfg.CONF.DATABASE.reconnect_interval
|
||||
root_helper = cfg.CONF.AGENT.root_helper
|
||||
rpc = cfg.CONF.AGENT.rpc
|
||||
tun_br = cfg.CONF.OVS.tunnel_bridge
|
||||
local_ip = cfg.CONF.OVS.local_ip
|
||||
|
||||
if enable_tunneling:
|
||||
# Get parameters for OVSQuantumTunnelAgent
|
||||
tun_br = cfg.CONF.OVS.tunnel_bridge
|
||||
# Mandatory parameter.
|
||||
local_ip = cfg.CONF.OVS.local_ip
|
||||
plugin = OVSQuantumTunnelAgent(integ_br, tun_br, local_ip, root_helper,
|
||||
polling_interval, reconnect_interval,
|
||||
rpc)
|
||||
else:
|
||||
# Get parameters for OVSQuantumAgent.
|
||||
plugin = OVSQuantumAgent(integ_br, root_helper, polling_interval,
|
||||
reconnect_interval, rpc)
|
||||
bridge_mappings = {}
|
||||
for mapping in cfg.CONF.OVS.bridge_mappings:
|
||||
mapping = mapping.strip()
|
||||
if mapping != '':
|
||||
try:
|
||||
physical_network, bridge = mapping.split(':')
|
||||
bridge_mappings[physical_network] = bridge
|
||||
LOG.debug("physical network %s mapped to bridge %s" %
|
||||
(physical_network, bridge))
|
||||
except ValueError as ex:
|
||||
LOG.error("Invalid bridge mapping: \'%s\' - %s" %
|
||||
(mapping, ex))
|
||||
sys.exit(1)
|
||||
|
||||
plugin = OVSQuantumAgent(integ_br, tun_br, local_ip, bridge_mappings,
|
||||
root_helper, polling_interval,
|
||||
reconnect_interval, rpc)
|
||||
|
||||
# Start everything.
|
||||
plugin.daemon_loop(db_connection_url)
|
||||
|
@ -17,8 +17,9 @@
|
||||
from quantum.openstack.common import cfg
|
||||
|
||||
|
||||
# Topic for tunnel notifications between the plugin and agent
|
||||
TUNNEL = 'tunnel'
|
||||
DEFAULT_BRIDGE_MAPPINGS = ['default:br-eth1']
|
||||
DEFAULT_VLAN_RANGES = ['default:1000:2999']
|
||||
DEFAULT_TUNNEL_RANGES = []
|
||||
|
||||
database_opts = [
|
||||
cfg.StrOpt('sql_connection', default='sqlite://'),
|
||||
@ -27,18 +28,24 @@ database_opts = [
|
||||
]
|
||||
|
||||
ovs_opts = [
|
||||
cfg.BoolOpt('enable_tunneling', default=False),
|
||||
cfg.StrOpt('integration_bridge', default='br-int'),
|
||||
cfg.StrOpt('tunnel_bridge', default='br-tun'),
|
||||
cfg.StrOpt('local_ip', default='10.0.0.3'),
|
||||
cfg.IntOpt('vlan_min', default=1),
|
||||
cfg.IntOpt('vlan_max', default=4094),
|
||||
cfg.ListOpt('bridge_mappings',
|
||||
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 = [
|
||||
cfg.IntOpt('polling_interval', default=2),
|
||||
cfg.StrOpt('root_helper', default='sudo'),
|
||||
cfg.StrOpt('log_file', default=None),
|
||||
cfg.BoolOpt('rpc', default=True),
|
||||
]
|
||||
|
||||
|
30
quantum/plugins/openvswitch/common/constants.py
Normal file
30
quantum/plugins/openvswitch/common/constants.py
Normal 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-'
|
@ -30,146 +30,242 @@ from quantum.plugins.openvswitch import ovs_models_v2
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_vlans():
|
||||
session = db.get_session()
|
||||
try:
|
||||
bindings = (session.query(ovs_models_v2.VlanBinding).
|
||||
all())
|
||||
except exc.NoResultFound:
|
||||
return []
|
||||
return [(binding.vlan_id, binding.network_id) for binding in bindings]
|
||||
def initialize():
|
||||
options = {"sql_connection": "%s" % cfg.CONF.DATABASE.sql_connection}
|
||||
options.update({"sql_max_retries": cfg.CONF.DATABASE.sql_max_retries})
|
||||
options.update({"reconnect_interval":
|
||||
cfg.CONF.DATABASE.reconnect_interval})
|
||||
options.update({"base": models_v2.model_base.BASEV2})
|
||||
db.configure_db(options)
|
||||
|
||||
|
||||
def get_vlan(net_id, session=None):
|
||||
def get_network_binding(session, network_id):
|
||||
session = session or db.get_session()
|
||||
try:
|
||||
binding = (session.query(ovs_models_v2.VlanBinding).
|
||||
filter_by(network_id=net_id).
|
||||
binding = (session.query(ovs_models_v2.NetworkBinding).
|
||||
filter_by(network_id=network_id).
|
||||
one())
|
||||
return binding
|
||||
except exc.NoResultFound:
|
||||
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):
|
||||
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)
|
||||
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()
|
||||
try:
|
||||
binding = (session.query(ovs_models_v2.VlanBinding).
|
||||
filter_by(network_id=net_id).
|
||||
one())
|
||||
session.delete(binding)
|
||||
alloc = (session.query(ovs_models_v2.VlanAllocation).
|
||||
filter_by(physical_network=physical_network,
|
||||
vlan_id=vlan_id).
|
||||
one())
|
||||
return alloc
|
||||
except exc.NoResultFound:
|
||||
pass
|
||||
session.flush()
|
||||
return
|
||||
|
||||
|
||||
def update_vlan_id_pool():
|
||||
"""Update vlan_ids based on current configuration."""
|
||||
def reserve_vlan(session):
|
||||
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,
|
||||
cfg.CONF.OVS.vlan_max + 1))
|
||||
|
||||
def reserve_specific_vlan(session, physical_network, vlan_id):
|
||||
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()
|
||||
with session.begin(subtransactions=True):
|
||||
# remove unused vlan_ids outside current range
|
||||
with session.begin():
|
||||
# remove from table unallocated tunnels not currently allocatable
|
||||
try:
|
||||
records = (session.query(ovs_models_v2.VlanID).
|
||||
all())
|
||||
for record in records:
|
||||
allocs = (session.query(ovs_models_v2.TunnelAllocation).
|
||||
all())
|
||||
for alloc in allocs:
|
||||
try:
|
||||
vlans.remove(record.vlan_id)
|
||||
# see if tunnel is allocatable
|
||||
tunnel_ids.remove(alloc.tunnel_id)
|
||||
except KeyError:
|
||||
if not record.vlan_used:
|
||||
LOG.debug("removing vlan %s from pool"
|
||||
% record.vlan_id)
|
||||
session.delete(record)
|
||||
# it's not allocatable, so check if its allocated
|
||||
if not alloc.allocated:
|
||||
# it's not, so remove it from table
|
||||
LOG.debug("removing tunnel %s from pool" %
|
||||
alloc.tunnel_id)
|
||||
session.delete(alloc)
|
||||
except exc.NoResultFound:
|
||||
pass
|
||||
|
||||
# add missing vlan_ids
|
||||
for vlan in vlans:
|
||||
record = ovs_models_v2.VlanID(vlan)
|
||||
session.add(record)
|
||||
# add missing allocatable tunnels to table
|
||||
for tunnel_id in sorted(tunnel_ids):
|
||||
alloc = ovs_models_v2.TunnelAllocation(tunnel_id)
|
||||
session.add(alloc)
|
||||
|
||||
|
||||
def get_vlan_id(vlan_id):
|
||||
"""Get state of specified vlan"""
|
||||
|
||||
def get_tunnel_allocation(tunnel_id):
|
||||
session = db.get_session()
|
||||
try:
|
||||
record = (session.query(ovs_models_v2.VlanID).
|
||||
filter_by(vlan_id=vlan_id).
|
||||
one())
|
||||
return record
|
||||
alloc = (session.query(ovs_models_v2.TunnelAllocation).
|
||||
filter_by(tunnel_id=tunnel_id).
|
||||
one())
|
||||
return alloc
|
||||
except exc.NoResultFound:
|
||||
return None
|
||||
return
|
||||
|
||||
|
||||
def reserve_vlan_id(session):
|
||||
"""Reserve an unused vlan_id"""
|
||||
|
||||
def reserve_tunnel(session):
|
||||
with session.begin(subtransactions=True):
|
||||
record = (session.query(ovs_models_v2.VlanID).
|
||||
filter_by(vlan_used=False).
|
||||
first())
|
||||
if not record:
|
||||
raise q_exc.NoNetworkAvailable()
|
||||
LOG.debug("reserving vlan %s from pool" % record.vlan_id)
|
||||
record.vlan_used = True
|
||||
return record.vlan_id
|
||||
alloc = (session.query(ovs_models_v2.TunnelAllocation).
|
||||
filter_by(allocated=False).
|
||||
first())
|
||||
if alloc:
|
||||
LOG.debug("reserving tunnel %s from pool" % alloc.tunnel_id)
|
||||
alloc.allocated = True
|
||||
return alloc.tunnel_id
|
||||
raise q_exc.NoNetworkAvailable()
|
||||
|
||||
|
||||
def reserve_specific_vlan_id(vlan_id, session):
|
||||
"""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)
|
||||
|
||||
def reserve_specific_tunnel(session, tunnel_id):
|
||||
with session.begin(subtransactions=True):
|
||||
try:
|
||||
record = (session.query(ovs_models_v2.VlanID).
|
||||
filter_by(vlan_id=vlan_id).
|
||||
one())
|
||||
if record.vlan_used:
|
||||
# REVISIT(rkukura) pass phyiscal_network
|
||||
raise q_exc.VlanIdInUse(vlan_id=vlan_id,
|
||||
physical_network='default')
|
||||
LOG.debug("reserving specific vlan %s from pool" % vlan_id)
|
||||
record.vlan_used = True
|
||||
alloc = (session.query(ovs_models_v2.TunnelAllocation).
|
||||
filter_by(tunnel_id=tunnel_id).
|
||||
one())
|
||||
if alloc.allocated:
|
||||
raise q_exc.TunnelIdInUse(tunnel_id=tunnel_id)
|
||||
LOG.debug("reserving specific tunnel %s from pool" % tunnel_id)
|
||||
alloc.allocated = True
|
||||
except exc.NoResultFound:
|
||||
LOG.debug("reserving specific vlan %s outside pool" % vlan_id)
|
||||
record = ovs_models_v2.VlanID(vlan_id)
|
||||
record.vlan_used = True
|
||||
session.add(record)
|
||||
LOG.debug("reserving specific tunnel %s outside pool" % tunnel_id)
|
||||
alloc = ovs_models_v2.TunnelAllocation(tunnel_id)
|
||||
alloc.allocated = True
|
||||
session.add(alloc)
|
||||
|
||||
|
||||
def release_vlan_id(vlan_id):
|
||||
"""Set the vlan state to be unused, and delete if not in range"""
|
||||
|
||||
session = db.get_session()
|
||||
def release_tunnel(session, tunnel_id, tunnel_id_ranges):
|
||||
with session.begin(subtransactions=True):
|
||||
try:
|
||||
record = (session.query(ovs_models_v2.VlanID).
|
||||
filter_by(vlan_id=vlan_id).
|
||||
one())
|
||||
record.vlan_used = False
|
||||
if (vlan_id >= cfg.CONF.OVS.vlan_min and
|
||||
vlan_id <= cfg.CONF.OVS.vlan_max):
|
||||
LOG.debug("releasing vlan %s to pool" % vlan_id)
|
||||
else:
|
||||
LOG.debug("removing vlan %s outside pool" % vlan_id)
|
||||
session.delete(record)
|
||||
alloc = (session.query(ovs_models_v2.TunnelAllocation).
|
||||
filter_by(tunnel_id=tunnel_id).
|
||||
one())
|
||||
alloc.allocated = False
|
||||
inside = False
|
||||
for tunnel_id_range in tunnel_id_ranges:
|
||||
if (tunnel_id >= tunnel_id_range[0]
|
||||
and tunnel_id <= tunnel_id_range[1]):
|
||||
inside = True
|
||||
break
|
||||
if not inside:
|
||||
session.delete(alloc)
|
||||
LOG.debug("releasing tunnel %s %s pool" %
|
||||
(tunnel_id, inside and "to" or "outside"))
|
||||
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):
|
||||
@ -192,19 +288,19 @@ def set_port_status(port_id, status):
|
||||
raise q_exc.PortNotFound(port_id=port_id)
|
||||
|
||||
|
||||
def get_tunnels():
|
||||
def get_tunnel_endpoints():
|
||||
session = db.get_session()
|
||||
try:
|
||||
tunnels = session.query(ovs_models_v2.TunnelInfo).all()
|
||||
tunnels = session.query(ovs_models_v2.TunnelEndpoint).all()
|
||||
except exc.NoResultFound:
|
||||
return []
|
||||
return [{'id': tunnel.id,
|
||||
'ip_address': tunnel.ip_address} for tunnel in tunnels]
|
||||
|
||||
|
||||
def generate_tunnel_id(session):
|
||||
def _generate_tunnel_id(session):
|
||||
try:
|
||||
tunnels = session.query(ovs_models_v2.TunnelInfo).all()
|
||||
tunnels = session.query(ovs_models_v2.TunnelEndpoint).all()
|
||||
except exc.NoResultFound:
|
||||
return 0
|
||||
tunnel_ids = ([tunnel['id'] for tunnel in tunnels])
|
||||
@ -215,15 +311,14 @@ def generate_tunnel_id(session):
|
||||
return id + 1
|
||||
|
||||
|
||||
def add_tunnel(ip):
|
||||
def add_tunnel_endpoint(ip):
|
||||
session = db.get_session()
|
||||
try:
|
||||
tunnel = (session.query(ovs_models_v2.TunnelInfo).
|
||||
tunnel = (session.query(ovs_models_v2.TunnelEndpoint).
|
||||
filter_by(ip_address=ip).one())
|
||||
except exc.NoResultFound:
|
||||
# Generate an id for the tunnel
|
||||
id = generate_tunnel_id(session)
|
||||
tunnel = ovs_models_v2.TunnelInfo(ip, id)
|
||||
id = _generate_tunnel_id(session)
|
||||
tunnel = ovs_models_v2.TunnelEndpoint(ip, id)
|
||||
session.add(tunnel)
|
||||
session.flush()
|
||||
return tunnel
|
||||
|
@ -22,41 +22,69 @@ from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
|
||||
from quantum.db.models_v2 import model_base
|
||||
|
||||
|
||||
class VlanID(model_base.BASEV2):
|
||||
"""Represents a vlan_id usage"""
|
||||
__tablename__ = 'vlan_ids'
|
||||
class VlanAllocation(model_base.BASEV2):
|
||||
"""Represents allocation state of vlan_id on physical network"""
|
||||
__tablename__ = 'ovs_vlan_allocations'
|
||||
|
||||
vlan_id = Column(Integer, nullable=False, primary_key=True)
|
||||
vlan_used = Column(Boolean, nullable=False)
|
||||
physical_network = Column(String(64), nullable=False, primary_key=True)
|
||||
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_used = False
|
||||
self.allocated = False
|
||||
|
||||
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):
|
||||
"""Represents a binding of network_id to vlan_id."""
|
||||
__tablename__ = 'vlan_bindings'
|
||||
class TunnelAllocation(model_base.BASEV2):
|
||||
"""Represents allocation state of tunnel_id"""
|
||||
__tablename__ = 'ovs_tunnel_allocations'
|
||||
|
||||
network_id = Column(String(36), ForeignKey('networks.id',
|
||||
ondelete="CASCADE"),
|
||||
tunnel_id = Column(Integer, nullable=False, primary_key=True,
|
||||
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)
|
||||
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.vlan_id = vlan_id
|
||||
self.network_type = network_type
|
||||
self.physical_network = physical_network
|
||||
self.physical_id = physical_id
|
||||
|
||||
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):
|
||||
"""Represents a remote IP in tunnel mode."""
|
||||
__tablename__ = 'tunnel_ips'
|
||||
"""Represents tunnel endpoint in DB mode"""
|
||||
__tablename__ = 'ovs_tunnel_ips'
|
||||
|
||||
ip_address = Column(String(255), primary_key=True)
|
||||
|
||||
@ -67,9 +95,9 @@ class TunnelIP(model_base.BASEV2):
|
||||
return "<TunnelIP(%s)>" % (self.ip_address)
|
||||
|
||||
|
||||
class TunnelInfo(model_base.BASEV2):
|
||||
"""Represents remote tunnel information in tunnel mode."""
|
||||
__tablename__ = 'tunnel_info'
|
||||
class TunnelEndpoint(model_base.BASEV2):
|
||||
"""Represents tunnel endpoint in RPC mode"""
|
||||
__tablename__ = 'ovs_tunnel_endpoints'
|
||||
|
||||
ip_address = Column(String(64), primary_key=True)
|
||||
id = Column(Integer, nullable=False)
|
||||
@ -79,4 +107,4 @@ class TunnelInfo(model_base.BASEV2):
|
||||
self.id = id
|
||||
|
||||
def __repr__(self):
|
||||
return "<TunnelInfo(%s,%s)>" % (self.ip_address, self.id)
|
||||
return "<TunnelEndpoint(%s,%s)>" % (self.ip_address, self.id)
|
||||
|
@ -22,22 +22,22 @@
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
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 topics
|
||||
from quantum.db import api as db
|
||||
from quantum.db import db_base_plugin_v2
|
||||
from quantum.db import dhcp_rpc_base
|
||||
from quantum.db import l3_db
|
||||
from quantum.db import models_v2
|
||||
from quantum.openstack.common import context
|
||||
from quantum.openstack.common import cfg
|
||||
from quantum.openstack.common import rpc
|
||||
from quantum.openstack.common.rpc import dispatcher
|
||||
from quantum.openstack.common.rpc import proxy
|
||||
from quantum.plugins.openvswitch.common import config
|
||||
from quantum.plugins.openvswitch.common import constants
|
||||
from quantum.plugins.openvswitch import ovs_db_v2
|
||||
from quantum import policy
|
||||
|
||||
@ -50,8 +50,8 @@ class OVSRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin):
|
||||
# Set RPC API version to 1.0 by default.
|
||||
RPC_API_VERSION = '1.0'
|
||||
|
||||
def __init__(self, context, notifier):
|
||||
self.context = context
|
||||
def __init__(self, rpc_context, notifier):
|
||||
self.rpc_context = rpc_context
|
||||
self.notifier = notifier
|
||||
|
||||
def create_rpc_dispatcher(self):
|
||||
@ -62,27 +62,29 @@ class OVSRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin):
|
||||
'''
|
||||
return dispatcher.RpcDispatcher([self])
|
||||
|
||||
def get_device_details(self, context, **kwargs):
|
||||
def get_device_details(self, rpc_context, **kwargs):
|
||||
"""Agent requests device details"""
|
||||
agent_id = kwargs.get('agent_id')
|
||||
device = kwargs.get('device')
|
||||
LOG.debug("Device %s details requested from %s", device, agent_id)
|
||||
port = ovs_db_v2.get_port(device)
|
||||
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,
|
||||
'vlan_id': vlan_id,
|
||||
'network_id': port['network_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
|
||||
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:
|
||||
entry = {'device': device}
|
||||
LOG.debug("%s can not be found in database", device)
|
||||
return entry
|
||||
|
||||
def update_device_down(self, context, **kwargs):
|
||||
def update_device_down(self, rpc_context, **kwargs):
|
||||
"""Device no longer exists on agent"""
|
||||
# (TODO) garyk - live migration and port status
|
||||
agent_id = kwargs.get('agent_id')
|
||||
@ -93,14 +95,14 @@ class OVSRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin):
|
||||
entry = {'device': device,
|
||||
'exists': True}
|
||||
# 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:
|
||||
entry = {'device': device,
|
||||
'exists': False}
|
||||
LOG.debug("%s can not be found in database", device)
|
||||
return entry
|
||||
|
||||
def tunnel_sync(self, context, **kwargs):
|
||||
def tunnel_sync(self, rpc_context, **kwargs):
|
||||
"""Update new tunnel.
|
||||
|
||||
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')
|
||||
# Update the database with the IP
|
||||
tunnel = ovs_db_v2.add_tunnel(tunnel_ip)
|
||||
tunnels = ovs_db_v2.get_tunnels()
|
||||
tunnel = ovs_db_v2.add_tunnel_endpoint(tunnel_ip)
|
||||
tunnels = ovs_db_v2.get_tunnel_endpoints()
|
||||
entry = dict()
|
||||
entry['tunnels'] = tunnels
|
||||
# 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)
|
||||
# Return the list of tunnels IP's to the agent
|
||||
return entry
|
||||
@ -139,7 +141,7 @@ class AgentNotifierApi(proxy.RpcProxy):
|
||||
topics.PORT,
|
||||
topics.UPDATE)
|
||||
self.topic_tunnel_update = topics.get_topic_name(topic,
|
||||
config.TUNNEL,
|
||||
constants.TUNNEL,
|
||||
topics.UPDATE)
|
||||
|
||||
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"]
|
||||
|
||||
def __init__(self, configfile=None):
|
||||
self.enable_tunneling = cfg.CONF.OVS.enable_tunneling
|
||||
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)
|
||||
|
||||
# update the vlan_id table based on current configuration
|
||||
ovs_db_v2.update_vlan_id_pool()
|
||||
ovs_db_v2.initialize()
|
||||
self._parse_network_vlan_ranges()
|
||||
ovs_db_v2.sync_vlan_allocations(self.network_vlan_ranges)
|
||||
self._parse_tunnel_id_ranges()
|
||||
ovs_db_v2.sync_tunnel_allocations(self.tunnel_id_ranges)
|
||||
self.agent_rpc = cfg.CONF.AGENT.rpc
|
||||
self.setup_rpc()
|
||||
|
||||
def setup_rpc(self):
|
||||
# RPC support
|
||||
self.topic = topics.PLUGIN
|
||||
self.context = context.RequestContext('quantum', 'quantum',
|
||||
is_admin=False)
|
||||
self.rpc_context = context.RequestContext('quantum', 'quantum',
|
||||
is_admin=False)
|
||||
self.conn = rpc.create_connection(new=True)
|
||||
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.conn.create_consumer(self.topic, self.dispatcher,
|
||||
fanout=False)
|
||||
# Consume from all consumers in a 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
|
||||
# when available.
|
||||
|
||||
@ -229,9 +264,18 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
|
||||
|
||||
def _extend_network_dict(self, context, network):
|
||||
if self._check_provider_view_auth(context, network):
|
||||
if not self.enable_tunneling:
|
||||
network['provider:vlan_id'] = ovs_db_v2.get_vlan(
|
||||
network['id'], context.session)
|
||||
binding = ovs_db_v2.get_network_binding(context.session,
|
||||
network['id'])
|
||||
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):
|
||||
network_type = attrs.get('provider:network_type')
|
||||
@ -251,16 +295,13 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
|
||||
if not network_type_set:
|
||||
msg = _("provider:network_type required")
|
||||
raise q_exc.InvalidInput(error_message=msg)
|
||||
elif network_type == 'flat':
|
||||
msg = _("plugin does not support flat networks")
|
||||
raise q_exc.InvalidInput(error_message=msg)
|
||||
# REVISIT(rkukura) to be enabled in phase 3
|
||||
# if vlan_id_set:
|
||||
# msg = _("provider:vlan_id specified for flat network")
|
||||
# raise q_exc.InvalidInput(error_message=msg)
|
||||
# else:
|
||||
# vlan_id = db.FLAT_VLAN_ID
|
||||
elif network_type == 'vlan':
|
||||
elif network_type == constants.TYPE_FLAT:
|
||||
if vlan_id_set:
|
||||
msg = _("provider:vlan_id specified for flat network")
|
||||
raise q_exc.InvalidInput(error_message=msg)
|
||||
else:
|
||||
vlan_id = constants.FLAT_VLAN_ID
|
||||
elif network_type == constants.TYPE_VLAN:
|
||||
if not vlan_id_set:
|
||||
msg = _("provider:vlan_id required")
|
||||
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)
|
||||
|
||||
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)
|
||||
# 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)
|
||||
|
||||
@ -304,38 +342,58 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
|
||||
|
||||
def create_network(self, context, network):
|
||||
(network_type, physical_network,
|
||||
vlan_id) = self._process_provider_create(context,
|
||||
network['network'])
|
||||
physical_id) = self._process_provider_create(context,
|
||||
network['network'])
|
||||
|
||||
net = super(OVSQuantumPluginV2, self).create_network(context, network)
|
||||
try:
|
||||
session = context.session
|
||||
with session.begin(subtransactions=True):
|
||||
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:
|
||||
ovs_db_v2.reserve_specific_vlan_id(vlan_id, context.session)
|
||||
except Exception:
|
||||
super(OVSQuantumPluginV2, self).delete_network(context, net['id'])
|
||||
raise
|
||||
|
||||
ovs_db_v2.reserve_specific_vlan(session, physical_network,
|
||||
physical_id)
|
||||
net = super(OVSQuantumPluginV2, self).create_network(context,
|
||||
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'])
|
||||
ovs_db_v2.add_vlan_binding(vlan_id, str(net['id']), context.session)
|
||||
self._extend_network_dict(context, net)
|
||||
return net
|
||||
|
||||
def update_network(self, context, id, network):
|
||||
self._check_provider_update(context, network['network'])
|
||||
|
||||
net = super(OVSQuantumPluginV2, self).update_network(context, id,
|
||||
network)
|
||||
self._extend_network_dict(context, net)
|
||||
session = context.session
|
||||
with session.begin(subtransactions=True):
|
||||
net = super(OVSQuantumPluginV2, self).update_network(context, id,
|
||||
network)
|
||||
self._extend_network_dict(context, net)
|
||||
return net
|
||||
|
||||
def delete_network(self, context, id):
|
||||
vlan_id = ovs_db_v2.get_vlan(id)
|
||||
result = super(OVSQuantumPluginV2, self).delete_network(context, id)
|
||||
ovs_db_v2.release_vlan_id(vlan_id)
|
||||
session = context.session
|
||||
with session.begin(subtransactions=True):
|
||||
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:
|
||||
self.notifier.network_delete(self.context, id)
|
||||
self.notifier.network_delete(self.rpc_context, id)
|
||||
return result
|
||||
|
||||
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)
|
||||
if self.agent_rpc:
|
||||
if original_port['admin_state_up'] != port['admin_state_up']:
|
||||
vlan_id = ovs_db_v2.get_vlan(port['network_id'])
|
||||
self.notifier.port_update(self.context, port, vlan_id)
|
||||
binding = ovs_db_v2.get_network_binding(None,
|
||||
port['network_id'])
|
||||
# REVISIT(rkukura): needs other binding data as well
|
||||
self.notifier.port_update(self.rpc_context, port,
|
||||
binding.physical_id)
|
||||
return port
|
||||
|
||||
def delete_port(self, context, id):
|
||||
|
@ -17,96 +17,204 @@ import unittest2
|
||||
|
||||
from quantum.common import exceptions as q_exc
|
||||
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
|
||||
|
||||
PHYS_NET = 'physnet1'
|
||||
VLAN_MIN = 10
|
||||
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):
|
||||
cfg.CONF.set_override('vlan_min', VLAN_MIN, group='OVS')
|
||||
cfg.CONF.set_override('vlan_max', VLAN_MAX, group='OVS')
|
||||
|
||||
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()
|
||||
ovs_db_v2.initialize()
|
||||
ovs_db_v2.sync_vlan_allocations(VLAN_RANGES)
|
||||
self.session = db.get_session()
|
||||
|
||||
def tearDown(self):
|
||||
db.clear_db()
|
||||
cfg.CONF.reset()
|
||||
|
||||
def test_update_vlan_id_pool(self):
|
||||
self.assertIsNone(ovs_db_v2.get_vlan_id(VLAN_MIN - 1))
|
||||
self.assertFalse(ovs_db_v2.get_vlan_id(VLAN_MIN).vlan_used)
|
||||
self.assertFalse(ovs_db_v2.get_vlan_id(VLAN_MIN + 1).vlan_used)
|
||||
self.assertFalse(ovs_db_v2.get_vlan_id(VLAN_MAX).vlan_used)
|
||||
self.assertIsNone(ovs_db_v2.get_vlan_id(VLAN_MAX + 1))
|
||||
def test_sync_vlan_allocations(self):
|
||||
self.assertIsNone(ovs_db_v2.get_vlan_allocation(PHYS_NET,
|
||||
VLAN_MIN - 1))
|
||||
self.assertFalse(ovs_db_v2.get_vlan_allocation(PHYS_NET,
|
||||
VLAN_MIN).allocated)
|
||||
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')
|
||||
cfg.CONF.set_override('vlan_max', VLAN_MAX + 5, group='OVS')
|
||||
ovs_db_v2.update_vlan_id_pool()
|
||||
ovs_db_v2.sync_vlan_allocations(UPDATED_VLAN_RANGES)
|
||||
|
||||
self.assertIsNone(ovs_db_v2.get_vlan_id(VLAN_MIN + 5 - 1))
|
||||
self.assertFalse(ovs_db_v2.get_vlan_id(VLAN_MIN + 5).vlan_used)
|
||||
self.assertFalse(ovs_db_v2.get_vlan_id(VLAN_MIN + 5 + 1).vlan_used)
|
||||
self.assertFalse(ovs_db_v2.get_vlan_id(VLAN_MAX + 5).vlan_used)
|
||||
self.assertIsNone(ovs_db_v2.get_vlan_id(VLAN_MAX + 5 + 1))
|
||||
self.assertIsNone(ovs_db_v2.get_vlan_allocation(PHYS_NET,
|
||||
VLAN_MIN + 5 - 1))
|
||||
self.assertFalse(ovs_db_v2.get_vlan_allocation(PHYS_NET,
|
||||
VLAN_MIN + 5).
|
||||
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):
|
||||
session = db.get_session()
|
||||
def test_vlan_pool(self):
|
||||
vlan_ids = set()
|
||||
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.assertLessEqual(vlan_id, VLAN_MAX)
|
||||
vlan_ids.add(vlan_id)
|
||||
|
||||
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:
|
||||
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):
|
||||
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()
|
||||
def test_specific_vlan_inside_pool(self):
|
||||
vlan_id = VLAN_MIN + 5
|
||||
self.assertFalse(ovs_db_v2.get_vlan_id(vlan_id).vlan_used)
|
||||
ovs_db_v2.reserve_specific_vlan_id(vlan_id, session)
|
||||
self.assertTrue(ovs_db_v2.get_vlan_id(vlan_id).vlan_used)
|
||||
self.assertFalse(ovs_db_v2.get_vlan_allocation(PHYS_NET,
|
||||
vlan_id).allocated)
|
||||
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):
|
||||
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)
|
||||
self.assertFalse(ovs_db_v2.get_vlan_id(vlan_id).vlan_used)
|
||||
ovs_db_v2.release_vlan(self.session, PHYS_NET, vlan_id, VLAN_RANGES)
|
||||
self.assertFalse(ovs_db_v2.get_vlan_allocation(PHYS_NET,
|
||||
vlan_id).allocated)
|
||||
|
||||
def test_specific_vlan_id_outside_pool(self):
|
||||
session = db.get_session()
|
||||
def test_specific_vlan_outside_pool(self):
|
||||
vlan_id = VLAN_MAX + 5
|
||||
self.assertIsNone(ovs_db_v2.get_vlan_id(vlan_id))
|
||||
ovs_db_v2.reserve_specific_vlan_id(vlan_id, session)
|
||||
self.assertTrue(ovs_db_v2.get_vlan_id(vlan_id).vlan_used)
|
||||
self.assertIsNone(ovs_db_v2.get_vlan_allocation(PHYS_NET, vlan_id))
|
||||
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):
|
||||
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)
|
||||
self.assertIsNone(ovs_db_v2.get_vlan_id(vlan_id))
|
||||
ovs_db_v2.release_vlan(self.session, PHYS_NET, vlan_id, VLAN_RANGES)
|
||||
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)
|
||||
|
@ -22,7 +22,6 @@ from quantum.plugins.openvswitch.common import config
|
||||
class ConfigurationTest(unittest.TestCase):
|
||||
|
||||
def test_defaults(self):
|
||||
self.assertFalse(cfg.CONF.OVS.enable_tunneling)
|
||||
self.assertEqual('br-int', cfg.CONF.OVS.integration_bridge)
|
||||
self.assertEqual('br-tun', cfg.CONF.OVS.tunnel_bridge)
|
||||
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.AGENT.polling_interval)
|
||||
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))
|
||||
|
@ -26,7 +26,7 @@ from quantum.common import topics
|
||||
from quantum.openstack.common import context
|
||||
from quantum.openstack.common import rpc
|
||||
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):
|
||||
@ -81,7 +81,7 @@ class rpcApiTestCase(unittest2.TestCase):
|
||||
rpcapi = povs.AgentNotifierApi(topics.AGENT)
|
||||
self._test_ovs_api(rpcapi,
|
||||
topics.get_topic_name(topics.AGENT,
|
||||
config.TUNNEL,
|
||||
constants.TUNNEL,
|
||||
topics.UPDATE),
|
||||
'tunnel_update', rpc_method='fanout_cast',
|
||||
tunnel_ip='fake_ip', tunnel_id='fake_id')
|
||||
|
@ -28,7 +28,7 @@ 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)
|
||||
LVM = ovs_quantum_agent.LocalVLANMapping(LV_ID, 'gre', None, LS_ID, LV_IDS)
|
||||
VIF_ID = '404deaec-5d37-11e1-a64b-000c29d5f0a8'
|
||||
VIF_MAC = '3c:09:24:1e:78:23'
|
||||
OFPORT_NUM = 1
|
||||
@ -79,10 +79,10 @@ class TunnelTest(unittest.TestCase):
|
||||
def testConstruct(self):
|
||||
self.mox.ReplayAll()
|
||||
|
||||
b = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
|
||||
self.TUN_BRIDGE,
|
||||
'10.0.0.1',
|
||||
'sudo', 2, 2, False)
|
||||
b = ovs_quantum_agent.OVSQuantumAgent(self.INT_BRIDGE,
|
||||
self.TUN_BRIDGE,
|
||||
'10.0.0.1', {},
|
||||
'sudo', 2, 2, False)
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def testProvisionLocalVlan(self):
|
||||
@ -96,24 +96,24 @@ class TunnelTest(unittest.TestCase):
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
|
||||
self.TUN_BRIDGE,
|
||||
'10.0.0.1',
|
||||
'sudo', 2, 2, False)
|
||||
a = ovs_quantum_agent.OVSQuantumAgent(self.INT_BRIDGE,
|
||||
self.TUN_BRIDGE,
|
||||
'10.0.0.1', {},
|
||||
'sudo', 2, 2, False)
|
||||
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()
|
||||
|
||||
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.mox.ReplayAll()
|
||||
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
|
||||
self.TUN_BRIDGE,
|
||||
'10.0.0.1',
|
||||
'sudo', 2, 2, False)
|
||||
a = ovs_quantum_agent.OVSQuantumAgent(self.INT_BRIDGE,
|
||||
self.TUN_BRIDGE,
|
||||
'10.0.0.1', {},
|
||||
'sudo', 2, 2, False)
|
||||
a.available_local_vlans = set()
|
||||
a.local_vlan_map[NET_UUID] = LVM
|
||||
a.reclaim_local_vlan(NET_UUID, LVM)
|
||||
@ -131,20 +131,20 @@ class TunnelTest(unittest.TestCase):
|
||||
actions=action_string)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
|
||||
self.TUN_BRIDGE,
|
||||
'10.0.0.1',
|
||||
'sudo', 2, 2, False)
|
||||
a = ovs_quantum_agent.OVSQuantumAgent(self.INT_BRIDGE,
|
||||
self.TUN_BRIDGE,
|
||||
'10.0.0.1', {},
|
||||
'sudo', 2, 2, False)
|
||||
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()
|
||||
|
||||
def testPortUnbound(self):
|
||||
self.mox.ReplayAll()
|
||||
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
|
||||
self.TUN_BRIDGE,
|
||||
'10.0.0.1',
|
||||
'sudo', 2, 2, False)
|
||||
a = ovs_quantum_agent.OVSQuantumAgent(self.INT_BRIDGE,
|
||||
self.TUN_BRIDGE,
|
||||
'10.0.0.1', {},
|
||||
'sudo', 2, 2, False)
|
||||
a.available_local_vlans = set([LV_ID])
|
||||
a.local_vlan_map[NET_UUID] = LVM
|
||||
a.port_unbound(VIF_PORT, NET_UUID)
|
||||
@ -158,10 +158,10 @@ class TunnelTest(unittest.TestCase):
|
||||
actions='drop')
|
||||
|
||||
self.mox.ReplayAll()
|
||||
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
|
||||
self.TUN_BRIDGE,
|
||||
'10.0.0.1',
|
||||
'sudo', 2, 2, False)
|
||||
a = ovs_quantum_agent.OVSQuantumAgent(self.INT_BRIDGE,
|
||||
self.TUN_BRIDGE,
|
||||
'10.0.0.1', {},
|
||||
'sudo', 2, 2, False)
|
||||
a.available_local_vlans = set([LV_ID])
|
||||
a.local_vlan_map[NET_UUID] = LVM
|
||||
a.port_dead(VIF_PORT)
|
||||
|
Loading…
Reference in New Issue
Block a user