]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Implementation of 2nd phase of provider extension for openswitch
authorBob Kukura <rkukura@redhat.com>
Wed, 15 Aug 2012 06:43:17 +0000 (02:43 -0400)
committerBob Kukura <rkukura@redhat.com>
Fri, 31 Aug 2012 01:24:11 +0000 (21:24 -0400)
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

13 files changed:
etc/quantum/plugins/openvswitch/ovs_quantum_plugin.ini
quantum/agent/linux/ovs_lib.py
quantum/common/exceptions.py
quantum/plugins/openvswitch/agent/ovs_quantum_agent.py
quantum/plugins/openvswitch/common/config.py
quantum/plugins/openvswitch/common/constants.py [new file with mode: 0644]
quantum/plugins/openvswitch/ovs_db_v2.py
quantum/plugins/openvswitch/ovs_models_v2.py
quantum/plugins/openvswitch/ovs_quantum_plugin.py
quantum/tests/unit/openvswitch/test_ovs_db.py
quantum/tests/unit/openvswitch/test_ovs_defaults.py
quantum/tests/unit/openvswitch/test_ovs_rpcapi.py
quantum/tests/unit/openvswitch/test_ovs_tunnel.py

index 178b49a7227e96ab8c6e7e3b35d679b7cca90187..1b1b870879624e44650aa68f2bb7e87efdbe9303 100644 (file)
@@ -12,29 +12,38 @@ sql_connection = sqlite://
 reconnect_interval = 2
 
 [OVS]
-# This enables the new OVSQuantumTunnelAgent which enables tunneling
-# between hybervisors. Leave it set to False or omit for legacy behavior.
-enable_tunneling = False
+# (ListOpt) Comma-separated list of
+# <physical_network>:<vlan_min>:<vlan_max> tuples enumerating ranges
+# of VLAN IDs on named physical networks that are available for
+# allocation.
+# network_vlan_ranges = default:1000:2999
+
+# (ListOpt) Comma-separated list of <tun_min>:<tun_max> tuples
+# enumerating ranges of GRE tunnel IDs that are available for
+# allocation.
+# tunnel_id_ranges =
 
 # Do not change this parameter unless you have a good reason to.
 # This is the name of the OVS integration bridge. There is one per hypervisor.
 # The integration bridge acts as a virtual "patch port". All VM VIFs are
 # attached to this bridge and then "patched" according to their network
 # connectivity.
-integration_bridge = br-int
+integration_bridge = br-int
 
-# Only used if enable-tunneling (above) is True.
+# Only used if tunnel_id_ranges (above) is not empty.
 # In most cases, the default value should be fine.
-tunnel_bridge = br-tun
+tunnel_bridge = br-tun
 
-# Uncomment this line if enable-tunneling is True above.
+# (ListOpt) Comma-separated list of <physical_network>:<bridge> tuples
+# mapping physical network names to agent's node-specific OVS bridge
+# names. Each bridge must exist, and should have physical network
+# interface configured as a port.
+# bridge_mappings = default:br-eth1
+
+# Uncomment this line if tunnel_id_ranges (above) is not empty.
 # Set local-ip to be the local IP address of this hypervisor.
 # local_ip = 10.0.0.3
 
-# Uncomment if you want to use custom VLAN range.
-# vlan_min = 1
-# vlan_max = 4094
-
 [AGENT]
 # Agent's polling interval in seconds
 polling_interval = 2
@@ -47,25 +56,26 @@ root_helper = sudo
 # Sample Configurations.
 #-----------------------------------------------------------------------------
 #
-# 1. Without tunneling.
+# 1. With VLANs on eth1.
 # [DATABASE]
 # sql_connection = mysql://root:nova@127.0.0.1:3306/ovs_quantum
 # [OVS]
-# enable_tunneling = False
+# network_vlan_ranges = default:2000:3999
+# tunnel_id_ranges =
 # integration_bridge = br-int
+# bridge_mappings = default:br-eth1
 # [AGENT]
 # root_helper = sudo
 # Add the following setting, if you want to log to a file
-# log_file = /var/log/quantum/ovs_quantum_agent.log
 #
 # 2. With tunneling.
 # [DATABASE]
 # sql_connection = mysql://root:nova@127.0.0.1:3306/ovs_quantum
 # [OVS]
-# enable_tunneling = True
+# network_vlan_ranges =
+# tunnel_id_ranges = 1:1000
 # integration_bridge = br-int
 # tunnel_bridge = br-tun
