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
|
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
|
||||||
|
@ -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])
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
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__)
|
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
|
||||||
|
@ -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)
|
||||||
|
@ -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):
|
||||||
|
@ -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)
|
||||||
|
@ -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))
|
||||||
|
@ -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')
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user