From c099e06a9a0b1aea900d65d8df23305f32e3d7cc Mon Sep 17 00:00:00 2001 From: Bob Kukura Date: Wed, 15 Aug 2012 02:43:17 -0400 Subject: [PATCH] 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 --- .../openvswitch/ovs_quantum_plugin.ini | 42 +- quantum/agent/linux/ovs_lib.py | 5 + quantum/common/exceptions.py | 5 + .../openvswitch/agent/ovs_quantum_agent.py | 535 +++++++----------- quantum/plugins/openvswitch/common/config.py | 19 +- .../plugins/openvswitch/common/constants.py | 30 + quantum/plugins/openvswitch/ovs_db_v2.py | 303 ++++++---- quantum/plugins/openvswitch/ovs_models_v2.py | 74 ++- .../plugins/openvswitch/ovs_quantum_plugin.py | 213 ++++--- quantum/tests/unit/openvswitch/test_ovs_db.py | 236 +++++--- .../unit/openvswitch/test_ovs_defaults.py | 12 +- .../tests/unit/openvswitch/test_ovs_rpcapi.py | 4 +- .../tests/unit/openvswitch/test_ovs_tunnel.py | 56 +- 13 files changed, 896 insertions(+), 638 deletions(-) create mode 100644 quantum/plugins/openvswitch/common/constants.py diff --git a/etc/quantum/plugins/openvswitch/ovs_quantum_plugin.ini b/etc/quantum/plugins/openvswitch/ovs_quantum_plugin.ini index 178b49a72..1b1b87087 100644 --- a/etc/quantum/plugins/openvswitch/ovs_quantum_plugin.ini +++ b/etc/quantum/plugins/openvswitch/ovs_quantum_plugin.ini @@ -12,29 +12,38 @@ sql_connection = sqlite:// reconnect_interval = 2 [OVS] -# This enables the new OVSQuantumTunnelAgent which enables tunneling -# between hybervisors. Leave it set to False or omit for legacy behavior. -enable_tunneling = False +# (ListOpt) Comma-separated list of +# :: tuples enumerating ranges +# of VLAN IDs on named physical networks that are available for +# allocation. +# network_vlan_ranges = default:1000:2999 + +# (ListOpt) Comma-separated list of : tuples +# enumerating ranges of GRE tunnel IDs that are available for +# allocation. +# tunnel_id_ranges = # Do not change this parameter unless you have a good reason to. # This is the name of the OVS integration bridge. There is one per hypervisor. # The integration bridge acts as a virtual "patch port". All VM VIFs are # attached to this bridge and then "patched" according to their network # connectivity. -integration_bridge = br-int +# integration_bridge = br-int -# Only used if enable-tunneling (above) is True. +# Only used if tunnel_id_ranges (above) is not empty. # In most cases, the default value should be fine. -tunnel_bridge = br-tun +# tunnel_bridge = br-tun -# Uncomment this line if enable-tunneling is True above. +# (ListOpt) Comma-separated list of : tuples +# mapping physical network names to agent's node-specific OVS bridge +# names. Each bridge must exist, and should have physical network +# interface configured as a port. +# bridge_mappings = default:br-eth1 + +# Uncomment this line if tunnel_id_ranges (above) is not empty. # Set local-ip to be the local IP address of this hypervisor. # local_ip = 10.0.0.3 -# Uncomment if you want to use custom VLAN range. -# vlan_min = 1 -# vlan_max = 4094 - [AGENT] # Agent's polling interval in seconds polling_interval = 2 @@ -47,25 +56,26 @@ root_helper = sudo # Sample Configurations. #----------------------------------------------------------------------------- # -# 1. Without tunneling. +# 1. With VLANs on eth1. # [DATABASE] # sql_connection = mysql://root:nova@127.0.0.1:3306/ovs_quantum # [OVS] -# enable_tunneling = False +# network_vlan_ranges = default:2000:3999 +# tunnel_id_ranges = # integration_bridge = br-int +# bridge_mappings = default:br-eth1 # [AGENT] # root_helper = sudo # Add the following setting, if you want to log to a file -# log_file = /var/log/quantum/ovs_quantum_agent.log # # 2. With tunneling. # [DATABASE] # sql_connection = mysql://root:nova@127.0.0.1:3306/ovs_quantum # [OVS] -# enable_tunneling = True +# network_vlan_ranges = +# tunnel_id_ranges = 1:1000 # integration_bridge = br-int # tunnel_bridge = br-tun -# remote-ip-file = /opt/stack/remote-ips.txt # local_ip = 10.0.0.3 # [AGENT] # root_helper = sudo diff --git a/quantum/agent/linux/ovs_lib.py b/quantum/agent/linux/ovs_lib.py index b6f9d0153..e982bac80 100644 --- a/quantum/agent/linux/ovs_lib.py +++ b/quantum/agent/linux/ovs_lib.py @@ -71,6 +71,11 @@ class OVSBridge: self.run_vsctl(["--", "--if-exists", "del-br", self.br_name]) self.run_vsctl(["add-br", self.br_name]) + def add_port(self, port_name): + self.run_vsctl(["--", "--may-exist", "add-port", self.br_name, + port_name]) + return self.get_port_ofport(port_name) + def delete_port(self, port_name): self.run_vsctl(["--", "--if-exists", "del-port", self.br_name, port_name]) diff --git a/quantum/common/exceptions.py b/quantum/common/exceptions.py index 4e0f0456d..054bb433a 100644 --- a/quantum/common/exceptions.py +++ b/quantum/common/exceptions.py @@ -127,6 +127,11 @@ class VlanIdInUse(InUse): "%(physical_network)s is in use.") +class TunnelIdInUse(InUse): + message = _("Unable to create the network. " + "The tunnel ID %(tunnel_id)s is in use.") + + class ResourceExhausted(QuantumException): pass diff --git a/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py b/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py index b87afff7e..c0e1e35d2 100755 --- a/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py +++ b/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py @@ -28,9 +28,10 @@ import eventlet from sqlalchemy.ext import sqlsoup from quantum.agent import rpc as agent_rpc +from quantum.agent.linux import ip_lib from quantum.agent.linux import ovs_lib from quantum.agent.linux import utils -from quantum.common import constants +from quantum.common import constants as q_const from quantum.common import config as logging_config from quantum.common import topics from quantum.openstack.common import cfg @@ -38,6 +39,7 @@ from quantum.openstack.common import context from quantum.openstack.common import rpc from quantum.openstack.common.rpc import dispatcher from quantum.plugins.openvswitch.common import config +from quantum.plugins.openvswitch.common import constants logging.basicConfig() LOG = logging.getLogger(__name__) @@ -49,15 +51,20 @@ DEAD_VLAN_TAG = "4095" # A class to represent a VIF (i.e., a port that has 'iface-id' and 'vif-mac' # attributes set). class LocalVLANMapping: - def __init__(self, vlan, lsw_id, vif_ids=None): + def __init__(self, vlan, network_type, physical_network, physical_id, + vif_ids=None): if vif_ids is None: vif_ids = [] self.vlan = vlan - self.lsw_id = lsw_id + self.network_type = network_type + self.physical_network = physical_network + self.physical_id = physical_id self.vif_ids = vif_ids def __str__(self): - return "lv-id = %s ls-id = %s" % (self.vlan, self.lsw_id) + return ("lv-id = %s type = %s phys-net = %s phys-id = %s" % + (self.vlan, self.network_type, self.physical_network, + self.physical_id)) class Port(object): @@ -142,266 +149,30 @@ class OVSRpcCallbacks(): class OVSQuantumAgent(object): - - def __init__(self, integ_br, root_helper, polling_interval, - reconnect_interval, rpc): - self.root_helper = root_helper - self.setup_integration_br(integ_br) - self.polling_interval = polling_interval - self.reconnect_interval = reconnect_interval - self.rpc = rpc - if rpc: - self.setup_rpc(integ_br) - - def setup_rpc(self, integ_br): - mac = utils.get_interface_mac(integ_br) - self.agent_id = '%s' % (mac.replace(":", "")) - self.topic = topics.AGENT - self.plugin_rpc = agent_rpc.PluginApi(topics.PLUGIN) - - # RPC network init - self.context = context.RequestContext('quantum', 'quantum', - is_admin=False) - # Handle updates from service - self.callbacks = OVSRpcCallbacks(self.context, self.int_br) - self.dispatcher = self.callbacks.create_rpc_dispatcher() - # Define the listening consumers for the agent - consumers = [[topics.PORT, topics.UPDATE], - [topics.NETWORK, topics.DELETE]] - self.connection = agent_rpc.create_consumers(self.dispatcher, - self.topic, - consumers) - - def port_bound(self, port, vlan_id): - self.int_br.set_db_attribute("Port", port.port_name, - "tag", str(vlan_id)) - self.int_br.delete_flows(in_port=port.ofport) - - def port_unbound(self, port, still_exists): - if still_exists: - self.int_br.clear_db_attribute("Port", port.port_name, "tag") - - def setup_integration_br(self, integ_br): - self.int_br = ovs_lib.OVSBridge(integ_br, self.root_helper) - self.int_br.remove_all_flows() - # switch all traffic using L2 learning - self.int_br.add_flow(priority=1, actions="normal") - - def db_loop(self, db_connection_url): - '''Main processing loop for Non-Tunneling Agent. - - :param options: database information - in the event need to reconnect - ''' - self.local_vlan_map = {} - old_local_bindings = {} - old_vif_ports = {} - db_connected = False - - while True: - if not db_connected: - time.sleep(self.reconnect_interval) - db = sqlsoup.SqlSoup(db_connection_url) - db_connected = True - LOG.info("Connecting to database \"%s\" on %s" % - (db.engine.url.database, db.engine.url.host)) - - all_bindings = {} - try: - ports = db.ports.all() - except Exception, e: - LOG.info("Unable to get port bindings! Exception: %s" % e) - db_connected = False - continue - - for port in ports: - all_bindings[port.id] = port - - vlan_bindings = {} - try: - vlan_binds = db.vlan_bindings.all() - except Exception, e: - LOG.info("Unable to get vlan bindings! Exception: %s" % e) - db_connected = False - continue - - for bind in vlan_binds: - vlan_bindings[bind.network_id] = bind.vlan_id - - new_vif_ports = {} - new_local_bindings = {} - vif_ports = self.int_br.get_vif_ports() - for p in vif_ports: - new_vif_ports[p.vif_id] = p - if p.vif_id in all_bindings: - net_id = all_bindings[p.vif_id].network_id - new_local_bindings[p.vif_id] = net_id - else: - # no binding, put him on the 'dead vlan' - self.int_br.set_db_attribute("Port", p.port_name, "tag", - DEAD_VLAN_TAG) - self.int_br.add_flow(priority=2, - in_port=p.ofport, - actions="drop") - - old_b = old_local_bindings.get(p.vif_id, None) - new_b = new_local_bindings.get(p.vif_id, None) - - if old_b != new_b: - if old_b is not None: - LOG.info("Removing binding to net-id = %s for %s" - % (old_b, str(p))) - self.port_unbound(p, True) - if p.vif_id in all_bindings: - all_bindings[p.vif_id].status = ( - constants.PORT_STATUS_DOWN) - if new_b is not None: - # If we don't have a binding we have to stick it on - # the dead vlan - net_id = all_bindings[p.vif_id].network_id - vlan_id = vlan_bindings.get(net_id, DEAD_VLAN_TAG) - self.port_bound(p, vlan_id) - if p.vif_id in all_bindings: - all_bindings[p.vif_id].status = ( - constants.PORT_STATUS_ACTIVE) - LOG.info(("Adding binding to net-id = %s " - "for %s on vlan %s") % - (new_b, str(p), vlan_id)) - - for vif_id in old_vif_ports: - if vif_id not in new_vif_ports: - LOG.info("Port Disappeared: %s" % vif_id) - if vif_id in old_local_bindings: - old_b = old_local_bindings[vif_id] - self.port_unbound(old_vif_ports[vif_id], False) - if vif_id in all_bindings: - all_bindings[vif_id].status = ( - constants.PORT_STATUS_DOWN) - - old_vif_ports = new_vif_ports - old_local_bindings = new_local_bindings - try: - db.commit() - except Exception, e: - LOG.info("Unable to commit to database! Exception: %s" % e) - db.rollback() - old_local_bindings = {} - old_vif_ports = {} - - time.sleep(self.polling_interval) - - def update_ports(self, registered_ports): - ports = self.int_br.get_vif_port_set() - if ports == registered_ports: - return - added = ports - registered_ports - removed = registered_ports - ports - return {'current': ports, - 'added': added, - 'removed': removed} - - def treat_devices_added(self, devices): - resync = False - for device in devices: - LOG.info("Port %s added", device) - try: - details = self.plugin_rpc.get_device_details(self.context, - device, - self.agent_id) - except Exception as e: - LOG.debug("Unable to get port details for %s: %s", device, e) - resync = True - continue - if 'port_id' in details: - LOG.info("Port %s updated. Details: %s", device, details) - port = self.int_br.get_vif_port_by_id(details['port_id']) - if port: - if details['admin_state_up']: - self.port_bound(port, details['vlan_id']) - else: - self.port_unbound(port, True) - else: - LOG.debug("Device %s not defined on plugin", device) - return resync - - def treat_devices_removed(self, devices): - resync = False - for device in devices: - LOG.info("Attachment %s removed", device) - try: - details = self.plugin_rpc.update_device_down(self.context, - device, - self.agent_id) - except Exception as e: - LOG.debug("port_removed failed for %s: %s", device, e) - resync = True - if details['exists']: - LOG.info("Port %s updated.", device) - # Nothing to do regarding local networking - else: - LOG.debug("Device %s not defined on plugin", device) - return resync - - def process_network_ports(self, port_info): - resync_a = False - resync_b = False - if 'added' in port_info: - resync_a = self.treat_devices_added(port_info['added']) - if 'removed' in port_info: - resync_b = self.treat_devices_removed(port_info['removed']) - # If one of the above opertaions fails => resync with plugin - return (resync_a | resync_b) - - def rpc_loop(self): - sync = True - ports = set() - - while True: - start = time.time() - if sync: - LOG.info("Agent out of sync with plugin!") - ports.clear() - sync = False - - port_info = self.update_ports(ports) - - # notify plugin about port deltas - if port_info: - LOG.debug("Agent loop has new devices!") - # If treat devices fails - indicates must resync with plugin - sync = self.process_network_ports(port_info) - ports = port_info['current'] - - # sleep till end of polling interval - elapsed = (time.time() - start) - if (elapsed < self.polling_interval): - time.sleep(self.polling_interval - elapsed) - else: - LOG.debug("Loop iteration exceeded interval (%s vs. %s)!", - self.polling_interval, elapsed) - - def daemon_loop(self, db_connection_url): - if self.rpc: - self.rpc_loop() - else: - self.db_loop(db_connection_url) - - -class OVSQuantumTunnelAgent(object): - '''Implements OVS-based tunneling. - - Two local bridges are created: an integration bridge (defaults to 'br-int') - and a tunneling bridge (defaults to 'br-tun'). - - All VM VIFs are plugged into the integration bridge. VMs for a given tenant - share a common "local" VLAN (i.e. not propagated externally). The VLAN id - of this local VLAN is mapped to a Logical Switch (LS) identifier and is - used to differentiate tenant traffic on inter-HV tunnels. - - A mesh of tunnels is created to other Hypervisors in the cloud. These - tunnels originate and terminate on the tunneling bridge of each hypervisor. - - Port patching is done to connect local VLANs on the integration bridge - to inter-hypervisor tunnels on the tunnel bridge. + '''Implements OVS-based tunneling, VLANs and flat networks. + + Two local bridges are created: an integration bridge (defaults to + 'br-int') and a tunneling bridge (defaults to 'br-tun'). An + additional bridge is created for each physical network interface + used for VLANs and/or flat networks. + + All VM VIFs are plugged into the integration bridge. VM VIFs on a + given virtual network share a common "local" VLAN (i.e. not + propagated externally). The VLAN id of this local VLAN is mapped + to the physical networking details realizing that virtual network. + + For virtual networks realized as GRE tunnels, a Logical Switch + (LS) identifier and is used to differentiate tenant traffic on + inter-HV tunnels. A mesh of tunnels is created to other + Hypervisors in the cloud. These tunnels originate and terminate on + the tunneling bridge of each hypervisor. Port patching is done to + connect local VLANs on the integration bridge to inter-hypervisor + tunnels on the tunnel bridge. + + For each virtual networks realized as a VLANs or flat network, a + veth is used to connect the local VLAN on the integration bridge + with the physical network bridge, with flow rules adding, + modifying, or stripping VLAN tags as necessary. ''' # Lower bound on available vlans. @@ -410,13 +181,15 @@ class OVSQuantumTunnelAgent(object): # Upper bound on available vlans. MAX_VLAN_TAG = 4094 - def __init__(self, integ_br, tun_br, local_ip, root_helper, + def __init__(self, integ_br, tun_br, local_ip, + bridge_mappings, root_helper, polling_interval, reconnect_interval, rpc): '''Constructor. :param integ_br: name of the integration bridge. :param tun_br: name of the tunnel bridge. :param local_ip: local IP address of this hypervisor. + :param bridge_mappings: mappings from phyiscal interface to bridge. :param root_helper: utility to use when running shell cmds. :param polling_interval: interval (secs) to poll DB. :param reconnect_internal: retry interval (secs) on DB error. @@ -424,9 +197,10 @@ class OVSQuantumTunnelAgent(object): ''' self.root_helper = root_helper self.available_local_vlans = set( - xrange(OVSQuantumTunnelAgent.MIN_VLAN_TAG, - OVSQuantumTunnelAgent.MAX_VLAN_TAG)) + xrange(OVSQuantumAgent.MIN_VLAN_TAG, + OVSQuantumAgent.MAX_VLAN_TAG)) self.setup_integration_br(integ_br) + self.setup_physical_bridges(bridge_mappings) self.local_vlan_map = {} self.polling_interval = polling_interval @@ -435,6 +209,7 @@ class OVSQuantumTunnelAgent(object): self.local_ip = local_ip self.tunnel_count = 0 self.setup_tunnel_br(tun_br) + self.rpc = rpc if rpc: self.setup_rpc(integ_br) @@ -455,31 +230,66 @@ class OVSQuantumTunnelAgent(object): # Define the listening consumers for the agent consumers = [[topics.PORT, topics.UPDATE], [topics.NETWORK, topics.DELETE], - [config.TUNNEL, topics.UPDATE]] + [constants.TUNNEL, topics.UPDATE]] self.connection = agent_rpc.create_consumers(self.dispatcher, self.topic, consumers) - def provision_local_vlan(self, net_uuid, lsw_id): + def provision_local_vlan(self, net_uuid, network_type, physical_network, + physical_id): '''Provisions a local VLAN. :param net_uuid: the uuid of the network associated with this vlan. - :param lsw_id: the logical switch id of this vlan.''' + :param network_type: the type of the network ('gre', 'vlan', 'flat') + :param physical_network: the physical network for 'vlan' or 'flat' + :param physical_id: the VLAN ID for 'vlan' or tunnel ID for 'tunnel' + ''' + if not self.available_local_vlans: - raise Exception("No local VLANs available for ls-id = %s" % lsw_id) + raise Exception("No local VLAN available for net-id=%s" % net_uuid) lvid = self.available_local_vlans.pop() LOG.info("Assigning %s as local vlan for net-id=%s" % (lvid, net_uuid)) - self.local_vlan_map[net_uuid] = LocalVLANMapping(lvid, lsw_id) - - # outbound - self.tun_br.add_flow(priority=4, in_port=self.patch_int_ofport, - dl_vlan=lvid, - actions="set_tunnel:%s,normal" % lsw_id) - # inbound bcast/mcast - self.tun_br.add_flow(priority=3, tun_id=lsw_id, - dl_dst="01:00:00:00:00:00/01:00:00:00:00:00", - actions="mod_vlan_vid:%s,output:%s" % - (lvid, self.patch_int_ofport)) + self.local_vlan_map[net_uuid] = LocalVLANMapping(lvid, network_type, + physical_network, + physical_id) + + if network_type == constants.TYPE_GRE: + # outbound + self.tun_br.add_flow(priority=4, in_port=self.patch_int_ofport, + dl_vlan=lvid, + actions="set_tunnel:%s,normal" % physical_id) + # inbound bcast/mcast + self.tun_br.add_flow(priority=3, tun_id=physical_id, + dl_dst="01:00:00:00:00:00/01:00:00:00:00:00", + actions="mod_vlan_vid:%s,output:%s" % + (lvid, self.patch_int_ofport)) + elif network_type == constants.TYPE_FLAT: + # outbound + br = self.phys_brs[physical_network] + br.add_flow(priority=4, + in_port=self.phys_ofports[physical_network], + dl_vlan=lvid, + actions="strip_vlan,normal") + # inbound + self.int_br.add_flow(priority=3, + in_port=self.int_ofports[physical_network], + dl_vlan=0xffff, + actions="mod_vlan_vid:%s,normal" % lvid) + elif network_type == constants.TYPE_VLAN: + # outbound + br = self.phys_brs[physical_network] + br.add_flow(priority=4, + in_port=self.phys_ofports[physical_network], + dl_vlan=lvid, + actions="mod_vlan_vid:%s,normal" % physical_id) + # inbound + self.int_br.add_flow(priority=3, + in_port=self.int_ofports[physical_network], + dl_vlan=physical_id, + actions="mod_vlan_vid:%s,normal" % lvid) + else: + LOG.error("provisioning unknown network type %s for net-id=%s" % + (network_type, net_uuid)) def reclaim_local_vlan(self, net_uuid, lvm): '''Reclaim a local VLAN. @@ -488,27 +298,57 @@ class OVSQuantumTunnelAgent(object): :param lvm: a LocalVLANMapping object that tracks (vlan, lsw_id, vif_ids) mapping.''' LOG.info("reclaming vlan = %s from net-id = %s" % (lvm.vlan, net_uuid)) - self.tun_br.delete_flows(tun_id=lvm.lsw_id) - self.tun_br.delete_flows(dl_vlan=lvm.vlan) + + if lvm.network_type == constants.TYPE_GRE: + self.tun_br.delete_flows(tun_id=lvm.physical_id) + self.tun_br.delete_flows(dl_vlan=lvm.vlan) + elif network_type == constants.TYPE_FLAT: + # outbound + br = self.phys_brs[lvm.physical_network] + br.delete_flows(in_port=self.phys_ofports[lvm.physical_network], + dl_vlan=lvm.vlan) + # inbound + br = self.int_br + br.delete_flows(in_port=self.int_ofports[lvm.physical_network], + dl_vlan=0xffff) + elif network_type == constants.TYPE_VLAN: + # outbound + br = self.phys_brs[lvm.physical_network] + br.delete_flows(in_port=self.phys_ofports[lvm.physical_network], + dl_vlan=lvm.vlan) + # inbound + br = self.int_br + br.delete_flows(in_port=self.int_ofports[lvm.physical_network], + dl_vlan=lvm.physical_id) + else: + LOG.error("reclaiming unknown network type %s for net-id=%s" % + (lvm.network_type, net_uuid)) + del self.local_vlan_map[net_uuid] self.available_local_vlans.add(lvm.vlan) - def port_bound(self, port, net_uuid, lsw_id): + def port_bound(self, port, net_uuid, + network_type, physical_network, physical_id): '''Bind port to net_uuid/lsw_id and install flow for inbound traffic to vm. :param port: a ovslib.VifPort object. :param net_uuid: the net_uuid this port is to be associated with. - :param lsw_id: the logical switch this port is to be associated with. + :param network_type: the type of the network ('gre', 'vlan', 'flat') + :param physical_network: the physical network for 'vlan' or 'flat' + :param physical_id: the VLAN ID for 'vlan' or tunnel ID for 'tunnel' ''' if net_uuid not in self.local_vlan_map: - self.provision_local_vlan(net_uuid, lsw_id) + self.provision_local_vlan(net_uuid, network_type, + physical_network, physical_id) lvm = self.local_vlan_map[net_uuid] lvm.vif_ids.append(port.vif_id) - # inbound unicast - self.tun_br.add_flow(priority=3, tun_id=lsw_id, dl_dst=port.vif_mac, - actions="mod_vlan_vid:%s,normal" % lvm.vlan) + if network_type == constants.TYPE_GRE: + # inbound unicast + self.tun_br.add_flow(priority=3, tun_id=physical_id, + dl_dst=port.vif_mac, + actions="mod_vlan_vid:%s,normal" % lvm.vlan) self.int_br.set_db_attribute("Port", port.port_name, "tag", str(lvm.vlan)) @@ -529,6 +369,8 @@ class OVSQuantumTunnelAgent(object): return lvm = self.local_vlan_map[net_uuid] + # REVISIT(rkukura): Does inbound unicast flow need to be removed here? + if port.vif_id in lvm.vif_ids: lvm.vif_ids.remove(port.vif_id) else: @@ -573,11 +415,57 @@ class OVSQuantumTunnelAgent(object): self.tun_br.remove_all_flows() self.tun_br.add_flow(priority=1, actions="drop") + def setup_physical_bridges(self, bridge_mappings): + '''Setup the physical network bridges. + + Creates phyiscal network bridges and links them to the + integration bridge using veths. + + :param bridge_mappings: map physical network names to bridge names.''' + self.phys_brs = {} + self.int_ofports = {} + self.phys_ofports = {} + ip_wrapper = ip_lib.IPWrapper(self.root_helper) + for physical_network, bridge in bridge_mappings.iteritems(): + # setup physical bridge + if not ip_lib.device_exists(bridge, self.root_helper): + LOG.error("Bridge %s for physical network %s does not exist" % + (bridge, physical_network)) + sys.exit(1) + br = ovs_lib.OVSBridge(bridge, self.root_helper) + br.remove_all_flows() + br.add_flow(priority=1, actions="normal") + self.phys_brs[physical_network] = br + + # create veth to patch physical bridge with integration bridge + int_veth_name = constants.VETH_INTEGRATION_PREFIX + bridge + self.int_br.delete_port(int_veth_name) + phys_veth_name = constants.VETH_PHYSICAL_PREFIX + bridge + br.delete_port(phys_veth_name) + if ip_lib.device_exists(int_veth_name, self.root_helper): + ip_lib.IPDevice(int_veth_name, self.root_helper).link.delete() + int_veth, phys_veth = ip_wrapper.add_veth(int_veth_name, + phys_veth_name) + self.int_ofports[physical_network] = self.int_br.add_port(int_veth) + self.phys_ofports[physical_network] = br.add_port(phys_veth) + + # block all untranslated traffic over veth between bridges + self.int_br.add_flow(priority=2, + in_port=self.int_ofports[physical_network], + actions="drop") + br.add_flow(priority=2, + in_port=self.phys_ofports[physical_network], + actions="drop") + + # enable veth to pass traffic + int_veth.link.set_up() + phys_veth.link.set_up() + def manage_tunnels(self, tunnel_ips, old_tunnel_ips, db): if self.local_ip in tunnel_ips: tunnel_ips.remove(self.local_ip) else: - db.tunnel_ips.insert(ip_address=self.local_ip) + db.ovs_tunnel_ips.insert(ip_address=self.local_ip) new_tunnel_ips = tunnel_ips - old_tunnel_ips if new_tunnel_ips: @@ -614,10 +502,11 @@ class OVSQuantumTunnelAgent(object): all_bindings = dict((p.id, Port(p)) for p in db.ports.all()) all_bindings_vif_port_ids = set(all_bindings) - lsw_id_bindings = dict((bind.network_id, bind.vlan_id) - for bind in db.vlan_bindings.all()) + net_bindings = dict((bind.network_id, bind) + for bind in + db.ovs_network_bindings.all()) - tunnel_ips = set(x.ip_address for x in db.tunnel_ips.all()) + tunnel_ips = set(x.ip_address for x in db.ovs_tunnel_ips.all()) self.manage_tunnels(tunnel_ips, old_tunnel_ips, db) # Get bindings from OVS bridge. @@ -642,7 +531,7 @@ class OVSQuantumTunnelAgent(object): if b[2] != b[1]]) LOG.debug('all_bindings: %s', all_bindings) - LOG.debug('lsw_id_bindings: %s', lsw_id_bindings) + LOG.debug('net_bindings: %s', net_bindings) LOG.debug('new_vif_ports_ids: %s', new_vif_ports_ids) LOG.debug('dead_vif_ports_ids: %s', dead_vif_ports_ids) LOG.debug('old_vif_ports_ids: %s', old_vif_ports_ids) @@ -669,21 +558,24 @@ class OVSQuantumTunnelAgent(object): self.port_unbound(p, old_net_uuid) if p.vif_id in all_bindings: all_bindings[p.vif_id].status = ( - constants.PORT_STATUS_DOWN) + q_const.PORT_STATUS_DOWN) if not new_port: self.port_dead(p) if new_port: new_net_uuid = new_port.network_id - if new_net_uuid not in lsw_id_bindings: - LOG.warn("No ls-id binding found for net-id '%s'" % - new_net_uuid) + if new_net_uuid not in net_bindings: + LOG.warn("No network binding found for net-id" + " '%s'" % new_net_uuid) continue - lsw_id = lsw_id_bindings[new_net_uuid] - self.port_bound(p, new_net_uuid, lsw_id) + bind = net_bindings[new_net_uuid] + self.port_bound(p, new_net_uuid, + bind.network_type, + bind.physical_network, + bind.physical_id) all_bindings[p.vif_id].status = ( - constants.PORT_STATUS_ACTIVE) + q_const.PORT_STATUS_ACTIVE) LOG.info("Port %s on net-id = %s bound to %s " % ( str(p), new_net_uuid, str(self.local_vlan_map[new_net_uuid]))) @@ -692,7 +584,7 @@ class OVSQuantumTunnelAgent(object): LOG.info("Port Disappeared: " + vif_id) if vif_id in all_bindings: all_bindings[vif_id].status = ( - constants.PORT_STATUS_DOWN) + q_const.PORT_STATUS_DOWN) old_port = old_local_bindings.get(vif_id) if old_port: self.port_unbound(old_vif_ports[vif_id], @@ -739,7 +631,9 @@ class OVSQuantumTunnelAgent(object): if port: if details['admin_state_up']: self.port_bound(port, details['network_id'], - details['vlan_id']) + details['network_type'], + details['physical_network'], + details['physical_id']) else: self.port_unbound(port, details['network_id']) else: @@ -835,27 +729,32 @@ def main(): # (TODO) gary - swap with common logging logging_config.setup_logging(cfg.CONF) - # Determine which agent type to use. - enable_tunneling = cfg.CONF.OVS.enable_tunneling integ_br = cfg.CONF.OVS.integration_bridge db_connection_url = cfg.CONF.DATABASE.sql_connection polling_interval = cfg.CONF.AGENT.polling_interval reconnect_interval = cfg.CONF.DATABASE.reconnect_interval root_helper = cfg.CONF.AGENT.root_helper rpc = cfg.CONF.AGENT.rpc + tun_br = cfg.CONF.OVS.tunnel_bridge + local_ip = cfg.CONF.OVS.local_ip - if enable_tunneling: - # Get parameters for OVSQuantumTunnelAgent - tun_br = cfg.CONF.OVS.tunnel_bridge - # Mandatory parameter. - local_ip = cfg.CONF.OVS.local_ip - plugin = OVSQuantumTunnelAgent(integ_br, tun_br, local_ip, root_helper, - polling_interval, reconnect_interval, - rpc) - else: - # Get parameters for OVSQuantumAgent. - plugin = OVSQuantumAgent(integ_br, root_helper, polling_interval, - reconnect_interval, rpc) + bridge_mappings = {} + for mapping in cfg.CONF.OVS.bridge_mappings: + mapping = mapping.strip() + if mapping != '': + try: + physical_network, bridge = mapping.split(':') + bridge_mappings[physical_network] = bridge + LOG.debug("physical network %s mapped to bridge %s" % + (physical_network, bridge)) + except ValueError as ex: + LOG.error("Invalid bridge mapping: \'%s\' - %s" % + (mapping, ex)) + sys.exit(1) + + plugin = OVSQuantumAgent(integ_br, tun_br, local_ip, bridge_mappings, + root_helper, polling_interval, + reconnect_interval, rpc) # Start everything. plugin.daemon_loop(db_connection_url) diff --git a/quantum/plugins/openvswitch/common/config.py b/quantum/plugins/openvswitch/common/config.py index df83327bb..d89a970eb 100644 --- a/quantum/plugins/openvswitch/common/config.py +++ b/quantum/plugins/openvswitch/common/config.py @@ -17,8 +17,9 @@ from quantum.openstack.common import cfg -# Topic for tunnel notifications between the plugin and agent -TUNNEL = 'tunnel' +DEFAULT_BRIDGE_MAPPINGS = ['default:br-eth1'] +DEFAULT_VLAN_RANGES = ['default:1000:2999'] +DEFAULT_TUNNEL_RANGES = [] database_opts = [ cfg.StrOpt('sql_connection', default='sqlite://'), @@ -27,18 +28,24 @@ database_opts = [ ] ovs_opts = [ - cfg.BoolOpt('enable_tunneling', default=False), cfg.StrOpt('integration_bridge', default='br-int'), cfg.StrOpt('tunnel_bridge', default='br-tun'), cfg.StrOpt('local_ip', default='10.0.0.3'), - cfg.IntOpt('vlan_min', default=1), - cfg.IntOpt('vlan_max', default=4094), + cfg.ListOpt('bridge_mappings', + default=DEFAULT_BRIDGE_MAPPINGS, + help="List of :"), + cfg.ListOpt('network_vlan_ranges', + default=DEFAULT_VLAN_RANGES, + help="List of :: " + "or "), + cfg.ListOpt('tunnel_id_ranges', + default=DEFAULT_TUNNEL_RANGES, + help="List of :"), ] agent_opts = [ cfg.IntOpt('polling_interval', default=2), cfg.StrOpt('root_helper', default='sudo'), - cfg.StrOpt('log_file', default=None), cfg.BoolOpt('rpc', default=True), ] diff --git a/quantum/plugins/openvswitch/common/constants.py b/quantum/plugins/openvswitch/common/constants.py new file mode 100644 index 000000000..d23f2cd0f --- /dev/null +++ b/quantum/plugins/openvswitch/common/constants.py @@ -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-' diff --git a/quantum/plugins/openvswitch/ovs_db_v2.py b/quantum/plugins/openvswitch/ovs_db_v2.py index 324ea896a..58be6b637 100644 --- a/quantum/plugins/openvswitch/ovs_db_v2.py +++ b/quantum/plugins/openvswitch/ovs_db_v2.py @@ -30,146 +30,242 @@ from quantum.plugins.openvswitch import ovs_models_v2 LOG = logging.getLogger(__name__) -def get_vlans(): - session = db.get_session() - try: - bindings = (session.query(ovs_models_v2.VlanBinding). - all()) - except exc.NoResultFound: - return [] - return [(binding.vlan_id, binding.network_id) for binding in bindings] +def initialize(): + options = {"sql_connection": "%s" % cfg.CONF.DATABASE.sql_connection} + options.update({"sql_max_retries": cfg.CONF.DATABASE.sql_max_retries}) + options.update({"reconnect_interval": + cfg.CONF.DATABASE.reconnect_interval}) + options.update({"base": models_v2.model_base.BASEV2}) + db.configure_db(options) -def get_vlan(net_id, session=None): +def get_network_binding(session, network_id): session = session or db.get_session() try: - binding = (session.query(ovs_models_v2.VlanBinding). - filter_by(network_id=net_id). + binding = (session.query(ovs_models_v2.NetworkBinding). + filter_by(network_id=network_id). one()) + return binding except exc.NoResultFound: return - return binding.vlan_id -def add_vlan_binding(vlan_id, net_id, session): +def add_network_binding(session, network_id, network_type, + physical_network, physical_id): with session.begin(subtransactions=True): - binding = ovs_models_v2.VlanBinding(vlan_id, net_id) + binding = ovs_models_v2.NetworkBinding(network_id, network_type, + physical_network, + physical_id) session.add(binding) - return binding -def remove_vlan_binding(net_id): +def sync_vlan_allocations(network_vlan_ranges): + """Synchronize vlan_allocations table with configured VLAN ranges""" + + session = db.get_session() + with session.begin(): + # process vlan ranges for each physical network separately + for physical_network, vlan_ranges in network_vlan_ranges.iteritems(): + + # determine current configured allocatable vlans for this + # physical network + vlan_ids = set() + for vlan_range in vlan_ranges: + vlan_ids |= set(xrange(vlan_range[0], vlan_range[1] + 1)) + + # remove from table unallocated vlans not currently allocatable + try: + allocs = (session.query(ovs_models_v2.VlanAllocation). + filter_by(physical_network=physical_network). + all()) + for alloc in allocs: + try: + # see if vlan is allocatable + vlan_ids.remove(alloc.vlan_id) + except KeyError: + # it's not allocatable, so check if its allocated + if not alloc.allocated: + # it's not, so remove it from table + LOG.debug("removing vlan %s on physical network " + "%s from pool" % + (alloc.vlan_id, physical_network)) + session.delete(alloc) + except exc.NoResultFound: + pass + + # add missing allocatable vlans to table + for vlan_id in sorted(vlan_ids): + alloc = ovs_models_v2.VlanAllocation(physical_network, vlan_id) + session.add(alloc) + + +def get_vlan_allocation(physical_network, vlan_id): session = db.get_session() try: - binding = (session.query(ovs_models_v2.VlanBinding). - filter_by(network_id=net_id). - one()) - session.delete(binding) + alloc = (session.query(ovs_models_v2.VlanAllocation). + filter_by(physical_network=physical_network, + vlan_id=vlan_id). + one()) + return alloc except exc.NoResultFound: - pass - session.flush() + return -def update_vlan_id_pool(): - """Update vlan_ids based on current configuration.""" +def reserve_vlan(session): + with session.begin(subtransactions=True): + alloc = (session.query(ovs_models_v2.VlanAllocation). + filter_by(allocated=False). + first()) + if alloc: + LOG.debug("reserving vlan %s on physical network %s from pool" % + (alloc.vlan_id, alloc.physical_network)) + alloc.allocated = True + return (alloc.physical_network, alloc.vlan_id) + raise q_exc.NoNetworkAvailable() + + +def reserve_specific_vlan(session, physical_network, vlan_id): + with session.begin(subtransactions=True): + try: + alloc = (session.query(ovs_models_v2.VlanAllocation). + filter_by(physical_network=physical_network, + vlan_id=vlan_id). + one()) + if alloc.allocated: + raise q_exc.VlanIdInUse(vlan_id=vlan_id, + physical_network=physical_network) + LOG.debug("reserving specific vlan %s on physical network %s " + "from pool" % (vlan_id, physical_network)) + alloc.allocated = True + except exc.NoResultFound: + LOG.debug("reserving specific vlan %s on physical network %s " + "outside pool" % (vlan_id, physical_network)) + alloc = ovs_models_v2.VlanAllocation(physical_network, vlan_id) + alloc.allocated = True + session.add(alloc) - # determine current dynamically-allocated range - vlans = set(xrange(cfg.CONF.OVS.vlan_min, - cfg.CONF.OVS.vlan_max + 1)) - session = db.get_session() +def release_vlan(session, physical_network, vlan_id, network_vlan_ranges): with session.begin(subtransactions=True): - # remove unused vlan_ids outside current range try: - records = (session.query(ovs_models_v2.VlanID). - all()) - for record in records: + alloc = (session.query(ovs_models_v2.VlanAllocation). + filter_by(physical_network=physical_network, + vlan_id=vlan_id). + one()) + alloc.allocated = False + inside = False + for vlan_range in network_vlan_ranges.get(physical_network, []): + if vlan_id >= vlan_range[0] and vlan_id <= vlan_range[1]: + inside = True + break + if not inside: + session.delete(alloc) + LOG.debug("releasing vlan %s on physical network %s %s pool" % + (vlan_id, physical_network, + inside and "to" or "outside")) + except exc.NoResultFound: + LOG.warning("vlan_id %s on physical network %s not found" % + (vlan_id, physical_network)) + + +def sync_tunnel_allocations(tunnel_id_ranges): + """Synchronize tunnel_allocations table with configured tunnel ranges""" + + # determine current configured allocatable tunnels + tunnel_ids = set() + for tunnel_id_range in tunnel_id_ranges: + tun_min, tun_max = tunnel_id_range + if tun_max + 1 - tun_min > 1000000: + LOG.error("Skipping unreasonable tunnel ID range %s:%s" % + tunnel_id_range) + else: + tunnel_ids |= set(xrange(tun_min, tun_max + 1)) + + session = db.get_session() + with session.begin(): + # remove from table unallocated tunnels not currently allocatable + try: + allocs = (session.query(ovs_models_v2.TunnelAllocation). + all()) + for alloc in allocs: try: - vlans.remove(record.vlan_id) + # see if tunnel is allocatable + tunnel_ids.remove(alloc.tunnel_id) except KeyError: - if not record.vlan_used: - LOG.debug("removing vlan %s from pool" - % record.vlan_id) - session.delete(record) + # it's not allocatable, so check if its allocated + if not alloc.allocated: + # it's not, so remove it from table + LOG.debug("removing tunnel %s from pool" % + alloc.tunnel_id) + session.delete(alloc) except exc.NoResultFound: pass - # add missing vlan_ids - for vlan in vlans: - record = ovs_models_v2.VlanID(vlan) - session.add(record) - + # add missing allocatable tunnels to table + for tunnel_id in sorted(tunnel_ids): + alloc = ovs_models_v2.TunnelAllocation(tunnel_id) + session.add(alloc) -def get_vlan_id(vlan_id): - """Get state of specified vlan""" +def get_tunnel_allocation(tunnel_id): session = db.get_session() try: - record = (session.query(ovs_models_v2.VlanID). - filter_by(vlan_id=vlan_id). - one()) - return record + alloc = (session.query(ovs_models_v2.TunnelAllocation). + filter_by(tunnel_id=tunnel_id). + one()) + return alloc except exc.NoResultFound: - return None - + return -def reserve_vlan_id(session): - """Reserve an unused vlan_id""" +def reserve_tunnel(session): with session.begin(subtransactions=True): - record = (session.query(ovs_models_v2.VlanID). - filter_by(vlan_used=False). - first()) - if not record: - raise q_exc.NoNetworkAvailable() - LOG.debug("reserving vlan %s from pool" % record.vlan_id) - record.vlan_used = True - return record.vlan_id - - -def reserve_specific_vlan_id(vlan_id, session): - """Reserve a specific vlan_id""" + alloc = (session.query(ovs_models_v2.TunnelAllocation). + filter_by(allocated=False). + first()) + if alloc: + LOG.debug("reserving tunnel %s from pool" % alloc.tunnel_id) + alloc.allocated = True + return alloc.tunnel_id + raise q_exc.NoNetworkAvailable() - if vlan_id < 1 or vlan_id > 4094: - msg = _("Specified VLAN %s outside legal range (1-4094)") % vlan_id - raise q_exc.InvalidInput(error_message=msg) +def reserve_specific_tunnel(session, tunnel_id): with session.begin(subtransactions=True): try: - record = (session.query(ovs_models_v2.VlanID). - filter_by(vlan_id=vlan_id). - one()) - if record.vlan_used: - # REVISIT(rkukura) pass phyiscal_network - raise q_exc.VlanIdInUse(vlan_id=vlan_id, - physical_network='default') - LOG.debug("reserving specific vlan %s from pool" % vlan_id) - record.vlan_used = True + alloc = (session.query(ovs_models_v2.TunnelAllocation). + filter_by(tunnel_id=tunnel_id). + one()) + if alloc.allocated: + raise q_exc.TunnelIdInUse(tunnel_id=tunnel_id) + LOG.debug("reserving specific tunnel %s from pool" % tunnel_id) + alloc.allocated = True except exc.NoResultFound: - LOG.debug("reserving specific vlan %s outside pool" % vlan_id) - record = ovs_models_v2.VlanID(vlan_id) - record.vlan_used = True - session.add(record) - + LOG.debug("reserving specific tunnel %s outside pool" % tunnel_id) + alloc = ovs_models_v2.TunnelAllocation(tunnel_id) + alloc.allocated = True + session.add(alloc) -def release_vlan_id(vlan_id): - """Set the vlan state to be unused, and delete if not in range""" - session = db.get_session() +def release_tunnel(session, tunnel_id, tunnel_id_ranges): with session.begin(subtransactions=True): try: - record = (session.query(ovs_models_v2.VlanID). - filter_by(vlan_id=vlan_id). - one()) - record.vlan_used = False - if (vlan_id >= cfg.CONF.OVS.vlan_min and - vlan_id <= cfg.CONF.OVS.vlan_max): - LOG.debug("releasing vlan %s to pool" % vlan_id) - else: - LOG.debug("removing vlan %s outside pool" % vlan_id) - session.delete(record) + alloc = (session.query(ovs_models_v2.TunnelAllocation). + filter_by(tunnel_id=tunnel_id). + one()) + alloc.allocated = False + inside = False + for tunnel_id_range in tunnel_id_ranges: + if (tunnel_id >= tunnel_id_range[0] + and tunnel_id <= tunnel_id_range[1]): + inside = True + break + if not inside: + session.delete(alloc) + LOG.debug("releasing tunnel %s %s pool" % + (tunnel_id, inside and "to" or "outside")) except exc.NoResultFound: - LOG.error("vlan id %s not found in release_vlan_id" % vlan_id) + LOG.warning("tunnel_id %s not found" % tunnel_id) def get_port(port_id): @@ -192,19 +288,19 @@ def set_port_status(port_id, status): raise q_exc.PortNotFound(port_id=port_id) -def get_tunnels(): +def get_tunnel_endpoints(): session = db.get_session() try: - tunnels = session.query(ovs_models_v2.TunnelInfo).all() + tunnels = session.query(ovs_models_v2.TunnelEndpoint).all() except exc.NoResultFound: return [] return [{'id': tunnel.id, 'ip_address': tunnel.ip_address} for tunnel in tunnels] -def generate_tunnel_id(session): +def _generate_tunnel_id(session): try: - tunnels = session.query(ovs_models_v2.TunnelInfo).all() + tunnels = session.query(ovs_models_v2.TunnelEndpoint).all() except exc.NoResultFound: return 0 tunnel_ids = ([tunnel['id'] for tunnel in tunnels]) @@ -215,15 +311,14 @@ def generate_tunnel_id(session): return id + 1 -def add_tunnel(ip): +def add_tunnel_endpoint(ip): session = db.get_session() try: - tunnel = (session.query(ovs_models_v2.TunnelInfo). + tunnel = (session.query(ovs_models_v2.TunnelEndpoint). filter_by(ip_address=ip).one()) except exc.NoResultFound: - # Generate an id for the tunnel - id = generate_tunnel_id(session) - tunnel = ovs_models_v2.TunnelInfo(ip, id) + id = _generate_tunnel_id(session) + tunnel = ovs_models_v2.TunnelEndpoint(ip, id) session.add(tunnel) session.flush() return tunnel diff --git a/quantum/plugins/openvswitch/ovs_models_v2.py b/quantum/plugins/openvswitch/ovs_models_v2.py index aa427a419..09972526a 100644 --- a/quantum/plugins/openvswitch/ovs_models_v2.py +++ b/quantum/plugins/openvswitch/ovs_models_v2.py @@ -22,41 +22,69 @@ from sqlalchemy import Boolean, Column, ForeignKey, Integer, String from quantum.db.models_v2 import model_base -class VlanID(model_base.BASEV2): - """Represents a vlan_id usage""" - __tablename__ = 'vlan_ids' +class VlanAllocation(model_base.BASEV2): + """Represents allocation state of vlan_id on physical network""" + __tablename__ = 'ovs_vlan_allocations' - vlan_id = Column(Integer, nullable=False, primary_key=True) - vlan_used = Column(Boolean, nullable=False) + physical_network = Column(String(64), nullable=False, primary_key=True) + vlan_id = Column(Integer, nullable=False, primary_key=True, + autoincrement=False) + allocated = Column(Boolean, nullable=False) - def __init__(self, vlan_id): + def __init__(self, physical_network, vlan_id): + self.physical_network = physical_network self.vlan_id = vlan_id - self.vlan_used = False + self.allocated = False def __repr__(self): - return "" % (self.vlan_id, self.vlan_used) + return "" % (self.physical_network, + self.vlan_id, self.allocated) -class VlanBinding(model_base.BASEV2): - """Represents a binding of network_id to vlan_id.""" - __tablename__ = 'vlan_bindings' +class TunnelAllocation(model_base.BASEV2): + """Represents allocation state of tunnel_id""" + __tablename__ = 'ovs_tunnel_allocations' - network_id = Column(String(36), ForeignKey('networks.id', - ondelete="CASCADE"), + tunnel_id = Column(Integer, nullable=False, primary_key=True, + autoincrement=False) + allocated = Column(Boolean, nullable=False) + + def __init__(self, tunnel_id): + self.tunnel_id = tunnel_id + self.allocated = False + + def __repr__(self): + return "" % (self.tunnel_id, self.allocated) + + +class NetworkBinding(model_base.BASEV2): + """Represents binding of virtual network to physical realization""" + __tablename__ = 'ovs_network_bindings' + + network_id = Column(String(36), + ForeignKey('networks.id', ondelete="CASCADE"), primary_key=True) - vlan_id = Column(Integer, nullable=False) + network_type = Column(String(32), nullable=False) # 'gre', 'vlan', 'flat' + physical_network = Column(String(64)) + physical_id = Column(Integer) # tunnel_id or vlan_id - def __init__(self, vlan_id, network_id): + def __init__(self, network_id, network_type, physical_network, + physical_id): self.network_id = network_id - self.vlan_id = vlan_id + self.network_type = network_type + self.physical_network = physical_network + self.physical_id = physical_id def __repr__(self): - return "" % (self.vlan_id, self.network_id) + return "" % (self.network_id, + self.network_type, + self.physical_network, + self.physical_id) class TunnelIP(model_base.BASEV2): - """Represents a remote IP in tunnel mode.""" - __tablename__ = 'tunnel_ips' + """Represents tunnel endpoint in DB mode""" + __tablename__ = 'ovs_tunnel_ips' ip_address = Column(String(255), primary_key=True) @@ -67,9 +95,9 @@ class TunnelIP(model_base.BASEV2): return "" % (self.ip_address) -class TunnelInfo(model_base.BASEV2): - """Represents remote tunnel information in tunnel mode.""" - __tablename__ = 'tunnel_info' +class TunnelEndpoint(model_base.BASEV2): + """Represents tunnel endpoint in RPC mode""" + __tablename__ = 'ovs_tunnel_endpoints' ip_address = Column(String(64), primary_key=True) id = Column(Integer, nullable=False) @@ -79,4 +107,4 @@ class TunnelInfo(model_base.BASEV2): self.id = id def __repr__(self): - return "" % (self.ip_address, self.id) + return "" % (self.ip_address, self.id) diff --git a/quantum/plugins/openvswitch/ovs_quantum_plugin.py b/quantum/plugins/openvswitch/ovs_quantum_plugin.py index 335f0ac10..f8e2702a3 100644 --- a/quantum/plugins/openvswitch/ovs_quantum_plugin.py +++ b/quantum/plugins/openvswitch/ovs_quantum_plugin.py @@ -22,22 +22,22 @@ import logging import os +import sys from quantum.api.v2 import attributes -from quantum.common import constants +from quantum.common import constants as q_const from quantum.common import exceptions as q_exc from quantum.common import topics -from quantum.db import api as db from quantum.db import db_base_plugin_v2 from quantum.db import dhcp_rpc_base from quantum.db import l3_db -from quantum.db import models_v2 from quantum.openstack.common import context from quantum.openstack.common import cfg from quantum.openstack.common import rpc from quantum.openstack.common.rpc import dispatcher from quantum.openstack.common.rpc import proxy from quantum.plugins.openvswitch.common import config +from quantum.plugins.openvswitch.common import constants from quantum.plugins.openvswitch import ovs_db_v2 from quantum import policy @@ -50,8 +50,8 @@ class OVSRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin): # Set RPC API version to 1.0 by default. RPC_API_VERSION = '1.0' - def __init__(self, context, notifier): - self.context = context + def __init__(self, rpc_context, notifier): + self.rpc_context = rpc_context self.notifier = notifier def create_rpc_dispatcher(self): @@ -62,27 +62,29 @@ class OVSRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin): ''' return dispatcher.RpcDispatcher([self]) - def get_device_details(self, context, **kwargs): + def get_device_details(self, rpc_context, **kwargs): """Agent requests device details""" agent_id = kwargs.get('agent_id') device = kwargs.get('device') LOG.debug("Device %s details requested from %s", device, agent_id) port = ovs_db_v2.get_port(device) if port: - vlan_id = ovs_db_v2.get_vlan(port['network_id']) + binding = ovs_db_v2.get_network_binding(None, port['network_id']) entry = {'device': device, - 'vlan_id': vlan_id, 'network_id': port['network_id'], 'port_id': port['id'], - 'admin_state_up': port['admin_state_up']} + 'admin_state_up': port['admin_state_up'], + 'network_type': binding.network_type, + 'physical_id': binding.physical_id, + 'physical_network': binding.physical_network} # Set the port status to UP - ovs_db_v2.set_port_status(port['id'], constants.PORT_STATUS_ACTIVE) + ovs_db_v2.set_port_status(port['id'], q_const.PORT_STATUS_ACTIVE) else: entry = {'device': device} LOG.debug("%s can not be found in database", device) return entry - def update_device_down(self, context, **kwargs): + def update_device_down(self, rpc_context, **kwargs): """Device no longer exists on agent""" # (TODO) garyk - live migration and port status agent_id = kwargs.get('agent_id') @@ -93,14 +95,14 @@ class OVSRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin): entry = {'device': device, 'exists': True} # Set port status to DOWN - ovs_db_v2.set_port_status(port['id'], constants.PORT_STATUS_DOWN) + ovs_db_v2.set_port_status(port['id'], q_const.PORT_STATUS_DOWN) else: entry = {'device': device, 'exists': False} LOG.debug("%s can not be found in database", device) return entry - def tunnel_sync(self, context, **kwargs): + def tunnel_sync(self, rpc_context, **kwargs): """Update new tunnel. Updates the datbase with the tunnel IP. All listening agents will also @@ -108,12 +110,12 @@ class OVSRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin): """ tunnel_ip = kwargs.get('tunnel_ip') # Update the database with the IP - tunnel = ovs_db_v2.add_tunnel(tunnel_ip) - tunnels = ovs_db_v2.get_tunnels() + tunnel = ovs_db_v2.add_tunnel_endpoint(tunnel_ip) + tunnels = ovs_db_v2.get_tunnel_endpoints() entry = dict() entry['tunnels'] = tunnels # Notify all other listening agents - self.notifier.tunnel_update(self.context, tunnel.ip_address, + self.notifier.tunnel_update(self.rpc_context, tunnel.ip_address, tunnel.id) # Return the list of tunnels IP's to the agent return entry @@ -139,7 +141,7 @@ class AgentNotifierApi(proxy.RpcProxy): topics.PORT, topics.UPDATE) self.topic_tunnel_update = topics.get_topic_name(topic, - config.TUNNEL, + constants.TUNNEL, topics.UPDATE) def network_delete(self, context, network_id): @@ -186,34 +188,67 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2, supported_extension_aliases = ["provider", "os-quantum-router"] def __init__(self, configfile=None): - self.enable_tunneling = cfg.CONF.OVS.enable_tunneling - options = {"sql_connection": cfg.CONF.DATABASE.sql_connection} - options.update({'base': models_v2.model_base.BASEV2}) - sql_max_retries = cfg.CONF.DATABASE.sql_max_retries - options.update({"sql_max_retries": sql_max_retries}) - reconnect_interval = cfg.CONF.DATABASE.reconnect_interval - options.update({"reconnect_interval": reconnect_interval}) - db.configure_db(options) - - # update the vlan_id table based on current configuration - ovs_db_v2.update_vlan_id_pool() + ovs_db_v2.initialize() + self._parse_network_vlan_ranges() + ovs_db_v2.sync_vlan_allocations(self.network_vlan_ranges) + self._parse_tunnel_id_ranges() + ovs_db_v2.sync_tunnel_allocations(self.tunnel_id_ranges) self.agent_rpc = cfg.CONF.AGENT.rpc self.setup_rpc() def setup_rpc(self): # RPC support self.topic = topics.PLUGIN - self.context = context.RequestContext('quantum', 'quantum', - is_admin=False) + self.rpc_context = context.RequestContext('quantum', 'quantum', + is_admin=False) self.conn = rpc.create_connection(new=True) self.notifier = AgentNotifierApi(topics.AGENT) - self.callbacks = OVSRpcCallbacks(self.context, self.notifier) + self.callbacks = OVSRpcCallbacks(self.rpc_context, self.notifier) self.dispatcher = self.callbacks.create_rpc_dispatcher() self.conn.create_consumer(self.topic, self.dispatcher, fanout=False) # Consume from all consumers in a thread self.conn.consume_in_thread() + def _parse_network_vlan_ranges(self): + self.network_vlan_ranges = {} + for entry in cfg.CONF.OVS.network_vlan_ranges: + entry = entry.strip() + if ':' in entry: + try: + physical_network, vlan_min, vlan_max = entry.split(':') + self._add_network_vlan_range(physical_network.strip(), + int(vlan_min), + int(vlan_max)) + except ValueError as ex: + LOG.error("Invalid network VLAN range: \'%s\' - %s" % + (entry, ex)) + sys.exit(1) + else: + self._add_network(entry) + LOG.debug("network VLAN ranges: %s" % self.network_vlan_ranges) + + def _add_network_vlan_range(self, physical_network, vlan_min, vlan_max): + self._add_network(physical_network) + self.network_vlan_ranges[physical_network].append((vlan_min, vlan_max)) + + def _add_network(self, physical_network): + if physical_network not in self.network_vlan_ranges: + self.network_vlan_ranges[physical_network] = [] + + def _parse_tunnel_id_ranges(self): + self.tunnel_id_ranges = [] + for entry in cfg.CONF.OVS.tunnel_id_ranges: + entry = entry.strip() + try: + tun_min, tun_max = entry.split(':') + self.tunnel_id_ranges.append((int(tun_min), int(tun_max))) + except ValueError as ex: + LOG.error("Invalid tunnel ID range: \'%s\' - %s" % + (entry, ex)) + sys.exit(1) + LOG.debug("tunnel ID ranges: %s" % self.tunnel_id_ranges) + # TODO(rkukura) Use core mechanism for attribute authorization # when available. @@ -229,9 +264,18 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2, def _extend_network_dict(self, context, network): if self._check_provider_view_auth(context, network): - if not self.enable_tunneling: - network['provider:vlan_id'] = ovs_db_v2.get_vlan( - network['id'], context.session) + binding = ovs_db_v2.get_network_binding(context.session, + network['id']) + network['provider:network_type'] = binding.network_type + if binding.network_type == constants.TYPE_GRE: + network['provider:physical_network'] = None + network['provider:vlan_id'] = None + elif binding.network_type == constants.TYPE_FLAT: + network['provider:physical_network'] = binding.physical_network + network['provider:vlan_id'] = None + elif binding.network_type == constants.TYPE_VLAN: + network['provider:physical_network'] = binding.physical_network + network['provider:vlan_id'] = binding.physical_id def _process_provider_create(self, context, attrs): network_type = attrs.get('provider:network_type') @@ -251,16 +295,13 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2, if not network_type_set: msg = _("provider:network_type required") raise q_exc.InvalidInput(error_message=msg) - elif network_type == 'flat': - msg = _("plugin does not support flat networks") - raise q_exc.InvalidInput(error_message=msg) - # REVISIT(rkukura) to be enabled in phase 3 - # if vlan_id_set: - # msg = _("provider:vlan_id specified for flat network") - # raise q_exc.InvalidInput(error_message=msg) - # else: - # vlan_id = db.FLAT_VLAN_ID - elif network_type == 'vlan': + elif network_type == constants.TYPE_FLAT: + if vlan_id_set: + msg = _("provider:vlan_id specified for flat network") + raise q_exc.InvalidInput(error_message=msg) + else: + vlan_id = constants.FLAT_VLAN_ID + elif network_type == constants.TYPE_VLAN: if not vlan_id_set: msg = _("provider:vlan_id required") raise q_exc.InvalidInput(error_message=msg) @@ -269,18 +310,15 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2, raise q_exc.InvalidInput(error_message=msg) if physical_network_set: - msg = _("plugin does not support specifying physical_network") + if physical_network not in self.network_vlan_ranges: + msg = _("unknown provider:physical_network %s" % + physical_network) + raise q_exc.InvalidInput(error_message=msg) + elif 'default' in self.network_vlan_ranges: + physical_network = 'default' + else: + msg = _("provider:physical_network required") raise q_exc.InvalidInput(error_message=msg) - # REVISIT(rkukura) to be enabled in phase 3 - # if physical_network not in self.physical_networks: - # msg = _("unknown provider:physical_network %s" % - # physical_network) - # raise q_exc.InvalidInput(error_message=msg) - #elif 'default' in self.physical_networks: - # physical_network = 'default' - #else: - # msg = _("provider:physical_network required") - # raise q_exc.InvalidInput(error_message=msg) return (network_type, physical_network, vlan_id) @@ -304,38 +342,58 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2, def create_network(self, context, network): (network_type, physical_network, - vlan_id) = self._process_provider_create(context, - network['network']) + physical_id) = self._process_provider_create(context, + network['network']) - net = super(OVSQuantumPluginV2, self).create_network(context, network) - try: + session = context.session + with session.begin(subtransactions=True): if not network_type: - vlan_id = ovs_db_v2.reserve_vlan_id(context.session) + try: + (physical_network, + physical_id) = ovs_db_v2.reserve_vlan(session) + network_type = constants.TYPE_VLAN + except q_exc.NoNetworkAvailable: + physical_id = ovs_db_v2.reserve_tunnel(session) + network_type = constants.TYPE_GRE else: - ovs_db_v2.reserve_specific_vlan_id(vlan_id, context.session) - except Exception: - super(OVSQuantumPluginV2, self).delete_network(context, net['id']) - raise - + ovs_db_v2.reserve_specific_vlan(session, physical_network, + physical_id) + net = super(OVSQuantumPluginV2, self).create_network(context, + network) + ovs_db_v2.add_network_binding(session, net['id'], network_type, + physical_network, physical_id) + self._extend_network_dict(context, net) + # note - exception will rollback entire transaction LOG.debug("Created network: %s" % net['id']) - ovs_db_v2.add_vlan_binding(vlan_id, str(net['id']), context.session) - self._extend_network_dict(context, net) return net def update_network(self, context, id, network): self._check_provider_update(context, network['network']) - net = super(OVSQuantumPluginV2, self).update_network(context, id, - network) - self._extend_network_dict(context, net) + session = context.session + with session.begin(subtransactions=True): + net = super(OVSQuantumPluginV2, self).update_network(context, id, + network) + self._extend_network_dict(context, net) return net def delete_network(self, context, id): - vlan_id = ovs_db_v2.get_vlan(id) - result = super(OVSQuantumPluginV2, self).delete_network(context, id) - ovs_db_v2.release_vlan_id(vlan_id) + session = context.session + with session.begin(subtransactions=True): + binding = ovs_db_v2.get_network_binding(session, id) + result = super(OVSQuantumPluginV2, self).delete_network(context, + id) + if binding.network_type == constants.TYPE_GRE: + ovs_db_v2.release_tunnel(session, binding.physical_id, + self.tunnel_id_ranges) + else: + ovs_db_v2.release_vlan(session, binding.physical_network, + binding.physical_id, + self.network_vlan_ranges) + # the network_binding record is deleted via cascade from + # the network record, so explicit removal is not necessary if self.agent_rpc: - self.notifier.network_delete(self.context, id) + self.notifier.network_delete(self.rpc_context, id) return result def get_network(self, context, id, fields=None): @@ -358,8 +416,11 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2, port = super(OVSQuantumPluginV2, self).update_port(context, id, port) if self.agent_rpc: if original_port['admin_state_up'] != port['admin_state_up']: - vlan_id = ovs_db_v2.get_vlan(port['network_id']) - self.notifier.port_update(self.context, port, vlan_id) + binding = ovs_db_v2.get_network_binding(None, + port['network_id']) + # REVISIT(rkukura): needs other binding data as well + self.notifier.port_update(self.rpc_context, port, + binding.physical_id) return port def delete_port(self, context, id): diff --git a/quantum/tests/unit/openvswitch/test_ovs_db.py b/quantum/tests/unit/openvswitch/test_ovs_db.py index 8f82ea434..373502d67 100644 --- a/quantum/tests/unit/openvswitch/test_ovs_db.py +++ b/quantum/tests/unit/openvswitch/test_ovs_db.py @@ -17,96 +17,204 @@ import unittest2 from quantum.common import exceptions as q_exc from quantum.db import api as db -from quantum.db import models_v2 -from quantum.plugins.openvswitch.common import config -from quantum.openstack.common import cfg from quantum.plugins.openvswitch import ovs_db_v2 +PHYS_NET = 'physnet1' VLAN_MIN = 10 VLAN_MAX = 19 +VLAN_RANGES = {PHYS_NET: [(VLAN_MIN, VLAN_MAX)]} +UPDATED_VLAN_RANGES = {PHYS_NET: [(VLAN_MIN + 5, VLAN_MAX + 5)]} +TUN_MIN = 100 +TUN_MAX = 109 +TUNNEL_RANGES = [(TUN_MIN, TUN_MAX)] +UPDATED_TUNNEL_RANGES = [(TUN_MIN + 5, TUN_MAX + 5)] +TEST_NETWORK_ID = 'abcdefghijklmnopqrstuvwxyz' -class OVSVlanIdsTest(unittest2.TestCase): +class VlanAllocationsTest(unittest2.TestCase): def setUp(self): - cfg.CONF.set_override('vlan_min', VLAN_MIN, group='OVS') - cfg.CONF.set_override('vlan_max', VLAN_MAX, group='OVS') - - options = {"sql_connection": cfg.CONF.DATABASE.sql_connection} - options.update({'base': models_v2.model_base.BASEV2}) - sql_max_retries = cfg.CONF.DATABASE.sql_max_retries - options.update({"sql_max_retries": sql_max_retries}) - reconnect_interval = cfg.CONF.DATABASE.reconnect_interval - options.update({"reconnect_interval": reconnect_interval}) - db.configure_db(options) - - ovs_db_v2.update_vlan_id_pool() + ovs_db_v2.initialize() + ovs_db_v2.sync_vlan_allocations(VLAN_RANGES) + self.session = db.get_session() def tearDown(self): db.clear_db() - cfg.CONF.reset() - - def test_update_vlan_id_pool(self): - self.assertIsNone(ovs_db_v2.get_vlan_id(VLAN_MIN - 1)) - self.assertFalse(ovs_db_v2.get_vlan_id(VLAN_MIN).vlan_used) - self.assertFalse(ovs_db_v2.get_vlan_id(VLAN_MIN + 1).vlan_used) - self.assertFalse(ovs_db_v2.get_vlan_id(VLAN_MAX).vlan_used) - self.assertIsNone(ovs_db_v2.get_vlan_id(VLAN_MAX + 1)) - - cfg.CONF.set_override('vlan_min', VLAN_MIN + 5, group='OVS') - cfg.CONF.set_override('vlan_max', VLAN_MAX + 5, group='OVS') - ovs_db_v2.update_vlan_id_pool() - - self.assertIsNone(ovs_db_v2.get_vlan_id(VLAN_MIN + 5 - 1)) - self.assertFalse(ovs_db_v2.get_vlan_id(VLAN_MIN + 5).vlan_used) - self.assertFalse(ovs_db_v2.get_vlan_id(VLAN_MIN + 5 + 1).vlan_used) - self.assertFalse(ovs_db_v2.get_vlan_id(VLAN_MAX + 5).vlan_used) - self.assertIsNone(ovs_db_v2.get_vlan_id(VLAN_MAX + 5 + 1)) - - def test_vlan_id_pool(self): - session = db.get_session() + + def test_sync_vlan_allocations(self): + self.assertIsNone(ovs_db_v2.get_vlan_allocation(PHYS_NET, + VLAN_MIN - 1)) + self.assertFalse(ovs_db_v2.get_vlan_allocation(PHYS_NET, + VLAN_MIN).allocated) + self.assertFalse(ovs_db_v2.get_vlan_allocation(PHYS_NET, + VLAN_MIN + 1).allocated) + self.assertFalse(ovs_db_v2.get_vlan_allocation(PHYS_NET, + VLAN_MAX).allocated) + self.assertIsNone(ovs_db_v2.get_vlan_allocation(PHYS_NET, + VLAN_MAX + 1)) + + ovs_db_v2.sync_vlan_allocations(UPDATED_VLAN_RANGES) + + self.assertIsNone(ovs_db_v2.get_vlan_allocation(PHYS_NET, + VLAN_MIN + 5 - 1)) + self.assertFalse(ovs_db_v2.get_vlan_allocation(PHYS_NET, + VLAN_MIN + 5). + allocated) + self.assertFalse(ovs_db_v2.get_vlan_allocation(PHYS_NET, + VLAN_MIN + 5 + 1). + allocated) + self.assertFalse(ovs_db_v2.get_vlan_allocation(PHYS_NET, + VLAN_MAX + 5 - 1). + allocated) + self.assertFalse(ovs_db_v2.get_vlan_allocation(PHYS_NET, + VLAN_MAX + 5). + allocated) + self.assertIsNone(ovs_db_v2.get_vlan_allocation(PHYS_NET, + VLAN_MAX + 5 + 1)) + + def test_vlan_pool(self): vlan_ids = set() for x in xrange(VLAN_MIN, VLAN_MAX + 1): - vlan_id = ovs_db_v2.reserve_vlan_id(db.get_session()) + physical_network, vlan_id = ovs_db_v2.reserve_vlan(self.session) + self.assertEqual(physical_network, PHYS_NET) self.assertGreaterEqual(vlan_id, VLAN_MIN) self.assertLessEqual(vlan_id, VLAN_MAX) vlan_ids.add(vlan_id) with self.assertRaises(q_exc.NoNetworkAvailable): - vlan_id = ovs_db_v2.reserve_vlan_id(session) - - for vlan_id in vlan_ids: - ovs_db_v2.release_vlan_id(vlan_id) + physical_network, vlan_id = ovs_db_v2.reserve_vlan(self.session) - def test_invalid_specific_vlan_id(self): - session = db.get_session() - with self.assertRaises(q_exc.InvalidInput): - vlan_id = ovs_db_v2.reserve_specific_vlan_id(0, session) + 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) - with self.assertRaises(q_exc.InvalidInput): - vlan_id = ovs_db_v2.reserve_specific_vlan_id(4095, session) + for vlan_id in vlan_ids: + ovs_db_v2.release_vlan(self.session, PHYS_NET, vlan_id, + VLAN_RANGES) - def test_specific_vlan_id_inside_pool(self): - session = db.get_session() + def test_specific_vlan_inside_pool(self): vlan_id = VLAN_MIN + 5 - self.assertFalse(ovs_db_v2.get_vlan_id(vlan_id).vlan_used) - ovs_db_v2.reserve_specific_vlan_id(vlan_id, session) - self.assertTrue(ovs_db_v2.get_vlan_id(vlan_id).vlan_used) + self.assertFalse(ovs_db_v2.get_vlan_allocation(PHYS_NET, + vlan_id).allocated) + ovs_db_v2.reserve_specific_vlan(self.session, PHYS_NET, vlan_id) + self.assertTrue(ovs_db_v2.get_vlan_allocation(PHYS_NET, + vlan_id).allocated) with self.assertRaises(q_exc.VlanIdInUse): - ovs_db_v2.reserve_specific_vlan_id(vlan_id, session) + ovs_db_v2.reserve_specific_vlan(self.session, PHYS_NET, vlan_id) - ovs_db_v2.release_vlan_id(vlan_id) - self.assertFalse(ovs_db_v2.get_vlan_id(vlan_id).vlan_used) + ovs_db_v2.release_vlan(self.session, PHYS_NET, vlan_id, VLAN_RANGES) + self.assertFalse(ovs_db_v2.get_vlan_allocation(PHYS_NET, + vlan_id).allocated) - def test_specific_vlan_id_outside_pool(self): - session = db.get_session() + def test_specific_vlan_outside_pool(self): vlan_id = VLAN_MAX + 5 - self.assertIsNone(ovs_db_v2.get_vlan_id(vlan_id)) - ovs_db_v2.reserve_specific_vlan_id(vlan_id, session) - self.assertTrue(ovs_db_v2.get_vlan_id(vlan_id).vlan_used) + self.assertIsNone(ovs_db_v2.get_vlan_allocation(PHYS_NET, vlan_id)) + ovs_db_v2.reserve_specific_vlan(self.session, PHYS_NET, vlan_id) + self.assertTrue(ovs_db_v2.get_vlan_allocation(PHYS_NET, + vlan_id).allocated) with self.assertRaises(q_exc.VlanIdInUse): - ovs_db_v2.reserve_specific_vlan_id(vlan_id, session) + ovs_db_v2.reserve_specific_vlan(self.session, PHYS_NET, vlan_id) + + ovs_db_v2.release_vlan(self.session, PHYS_NET, vlan_id, VLAN_RANGES) + self.assertIsNone(ovs_db_v2.get_vlan_allocation(PHYS_NET, vlan_id)) + + +class TunnelAllocationsTest(unittest2.TestCase): + def setUp(self): + ovs_db_v2.initialize() + ovs_db_v2.sync_tunnel_allocations(TUNNEL_RANGES) + self.session = db.get_session() + + def tearDown(self): + db.clear_db() + + def test_sync_tunnel_allocations(self): + self.assertIsNone(ovs_db_v2.get_tunnel_allocation(TUN_MIN - 1)) + self.assertFalse(ovs_db_v2.get_tunnel_allocation(TUN_MIN).allocated) + self.assertFalse(ovs_db_v2.get_tunnel_allocation(TUN_MIN + 1). + allocated) + self.assertFalse(ovs_db_v2.get_tunnel_allocation(TUN_MAX).allocated) + self.assertIsNone(ovs_db_v2.get_tunnel_allocation(TUN_MAX + 1)) + + ovs_db_v2.sync_tunnel_allocations(UPDATED_TUNNEL_RANGES) + + self.assertIsNone(ovs_db_v2.get_tunnel_allocation(TUN_MIN + 5 - 1)) + self.assertFalse(ovs_db_v2.get_tunnel_allocation(TUN_MIN + 5). + allocated) + self.assertFalse(ovs_db_v2.get_tunnel_allocation(TUN_MIN + 5 + 1). + allocated) + self.assertFalse(ovs_db_v2.get_tunnel_allocation(TUN_MAX + 5 - 1). + allocated) + self.assertFalse(ovs_db_v2.get_tunnel_allocation(TUN_MAX + 5). + allocated) + self.assertIsNone(ovs_db_v2.get_tunnel_allocation(TUN_MAX + 5 + 1)) + + def test_tunnel_pool(self): + tunnel_ids = set() + for x in xrange(TUN_MIN, TUN_MAX + 1): + tunnel_id = ovs_db_v2.reserve_tunnel(self.session) + self.assertGreaterEqual(tunnel_id, TUN_MIN) + self.assertLessEqual(tunnel_id, TUN_MAX) + tunnel_ids.add(tunnel_id) + + with self.assertRaises(q_exc.NoNetworkAvailable): + tunnel_id = ovs_db_v2.reserve_tunnel(self.session) + + ovs_db_v2.release_tunnel(self.session, tunnel_ids.pop(), TUNNEL_RANGES) + tunnel_id = ovs_db_v2.reserve_tunnel(self.session) + self.assertGreaterEqual(tunnel_id, TUN_MIN) + self.assertLessEqual(tunnel_id, TUN_MAX) + tunnel_ids.add(tunnel_id) + + for tunnel_id in tunnel_ids: + ovs_db_v2.release_tunnel(self.session, tunnel_id, TUNNEL_RANGES) + + def test_specific_tunnel_inside_pool(self): + tunnel_id = TUN_MIN + 5 + self.assertFalse(ovs_db_v2.get_tunnel_allocation(tunnel_id).allocated) + ovs_db_v2.reserve_specific_tunnel(self.session, tunnel_id) + self.assertTrue(ovs_db_v2.get_tunnel_allocation(tunnel_id).allocated) + + with self.assertRaises(q_exc.TunnelIdInUse): + ovs_db_v2.reserve_specific_tunnel(self.session, tunnel_id) + + ovs_db_v2.release_tunnel(self.session, tunnel_id, TUNNEL_RANGES) + self.assertFalse(ovs_db_v2.get_tunnel_allocation(tunnel_id).allocated) + + def test_specific_tunnel_outside_pool(self): + tunnel_id = TUN_MAX + 5 + self.assertIsNone(ovs_db_v2.get_tunnel_allocation(tunnel_id)) + ovs_db_v2.reserve_specific_tunnel(self.session, tunnel_id) + self.assertTrue(ovs_db_v2.get_tunnel_allocation(tunnel_id).allocated) + + with self.assertRaises(q_exc.TunnelIdInUse): + ovs_db_v2.reserve_specific_tunnel(self.session, tunnel_id) + + ovs_db_v2.release_tunnel(self.session, tunnel_id, TUNNEL_RANGES) + self.assertIsNone(ovs_db_v2.get_tunnel_allocation(tunnel_id)) + + +class NetworkBindingsTest(unittest2.TestCase): + def setUp(self): + ovs_db_v2.initialize() + self.session = db.get_session() + + def tearDown(self): + db.clear_db() - ovs_db_v2.release_vlan_id(vlan_id) - self.assertIsNone(ovs_db_v2.get_vlan_id(vlan_id)) + 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) diff --git a/quantum/tests/unit/openvswitch/test_ovs_defaults.py b/quantum/tests/unit/openvswitch/test_ovs_defaults.py index f8e7f1ec7..c1d09cde3 100644 --- a/quantum/tests/unit/openvswitch/test_ovs_defaults.py +++ b/quantum/tests/unit/openvswitch/test_ovs_defaults.py @@ -22,7 +22,6 @@ from quantum.plugins.openvswitch.common import config class ConfigurationTest(unittest.TestCase): def test_defaults(self): - self.assertFalse(cfg.CONF.OVS.enable_tunneling) self.assertEqual('br-int', cfg.CONF.OVS.integration_bridge) self.assertEqual('br-tun', cfg.CONF.OVS.tunnel_bridge) self.assertEqual('sqlite://', cfg.CONF.DATABASE.sql_connection) @@ -30,3 +29,14 @@ class ConfigurationTest(unittest.TestCase): self.assertEqual(2, cfg.CONF.DATABASE.reconnect_interval) self.assertEqual(2, cfg.CONF.AGENT.polling_interval) self.assertEqual('sudo', cfg.CONF.AGENT.root_helper) + + mappings = cfg.CONF.OVS.bridge_mappings + self.assertEqual(1, len(mappings)) + self.assertEqual('default:br-eth1', mappings[0]) + + ranges = cfg.CONF.OVS.network_vlan_ranges + self.assertEqual(1, len(ranges)) + self.assertEqual('default:1000:2999', ranges[0]) + + ranges = cfg.CONF.OVS.tunnel_id_ranges + self.assertEqual(0, len(ranges)) diff --git a/quantum/tests/unit/openvswitch/test_ovs_rpcapi.py b/quantum/tests/unit/openvswitch/test_ovs_rpcapi.py index 11c3506c0..32ea25da9 100644 --- a/quantum/tests/unit/openvswitch/test_ovs_rpcapi.py +++ b/quantum/tests/unit/openvswitch/test_ovs_rpcapi.py @@ -26,7 +26,7 @@ from quantum.common import topics from quantum.openstack.common import context from quantum.openstack.common import rpc from quantum.plugins.openvswitch import ovs_quantum_plugin as povs -from quantum.plugins.openvswitch.common import config +from quantum.plugins.openvswitch.common import constants class rpcApiTestCase(unittest2.TestCase): @@ -81,7 +81,7 @@ class rpcApiTestCase(unittest2.TestCase): rpcapi = povs.AgentNotifierApi(topics.AGENT) self._test_ovs_api(rpcapi, topics.get_topic_name(topics.AGENT, - config.TUNNEL, + constants.TUNNEL, topics.UPDATE), 'tunnel_update', rpc_method='fanout_cast', tunnel_ip='fake_ip', tunnel_id='fake_id') diff --git a/quantum/tests/unit/openvswitch/test_ovs_tunnel.py b/quantum/tests/unit/openvswitch/test_ovs_tunnel.py index 8a0eeb3db..c9a3385e1 100644 --- a/quantum/tests/unit/openvswitch/test_ovs_tunnel.py +++ b/quantum/tests/unit/openvswitch/test_ovs_tunnel.py @@ -28,7 +28,7 @@ NET_UUID = '3faeebfe-5d37-11e1-a64b-000c29d5f0a7' LS_ID = '42' LV_ID = 42 LV_IDS = [42, 43] -LVM = ovs_quantum_agent.LocalVLANMapping(LV_ID, LS_ID, LV_IDS) +LVM = ovs_quantum_agent.LocalVLANMapping(LV_ID, 'gre', None, LS_ID, LV_IDS) VIF_ID = '404deaec-5d37-11e1-a64b-000c29d5f0a8' VIF_MAC = '3c:09:24:1e:78:23' OFPORT_NUM = 1 @@ -79,10 +79,10 @@ class TunnelTest(unittest.TestCase): def testConstruct(self): self.mox.ReplayAll() - b = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE, - self.TUN_BRIDGE, - '10.0.0.1', - 'sudo', 2, 2, False) + b = ovs_quantum_agent.OVSQuantumAgent(self.INT_BRIDGE, + self.TUN_BRIDGE, + '10.0.0.1', {}, + 'sudo', 2, 2, False) self.mox.VerifyAll() def testProvisionLocalVlan(self): @@ -96,24 +96,24 @@ class TunnelTest(unittest.TestCase): self.mox.ReplayAll() - a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE, - self.TUN_BRIDGE, - '10.0.0.1', - 'sudo', 2, 2, False) + a = ovs_quantum_agent.OVSQuantumAgent(self.INT_BRIDGE, + self.TUN_BRIDGE, + '10.0.0.1', {}, + 'sudo', 2, 2, False) a.available_local_vlans = set([LV_ID]) - a.provision_local_vlan(NET_UUID, LS_ID) + a.provision_local_vlan(NET_UUID, 'gre', None, LS_ID) self.mox.VerifyAll() def testReclaimLocalVlan(self): - self.mock_tun_bridge.delete_flows(tun_id=LVM.lsw_id) + self.mock_tun_bridge.delete_flows(tun_id=LVM.physical_id) self.mock_tun_bridge.delete_flows(dl_vlan=LVM.vlan) self.mox.ReplayAll() - a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE, - self.TUN_BRIDGE, - '10.0.0.1', - 'sudo', 2, 2, False) + a = ovs_quantum_agent.OVSQuantumAgent(self.INT_BRIDGE, + self.TUN_BRIDGE, + '10.0.0.1', {}, + 'sudo', 2, 2, False) a.available_local_vlans = set() a.local_vlan_map[NET_UUID] = LVM a.reclaim_local_vlan(NET_UUID, LVM) @@ -131,20 +131,20 @@ class TunnelTest(unittest.TestCase): actions=action_string) self.mox.ReplayAll() - a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE, - self.TUN_BRIDGE, - '10.0.0.1', - 'sudo', 2, 2, False) + a = ovs_quantum_agent.OVSQuantumAgent(self.INT_BRIDGE, + self.TUN_BRIDGE, + '10.0.0.1', {}, + 'sudo', 2, 2, False) a.local_vlan_map[NET_UUID] = LVM - a.port_bound(VIF_PORT, NET_UUID, LS_ID) + a.port_bound(VIF_PORT, NET_UUID, 'gre', None, LS_ID) self.mox.VerifyAll() def testPortUnbound(self): self.mox.ReplayAll() - a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE, - self.TUN_BRIDGE, - '10.0.0.1', - 'sudo', 2, 2, False) + a = ovs_quantum_agent.OVSQuantumAgent(self.INT_BRIDGE, + self.TUN_BRIDGE, + '10.0.0.1', {}, + 'sudo', 2, 2, False) a.available_local_vlans = set([LV_ID]) a.local_vlan_map[NET_UUID] = LVM a.port_unbound(VIF_PORT, NET_UUID) @@ -158,10 +158,10 @@ class TunnelTest(unittest.TestCase): actions='drop') self.mox.ReplayAll() - a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE, - self.TUN_BRIDGE, - '10.0.0.1', - 'sudo', 2, 2, False) + a = ovs_quantum_agent.OVSQuantumAgent(self.INT_BRIDGE, + self.TUN_BRIDGE, + '10.0.0.1', {}, + 'sudo', 2, 2, False) a.available_local_vlans = set([LV_ID]) a.local_vlan_map[NET_UUID] = LVM a.port_dead(VIF_PORT) -- 2.45.2