-# remote-ip-file = /opt/stack/remote-ips.txt
 # local_ip = 10.0.0.3
 # [AGENT]
 # root_helper = sudo
index b6f9d0153f0f7a7e499efae7bcad6d144749adfd..e982bac80469f24164633c9d94f10edfa15b75df 100644 (file)
@@ -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])
index 4e0f0456d46cc4bdfcc0e1e0a0ed0bac8fd73fa8..054bb433ac34953ce01df353129f56129263d7e5 100644 (file)
@@ -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
 
index b87afff7edef01c5a183fd15bb0931d6f32190ed..c0e1e35d28ceabddd74c91f14e0ff12e890e58d9 100755 (executable)
@@ -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)
index df83327bb1fd7f59b96310aed3a9a3b02c74d107..d89a970eb76bba77497816d7a52b8c8bb7a14290 100644 (file)
@@ -17,8 +17,9 @@
 from quantum.openstack.common import cfg
 
 
-# Topic for tunnel notifications between the plugin and agent
-TUNNEL = 'tunnel'
+DEFAULT_BRIDGE_MAPPINGS = ['default:br-eth1']
+DEFAULT_VLAN_RANGES = ['default:1000:2999']
+DEFAULT_TUNNEL_RANGES = []
 
 database_opts = [
     cfg.StrOpt('sql_connection', default='sqlite://'),
@@ -27,18 +28,24 @@ database_opts = [
 ]
 
 ovs_opts = [
-    cfg.BoolOpt('enable_tunneling', default=False),
     cfg.StrOpt('integration_bridge', default='br-int'),
     cfg.StrOpt('tunnel_bridge', default='br-tun'),
     cfg.StrOpt('local_ip', default='10.0.0.3'),
-    cfg.IntOpt('vlan_min', default=1),
-    cfg.IntOpt('vlan_max', default=4094),
+    cfg.ListOpt('bridge_mappings',
+                default=DEFAULT_BRIDGE_MAPPINGS,
+                help="List of <physical_network>:<bridge>"),
+    cfg.ListOpt('network_vlan_ranges',
+                default=DEFAULT_VLAN_RANGES,
+                help="List of <physical_network>:<vlan_min>:<vlan_max> "
+                "or <physical_network>"),
+    cfg.ListOpt('tunnel_id_ranges',
+                default=DEFAULT_TUNNEL_RANGES,
+                help="List of <tun_min>:<tun_max>"),
 ]
 
 agent_opts = [
     cfg.IntOpt('polling_interval', default=2),
     cfg.StrOpt('root_helper', default='sudo'),
-    cfg.StrOpt('log_file', default=None),
     cfg.BoolOpt('rpc', default=True),
 ]
 
diff --git a/quantum/plugins/openvswitch/common/constants.py b/quantum/plugins/openvswitch/common/constants.py
new file mode 100644 (file)
index 0000000..d23f2cd
--- /dev/null
@@ -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-'
index 324ea896af33a253ab05a3371e272e3a2753b30f..58be6b637cd3fa47edc75a0ddb4f0f94d8377f28 100644 (file)
@@ -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
index aa427a41988f28ce7264e7b5b42aee63c849a384..09972526aa26cbede8cf8c0a3e6ea7db6d08d691 100644 (file)
@@ -22,41 +22,69 @@ from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
 from quantum.db.models_v2 import model_base
 
 
-class VlanID(model_base.BASEV2):
-    """Represents a vlan_id usage"""
-    __tablename__ = 'vlan_ids'
+class VlanAllocation(model_base.BASEV2):
+    """Represents allocation state of vlan_id on physical network"""
+    __tablename__ = 'ovs_vlan_allocations'
 
-    vlan_id = Column(Integer, nullable=False, primary_key=True)
-    vlan_used = Column(Boolean, nullable=False)
+    physical_network = Column(String(64), nullable=False, primary_key=True)
+    vlan_id = Column(Integer, nullable=False, primary_key=True,
+                     autoincrement=False)
+    allocated = Column(Boolean, nullable=False)
 
-    def __init__(self, vlan_id):
+    def __init__(self, physical_network, vlan_id):
+        self.physical_network = physical_network
         self.vlan_id = vlan_id
-        self.vlan_used = False
+        self.allocated = False
 
     def __repr__(self):
-        return "<VlanID(%d,%s)>" % (self.vlan_id, self.vlan_used)
+        return "<VlanAllocation(%s,%d,%s)>" % (self.physical_network,
+                                               self.vlan_id, self.allocated)
 
 
-class VlanBinding(model_base.BASEV2):
-    """Represents a binding of network_id to vlan_id."""
-    __tablename__ = 'vlan_bindings'
+class TunnelAllocation(model_base.BASEV2):
+    """Represents allocation state of tunnel_id"""
+    __tablename__ = 'ovs_tunnel_allocations'
 
-    network_id = Column(String(36), ForeignKey('networks.id',
-                                               ondelete="CASCADE"),
+    tunnel_id = Column(Integer, nullable=False, primary_key=True,
+                       autoincrement=False)
+    allocated = Column(Boolean, nullable=False)
+
+    def __init__(self, tunnel_id):
+        self.tunnel_id = tunnel_id
+        self.allocated = False
+
+    def __repr__(self):
+        return "<TunnelAllocation(%d,%s)>" % (self.tunnel_id, self.allocated)
+
+
+class NetworkBinding(model_base.BASEV2):
+    """Represents binding of virtual network to physical realization"""
+    __tablename__ = 'ovs_network_bindings'
+
+    network_id = Column(String(36),
+                        ForeignKey('networks.id', ondelete="CASCADE"),
                         primary_key=True)
-    vlan_id = Column(Integer, nullable=False)
+    network_type = Column(String(32), nullable=False)  # 'gre', 'vlan', 'flat'
+    physical_network = Column(String(64))
+    physical_id = Column(Integer)  # tunnel_id or vlan_id
 
-    def __init__(self, vlan_id, network_id):
+    def __init__(self, network_id, network_type, physical_network,
+                 physical_id):
         self.network_id = network_id
-        self.vlan_id = vlan_id
+        self.network_type = network_type
+        self.physical_network = physical_network
+        self.physical_id = physical_id
 
     def __repr__(self):
-        return "<VlanBinding(%s,%s)>" % (self.vlan_id, self.network_id)
+        return "<NetworkBinding(%s,%s,%s,%d)>" % (self.network_id,
+                                                  self.network_type,
+                                                  self.physical_network,
+                                                  self.physical_id)
 
 
 class TunnelIP(model_base.BASEV2):
-    """Represents a remote IP in tunnel mode."""
-    __tablename__ = 'tunnel_ips'
+    """Represents tunnel endpoint in DB mode"""
+    __tablename__ = 'ovs_tunnel_ips'
 
     ip_address = Column(String(255), primary_key=True)
 
@@ -67,9 +95,9 @@ class TunnelIP(model_base.BASEV2):
         return "<TunnelIP(%s)>" % (self.ip_address)
 
 
-class TunnelInfo(model_base.BASEV2):
-    """Represents remote tunnel information in tunnel mode."""
-    __tablename__ = 'tunnel_info'
+class TunnelEndpoint(model_base.BASEV2):
+    """Represents tunnel endpoint in RPC mode"""
+    __tablename__ = 'ovs_tunnel_endpoints'
 
     ip_address = Column(String(64), primary_key=True)
     id = Column(Integer, nullable=False)
@@ -79,4 +107,4 @@ class TunnelInfo(model_base.BASEV2):
         self.id = id
 
     def __repr__(self):
-        return "<TunnelInfo(%s,%s)>" % (self.ip_address, self.id)
+        return "<TunnelEndpoint(%s,%s)>" % (self.ip_address, self.id)
index 335f0ac10fe8a7ea86371c8e56eb54e7f459bbce..f8e2702a3145c4e2886b20ffd93328f340220f2b 100644 (file)
 
 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):
index 8f82ea434a31714d1a6d6afa35c34a3afbb024c5..373502d676d1d94ef1bf341ec08ee5943f079272 100644 (file)
@@ -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)
index f8e7f1ec7cd862f44ed2adea5cf0dc96084ca895..c1d09cde3a26dafd6b09ba333e63960e0e1ab2f1 100644 (file)
@@ -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))
index 11c3506c00261998e3b8f19a306db3af2b2b7b4b..32ea25da9632199dc086a5adf5d3fdf99ead3ad1 100644 (file)
@@ -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')
index 8a0eeb3dbeab8c938d35d42cbef495e6de1a8d9b..c9a3385e108ac95bd9b7b93b65a1a1ca79dd824c 100644 (file)
@@ -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)