]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Implementation of second phase of provider extension.
authorBob Kukura <rkukura@redhat.com>
Mon, 6 Aug 2012 13:42:20 +0000 (09:42 -0400)
committerBob Kukura <rkukura@redhat.com>
Tue, 14 Aug 2012 05:55:53 +0000 (01:55 -0400)
Enhances provider extension to support flat networks and VLANs on
multiple physical networks. Implements blueprint provider-networks.

To create a flat network using the CLI with admin rights:

net-create --tenant_id <tenant-id> <net-name> --provider:network_type flat --provider:physical_network <physical-network>

To create a VLAN network using the CLI with admin rights:

net-create --tenant_id <tenant-id> <net-name> --provider:network_type vlan --provider:physical_network <physical-network> --provider:vlan_id <vlan-id>

The provider extension is supported by the linuxbridge and openvswitch
plugins and their agents [openvswitch phase 2 implementation is
in-progress, and does not yet support flat networks or multiple
interfaces].

Ranges of VLANs available on named physical networks for normal
allocation are specified in the plugin's config file via the
ListOpt syntax:

network_vlan_ranges = <physical_network>:<vlan_min>:<vlan_max>

The mapping of each named physical network to its physical network
interface is specified (per-agent-host) in the agent's config file via
the ListOpt syntax:

physical_interface_mappings = <physical_network>:<physical_interface>

See quantum/plugins/linuxbridge/README for details and examples of
network_vlan_ranges and physical_interface_mappings usage.

Also, bulk operations are enabled for the linuxbridge plugin.

Change-Id: I93402bd5cc6316e9408ea71c3b3989d06898ee30

22 files changed:
etc/quantum.conf
etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini
quantum/api/v2/attributes.py
quantum/common/exceptions.py
quantum/db/db_base_plugin_v2.py
quantum/extensions/providernet.py
quantum/plugins/linuxbridge/README
quantum/plugins/linuxbridge/agent/linuxbridge_quantum_agent.py
quantum/plugins/linuxbridge/common/config.py
quantum/plugins/linuxbridge/common/constants.py
quantum/plugins/linuxbridge/common/exceptions.py [deleted file]
quantum/plugins/linuxbridge/db/l2network_db.py [deleted file]
quantum/plugins/linuxbridge/db/l2network_db_v2.py [new file with mode: 0644]
quantum/plugins/linuxbridge/db/l2network_models_v2.py
quantum/plugins/linuxbridge/lb_quantum_plugin.py
quantum/plugins/linuxbridge/tests/unit/test_defaults.py [new file with mode: 0644]
quantum/plugins/linuxbridge/tests/unit/test_lb_db.py [new file with mode: 0644]
quantum/plugins/linuxbridge/tests/unit/test_rpcapi.py
quantum/plugins/openvswitch/ovs_db_v2.py
quantum/plugins/openvswitch/ovs_quantum_plugin.py
quantum/plugins/openvswitch/tests/unit/test_defaults.py
quantum/tests/etc/quantum.conf.test

index ad8e18de2a17caff21b8fcb0703296779d8d4a28..86eeb9c69eb70f8de95ed3eb401589afba4a64ae 100644 (file)
@@ -43,7 +43,7 @@ api_paste_config = api-paste.ini
 # allow_bulk = True
 # RPC configuration options. Defined in rpc __init__
 # The messaging module to use, defaults to kombu.
-# rpc_backend = quantum.openstack.common.notifier.rpc.impl_kombu
+# rpc_backend = quantum.openstack.common.rpc.impl_kombu
 # Size of RPC thread pool
 # rpc_thread_pool_size = 64,
 # Size of RPC connection pool
index f494e5b28a60611246ba9c6ecd22eea576996604..239d1f92fcfbdb74221771cef5362da055d90c37 100644 (file)
@@ -1,6 +1,9 @@
 [VLANS]
-vlan_start = 1000
-vlan_end = 3000
+# (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
 
 [DATABASE]
 # This line MUST be changed to actually run the plugin.
@@ -16,8 +19,12 @@ sql_connection = sqlite://
 reconnect_interval = 2
 
 [LINUX_BRIDGE]
-# This is the interface connected to the switch on your Quantum network
-physical_interface = eth1
+# (ListOpt) Comma-separated list of
+# <physical_network>:<physical_interface> tuples mapping physical
+# network names to agent's node-specific physical network
+# interfaces. Server uses physical network names for validation but
+# ignores interfaces.
+# physical_interface_mappings = default:eth1
 
 [AGENT]
 # Agent's polling interval in seconds
@@ -25,7 +32,5 @@ polling_interval = 2
 # Change to "sudo quantum-rootwrap" to limit commands that can be run
 # as root.
 root_helper = sudo
-# Use Quantumv2 API
-# target_v2_api = False
 # Use RPC messaging to interface between agent and plugin
 # rpc = True
index 20ddd66c3380b7a2e75c060d80909ebecaf6ea32..7e115ca458297fd0f7f2190afc80a90b32631907 100644 (file)
@@ -27,6 +27,10 @@ from quantum.common import exceptions as q_exc
 LOG = logging.getLogger(__name__)
 
 
+def is_attr_set(attribute):
+    return attribute not in (None, ATTR_NOT_SPECIFIED)
+
+
 def _validate_boolean(data, valid_values=None):
     if data in [True, False]:
         return
@@ -46,6 +50,19 @@ def _validate_values(data, valid_values=None):
         return msg
 
 
+def _validate_range(data, valid_values=None):
+    min_value = valid_values[0]
+    max_value = valid_values[1]
+    if data >= min_value and data <= max_value:
+        return
+    else:
+        msg_dict = dict(data=data, min_value=min_value, max_value=max_value)
+        msg = _("%(data)s is not in range %(min_value)s through "
+                "%(max_value)s") % msg_dict
+        LOG.debug("validate_range: %s", msg)
+        return msg
+
+
 def _validate_mac_address(data, valid_values=None):
     try:
         netaddr.EUI(data)
@@ -120,6 +137,7 @@ MAC_PATTERN = "^%s[aceACE02468](:%s{2}){5}$" % (HEX_ELEM, HEX_ELEM)
 # Dictionary that maintains a list of validation functions
 validators = {'type:boolean': _validate_boolean,
               'type:values': _validate_values,
+              'type:range': _validate_range,
               'type:mac_address': _validate_mac_address,
               'type:ip_address': _validate_ip_address,
               'type:ip_address_or_none': _validate_ip_address_or_none,
index aeb994b7e957eaf6c32c9902760677d3a715e6ae..529af1bb3eabcb4000e494b40e42925e59104fac 100644 (file)
@@ -119,7 +119,8 @@ class IpAddressInUse(InUse):
 
 class VlanIdInUse(InUse):
     message = _("Unable to create the network. "
-                "The VLAN %(vlan_id)s is in use.")
+                "The VLAN %(vlan_id)s on physical network "
+                "%(physical_network)s is in use.")
 
 
 class ResourceExhausted(QuantumException):
index 1abd81b3c9c6d5f515aa902edc03187ca8436abf..85f5c42ec98503dad1b8db48972afa6d71d2b1d4 100644 (file)
@@ -755,7 +755,7 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
 
     def update_network(self, context, id, network):
         n = network['network']
-        with context.session.begin():
+        with context.session.begin(subtransactions=True):
             network = self._get_network(context, id)
             # validate 'shared' parameter
             if 'shared' in n:
@@ -764,7 +764,7 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
         return self._make_network_dict(network)
 
     def delete_network(self, context, id):
-        with context.session.begin():
+        with context.session.begin(subtransactions=True):
             network = self._get_network(context, id)
 
             filter = {'network_id': [id]}
@@ -874,7 +874,7 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
         s = subnet['subnet']
         self._validate_subnet(s)
 
-        with context.session.begin():
+        with context.session.begin(subtransactions=True):
             if "dns_nameservers" in s:
                 old_dns_list = self._get_dns_by_subnet(context, id)
 
@@ -922,7 +922,7 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
         return self._make_subnet_dict(subnet)
 
     def delete_subnet(self, context, id):
-        with context.session.begin():
+        with context.session.begin(subtransactions=True):
             subnet = self._get_subnet(context, id)
             # Check if ports are using this subnet
             allocated_qry = context.session.query(models_v2.IPAllocation)
@@ -999,7 +999,7 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
     def update_port(self, context, id, port):
         p = port['port']
 
-        with context.session.begin():
+        with context.session.begin(subtransactions=True):
             port = self._get_port(context, id)
             # Check if the IPs need to be updated
             if 'fixed_ips' in p:
@@ -1024,7 +1024,7 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
         return self._make_port_dict(port)
 
     def delete_port(self, context, id):
-        with context.session.begin():
+        with context.session.begin(subtransactions=True):
             port = self._get_port(context, id)
 
             allocated_qry = context.session.query(models_v2.IPAllocation)
index fff8a707fc0c494b329cd8740394226e32cd4d55..19b8f5b116710f38ff14d204685f851dedfe49ba 100644 (file)
@@ -17,9 +17,17 @@ from quantum.api.v2 import attributes
 
 EXTENDED_ATTRIBUTES_2_0 = {
     'networks': {
-        # TODO(rkukura): specify validation
-        'provider:vlan_id': {'allow_post': True, 'allow_put': False,
+        'provider:network_type': {'allow_post': True, 'allow_put': True,
+                                  'validate': {'type:values': ['flat',
+                                                               'vlan']},
+                                  'default': attributes.ATTR_NOT_SPECIFIED,
+                                  'is_visible': True},
+        'provider:physical_network': {'allow_post': True, 'allow_put': True,
+                                      'default': attributes.ATTR_NOT_SPECIFIED,
+                                      'is_visible': True},
+        'provider:vlan_id': {'allow_post': True, 'allow_put': True,
                              'convert_to': int,
+                             'validate': {'type:range': (1, 4095)},
                              'default': attributes.ATTR_NOT_SPECIFIED,
                              'is_visible': True},
     }
index 1b152f714e4f6958130f6787e4ccfd1060cdb4c3..6613087c49aa075389e713b1bd71ce3e8a3c39d6 100644 (file)
@@ -58,9 +58,9 @@ quantum_use_dhcp=true
 
 Make the Linux Bridge plugin the current quantum plugin
 
-- edit quantum.conf and change the core_plugin according to the API version
-V1: "core_plugin = quantum.plugins.linuxbridge.LinuxBridgePlugin.LinuxBridgePlugin"
-V2: "core_plugin = quantum.plugins.linuxbridge.lb_quantum_plugin.LinuxBridgePluginV2"
+- edit quantum.conf and change the core_plugin
+
+core_plugin = quantum.plugins.linuxbridge.lb_quantum_plugin.LinuxBridgePluginV2
 
 # -- Database config.
 
@@ -104,10 +104,26 @@ mysql> FLUSH PRIVILEGES;
   actually running the server set it to mysql. At any given time, only one
   of these should be active in the conf file (you can comment out the other).
 
-- Remember to change the interface configuration to indicate the correct
-  ethernet interface on that particular host which is being used to participate
-  in the Quantum networks. This configuration has to be applied on each host
-  on which the agent runs.
+- On the quantum server, network_vlan_ranges must be configured in
+  linuxbridge_conf.ini to specify the names of the physical networks
+  managed by the linuxbridge plugin, along with the ranges of VLAN IDs
+  available on each physical network for allocation to virtual
+  networks. An entry of the form
+  "<physical_network>:<vlan_min>:<vlan_max>" specifies a VLAN range on
+  the named physical network. An entry of the form
+  "<physical_network>" specifies a named network without making a
+  range of VLANs available for allocation. Networks specified using
+  either form are available for adminstrators to create provider flat
+  networks and provider VLANs. Multiple VLAN ranges can be specified
+  for the same physical network.
+
+  The following example linuxbridge_conf.ini entry shows three
+  physical networks that can be used to create provider networks, with
+  ranges of VLANs available for allocation on two of them:
+
+  [VLANS]
+  network_vlan_ranges = physnet1:1000:2999,physnet1:3000:3999,physnet2,physnet3:1:4094
+
 
 # -- Agent configuration
 
@@ -122,6 +138,22 @@ mysql> FLUSH PRIVILEGES;
 
   Note: debug and logging information should be updated in etc/quantum.conf
 
+- On each compute node, the network_interface_mappings must be
+  configured in linuxbridge_conf.ini to map each physical network name
+  to the physical interface connecting the node to that physical
+  network. Entries are of the form
+  "<physical_network>:<physical_interface>". For example, one compute
+  node may use the following physical_inteface_mappings entries:
+
+  [LINUX_BRIDGE]
+  physical_interface_mappings = physnet1:eth1,physnet2:eth2,physnet3:eth3
+
+  while another might use:
+
+  [LINUX_BRIDGE]
+  physical_interface_mappings = physnet1:em3,physnet2:em2,physnet3:em1
+
+
 $ Run the following:
   python linuxbridge_quantum_agent.py --config-file quantum.conf 
                                       --config-file linuxbridge_conf.ini
index 2679a6474a1961d902ad2246a3025638d63a4316..6a11227eeed3fc4279f4d6c430fcc610ef33676a 100755 (executable)
@@ -34,6 +34,7 @@ import eventlet
 import pyudev
 from sqlalchemy.ext.sqlsoup import SqlSoup
 
+from quantum.agent.linux import utils
 from quantum.agent import rpc as agent_rpc
 from quantum.common import config as logging_config
 from quantum.common import topics
@@ -42,8 +43,7 @@ from quantum.openstack.common import context
 from quantum.openstack.common import rpc
 from quantum.openstack.common.rpc import dispatcher
 from quantum.plugins.linuxbridge.common import config
-
-from quantum.agent.linux import utils
+from quantum.plugins.linuxbridge.common import constants
 
 logging.basicConfig()
 LOG = logging.getLogger(__name__)
@@ -67,9 +67,8 @@ DEFAULT_RECONNECT_INTERVAL = 2
 
 
 class LinuxBridge:
-    def __init__(self, br_name_prefix, physical_interface, root_helper):
-        self.br_name_prefix = br_name_prefix
-        self.physical_interface = physical_interface
+    def __init__(self, interface_mappings, root_helper):
+        self.interface_mappings = interface_mappings
         self.root_helper = root_helper
 
     def device_exists(self, device):
@@ -92,14 +91,14 @@ class LinuxBridge:
         if not network_id:
             LOG.warning("Invalid Network ID, will lead to incorrect bridge"
                         "name")
-        bridge_name = self.br_name_prefix + network_id[0:11]
+        bridge_name = BRIDGE_NAME_PREFIX + network_id[0:11]
         return bridge_name
 
-    def get_subinterface_name(self, vlan_id):
+    def get_subinterface_name(self, physical_interface, vlan_id):
         if not vlan_id:
             LOG.warning("Invalid VLAN ID, will lead to incorrect "
                         "subinterface name")
-        subinterface_name = '%s.%s' % (self.physical_interface, vlan_id)
+        subinterface_name = '%s.%s' % (physical_interface, vlan_id)
         return subinterface_name
 
     def get_tap_device_name(self, interface_id):
@@ -174,21 +173,27 @@ class LinuxBridge:
                 DEVICE_NAME_PLACEHOLDER, device_name)
             return os.path.exists(bridge_port_path)
 
-    def ensure_vlan_bridge(self, network_id, vlan_id):
+    def ensure_vlan_bridge(self, network_id, physical_interface, vlan_id):
         """Create a vlan and bridge unless they already exist."""
-        interface = self.ensure_vlan(vlan_id)
+        interface = self.ensure_vlan(physical_interface, vlan_id)
         bridge_name = self.get_bridge_name(network_id)
         self.ensure_bridge(bridge_name, interface)
         return interface
 
-    def ensure_vlan(self, vlan_id):
+    def ensure_flat_bridge(self, network_id, physical_interface):
+        """Create a non-vlan bridge unless it already exists."""
+        bridge_name = self.get_bridge_name(network_id)
+        self.ensure_bridge(bridge_name, physical_interface)
+        return physical_interface
+
+    def ensure_vlan(self, physical_interface, vlan_id):
         """Create a vlan unless it already exists."""
-        interface = self.get_subinterface_name(vlan_id)
+        interface = self.get_subinterface_name(physical_interface, vlan_id)
         if not self.device_exists(interface):
             LOG.debug("Creating subinterface %s for VLAN %s on interface %s" %
-                      (interface, vlan_id, self.physical_interface))
+                      (interface, vlan_id, physical_interface))
             if utils.execute(['ip', 'link', 'add', 'link',
-                              self.physical_interface,
+                              physical_interface,
                               'name', interface, 'type', 'vlan', 'id',
                               vlan_id], root_helper=self.root_helper):
                 return
@@ -225,7 +230,8 @@ class LinuxBridge:
             utils.execute(['brctl', 'addif', bridge_name, interface],
                           root_helper=self.root_helper)
 
-    def add_tap_interface(self, network_id, vlan_id, tap_device_name):
+    def add_tap_interface(self, network_id, physical_interface, vlan_id,
+                          tap_device_name):
         """
         If a VIF has been plugged into a network, this function will
         add the corresponding tap device to the relevant bridge
@@ -249,7 +255,10 @@ class LinuxBridge:
                               tap_device_name], root_helper=self.root_helper):
                 return False
 
-        self.ensure_vlan_bridge(network_id, vlan_id)
+        if int(vlan_id) == constants.FLAT_VLAN_ID:
+            self.ensure_flat_bridge(network_id, physical_interface)
+        else:
+            self.ensure_vlan_bridge(network_id, physical_interface, vlan_id)
         if utils.execute(['brctl', 'addif', bridge_name, tap_device_name],
                          root_helper=self.root_helper):
             return False
@@ -257,26 +266,38 @@ class LinuxBridge:
                                                           bridge_name))
         return True
 
-    def add_interface(self, network_id, vlan_id, interface_id):
+    def add_interface(self, network_id, physical_network, vlan_id,
+                      interface_id):
         if not interface_id:
             """
             Since the VIF id is null, no VIF is plugged into this port
             no more processing is required
             """
             return False
+
+        physical_interface = self.interface_mappings.get(physical_network)
+        if not physical_interface:
+            LOG.error("No mapping for physical network %s" % physical_network)
+            return False
+
         if interface_id.startswith(GATEWAY_INTERFACE_PREFIX):
-            return self.add_tap_interface(network_id, vlan_id, interface_id)
+            return self.add_tap_interface(network_id,
+                                          physical_interface, vlan_id,
+                                          interface_id)
         else:
             tap_device_name = self.get_tap_device_name(interface_id)
-            return self.add_tap_interface(network_id, vlan_id, tap_device_name)
+            return self.add_tap_interface(network_id,
+                                          physical_interface, vlan_id,
+                                          tap_device_name)
 
     def delete_vlan_bridge(self, bridge_name):
         if self.device_exists(bridge_name):
             interfaces_on_bridge = self.get_interfaces_on_bridge(bridge_name)
             for interface in interfaces_on_bridge:
                 self.remove_interface(bridge_name, interface)
-                if interface.startswith(self.physical_interface):
-                    self.delete_vlan(interface)
+                for physical_interface in self.interface_mappings.itervalues():
+                    if interface.startswith(physical_interface):
+                        self.delete_vlan(interface)
 
             LOG.debug("Deleting bridge %s" % bridge_name)
             if utils.execute(['ip', 'link', 'set', bridge_name, 'down'],
@@ -360,23 +381,23 @@ class LinuxBridgeRpcCallbacks():
 
 class LinuxBridgeQuantumAgentDB:
 
-    def __init__(self, br_name_prefix, physical_interface, polling_interval,
-                 reconnect_interval, root_helper, target_v2_api,
-                 db_connection_url):
+    def __init__(self, interface_mappings, polling_interval,
+                 reconnect_interval, root_helper, db_connection_url):
         self.polling_interval = polling_interval
         self.root_helper = root_helper
-        self.setup_linux_bridge(br_name_prefix, physical_interface)
-        self.target_v2_api = target_v2_api
+        self.setup_linux_bridge(interface_mappings)
         self.reconnect_interval = reconnect_interval
         self.db_connected = False
         self.db_connection_url = db_connection_url
 
-    def setup_linux_bridge(self, br_name_prefix, physical_interface):
-        self.linux_br = LinuxBridge(br_name_prefix, physical_interface,
-                                    self.root_helper)
+    def setup_linux_bridge(self, interface_mappings):
+        self.linux_br = LinuxBridge(interface_mappings, self.root_helper)
 
-    def process_port_binding(self, network_id, interface_id, vlan_id):
-        return self.linux_br.add_interface(network_id, vlan_id, interface_id)
+    def process_port_binding(self, network_id, interface_id,
+                             physical_network, vlan_id):
+        return self.linux_br.add_interface(network_id,
+                                           physical_network, vlan_id,
+                                           interface_id)
 
     def remove_port_binding(self, network_id, interface_id):
         bridge_name = self.linux_br.get_bridge_name(network_id)
@@ -437,16 +458,18 @@ class LinuxBridgeQuantumAgentDB:
                                 old_port_bindings):
         vlan_bindings = {}
         try:
-            vlan_binds = db.vlan_bindings.all()
+            network_binds = db.network_bindings.all()
         except Exception as e:
-            LOG.info("Unable to get vlan bindings! Exception: %s" % e)
+            LOG.info("Unable to get network bindings! Exception: %s" % e)
             self.db_connected = False
             return {VLAN_BINDINGS: {},
                     PORT_BINDINGS: []}
 
         vlans_string = ""
-        for bind in vlan_binds:
-            entry = {'network_id': bind.network_id, 'vlan_id': bind.vlan_id}
+        for bind in network_binds:
+            entry = {'network_id': bind.network_id,
+                     'physical_network': bind.physical_network,
+                     'vlan_id': bind.vlan_id}
             vlan_bindings[bind.network_id] = entry
             vlans_string = "%s %s" % (vlans_string, entry)
 
@@ -462,19 +485,12 @@ class LinuxBridgeQuantumAgentDB:
         all_bindings = {}
         for bind in port_binds:
             append_entry = False
-            if self.target_v2_api:
-                all_bindings[bind.id] = bind
-                entry = {'network_id': bind.network_id,
-                         'uuid': bind.id,
-                         'status': bind.status,
-                         'interface_id': bind.id}
-                append_entry = bind.admin_state_up
-            else:
-                all_bindings[bind.uuid] = bind
-                entry = {'network_id': bind.network_id, 'state': bind.state,
-                         'op_status': bind.op_status, 'uuid': bind.uuid,
-                         'interface_id': bind.interface_id}
-                append_entry = bind.state == 'ACTIVE'
+            all_bindings[bind.id] = bind
+            entry = {'network_id': bind.network_id,
+                     'uuid': bind.id,
+                     'status': bind.status,
+                     'interface_id': bind.id}
+            append_entry = bind.admin_state_up
             if append_entry:
                 port_bindings.append(entry)
 
@@ -484,15 +500,15 @@ class LinuxBridgeQuantumAgentDB:
             ports_string = "%s %s" % (ports_string, pb)
             port_id = pb['uuid']
             interface_id = pb['interface_id']
+            network_id = pb['network_id']
 
-            vlan_id = str(vlan_bindings[pb['network_id']]['vlan_id'])
-            if self.process_port_binding(pb['network_id'],
+            physical_network = vlan_bindings[network_id]['physical_network']
+            vlan_id = str(vlan_bindings[network_id]['vlan_id'])
+            if self.process_port_binding(network_id,
                                          interface_id,
+                                         physical_network,
                                          vlan_id):
-                if self.target_v2_api:
-                    all_bindings[port_id].status = OP_STATUS_UP
-                else:
-                    all_bindings[port_id].op_status = OP_STATUS_UP
+                all_bindings[port_id].status = OP_STATUS_UP
 
             plugged_interfaces.append(interface_id)
 
@@ -539,15 +555,16 @@ class LinuxBridgeQuantumAgentDB:
 
 class LinuxBridgeQuantumAgentRPC:
 
-    def __init__(self, br_name_prefix, physical_interface, polling_interval,
+    def __init__(self, interface_mappings, polling_interval,
                  root_helper):
         self.polling_interval = polling_interval
         self.root_helper = root_helper
-        self.setup_linux_bridge(br_name_prefix, physical_interface)
-        self.setup_rpc(physical_interface)
+        self.setup_linux_bridge(interface_mappings)
+        self.setup_rpc(interface_mappings.values())
 
-    def setup_rpc(self, physical_interface):
-        mac = utils.get_interface_mac(physical_interface)
+    def setup_rpc(self, physical_interfaces):
+        # REVISIT try until one succeeds?
+        mac = utils.get_interface_mac(physical_interfaces[0])
         self.agent_id = '%s%s' % ('lb', (mac.replace(":", "")))
         self.topic = topics.AGENT
         self.plugin_rpc = agent_rpc.PluginApi(topics.PLUGIN)
@@ -569,12 +586,14 @@ class LinuxBridgeQuantumAgentRPC:
         monitor = pyudev.Monitor.from_netlink(self.udev)
         monitor.filter_by('net')
 
-    def setup_linux_bridge(self, br_name_prefix, physical_interface):
-        self.linux_br = LinuxBridge(br_name_prefix, physical_interface,
-                                    self.root_helper)
+    def setup_linux_bridge(self, interface_mappings):
+        self.linux_br = LinuxBridge(interface_mappings, self.root_helper)
 
-    def process_port_binding(self, network_id, interface_id, vlan_id):
-        return self.linux_br.add_interface(network_id, vlan_id, interface_id)
+    def process_port_binding(self, network_id, interface_id,
+                             physical_network, vlan_id):
+        return self.linux_br.add_interface(network_id,
+                                           physical_network, vlan_id,
+                                           interface_id)
 
     def remove_port_binding(self, network_id, interface_id):
         bridge_name = self.linux_br.get_bridge_name(network_id)
@@ -633,6 +652,7 @@ class LinuxBridgeQuantumAgentRPC:
                     # create the networking for the port
                     self.process_port_binding(details['network_id'],
                                               details['port_id'],
+                                              details['physical_network'],
                                               details['vlan_id'])
                 else:
                     self.remove_port_binding(details['network_id'],
@@ -696,29 +716,32 @@ def main():
     # (TODO) gary - swap with common logging
     logging_config.setup_logging(cfg.CONF)
 
-    br_name_prefix = BRIDGE_NAME_PREFIX
-    physical_interface = cfg.CONF.LINUX_BRIDGE.physical_interface
+    interface_mappings = {}
+    for mapping in cfg.CONF.LINUX_BRIDGE.physical_interface_mappings:
+        try:
+            physical_network, physical_interface = mapping.split(':')
+            interface_mappings[physical_network] = physical_interface
+            LOG.debug("physical network %s mapped to physical interface %s" %
+                      (physical_network, physical_interface))
+        except ValueError as ex:
+            LOG.error("Invalid physical interface mapping: \'%s\' - %s" %
+                      (mapping, ex))
+            sys.exit(1)
+
     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
-    if not cfg.CONF.AGENT.target_v2_api:
-        rpc = False
-
     if rpc:
-        plugin = LinuxBridgeQuantumAgentRPC(br_name_prefix,
-                                            physical_interface,
+        plugin = LinuxBridgeQuantumAgentRPC(interface_mappings,
                                             polling_interval,
                                             root_helper)
     else:
         db_connection_url = cfg.CONF.DATABASE.sql_connection
-        target_v2_api = cfg.CONF.AGENT.target_v2_api
-        plugin = LinuxBridgeQuantumAgentDB(br_name_prefix,
-                                           physical_interface,
+        plugin = LinuxBridgeQuantumAgentDB(interface_mappings,
                                            polling_interval,
                                            reconnect_interval,
                                            root_helper,
-                                           target_v2_api,
                                            db_connection_url)
     LOG.info("Agent initialized successfully, now running... ")
     plugin.daemon_loop()
index cafe455f245749a9cc69a0078faff31fc423beba..a73fdca1b152314b4b301eb783da51bef24bcc3e 100644 (file)
 
 from quantum.openstack.common import cfg
 
+DEFAULT_VLAN_RANGES = ['default:1000:2999']
+DEFAULT_INTERFACE_MAPPINGS = ['default:eth1']
+
 
 vlan_opts = [
-    cfg.IntOpt('vlan_start', default=1000),
-    cfg.IntOpt('vlan_end', default=3000),
+    cfg.ListOpt('network_vlan_ranges',
+                default=DEFAULT_VLAN_RANGES,
+                help="List of <physical_network>:<vlan_min>:<vlan_max> "
+                "or <physical_network>"),
 ]
 
 database_opts = [
@@ -32,13 +37,14 @@ database_opts = [
 ]
 
 bridge_opts = [
-    cfg.StrOpt('physical_interface', default='eth1'),
+    cfg.ListOpt('physical_interface_mappings',
+                default=DEFAULT_INTERFACE_MAPPINGS,
+                help="List of <physical_network>:<physical_interface>"),
 ]
 
 agent_opts = [
     cfg.IntOpt('polling_interval', default=2),
     cfg.StrOpt('root_helper', default='sudo'),
-    cfg.BoolOpt('target_v2_api', default=False),
     cfg.BoolOpt('rpc', default=True),
 ]
 
index 7e67247eef29f8df8d421e2875b6882732bbe85b..87f908898e7554d9616e0d287f3425d1e8f60041 100644 (file)
 # @author: Sumit Naiksatam, Cisco Systems, Inc.
 
 
-PORT_STATE = 'port-state'
+FLAT_VLAN_ID = -1
+
 PORT_UP = "ACTIVE"
 PORT_DOWN = "DOWN"
 
-UUID = 'uuid'
-TENANTID = 'tenant_id'
-NETWORKID = 'network_id'
-NETWORKNAME = 'name'
-NETWORKPORTS = 'ports'
-OPSTATUS = 'op_status'
-INTERFACEID = 'interface_id'
-PORTSTATE = 'state'
-PORTID = 'port_id'
-PPNAME = 'name'
-PPVLANID = 'vlan_id'
 VLANID = 'vlan_id'
-VLANNAME = 'vlan_name'
-
-ATTACHMENT = 'attachment'
 PORT_ID = 'port-id'
-PORT_OP_STATUS = 'port-op-status'
-
 NET_ID = 'net-id'
-NET_NAME = 'net-name'
-NET_PORTS = 'net-ports'
-NET_OP_STATUS = 'net-op-status'
-NET_VLAN_NAME = 'net-vlan-name'
-NET_VLAN_ID = 'net-vlan-id'
-NET_TENANTS = 'net-tenants'
-
-USERNAME = 'username'
-PASSWORD = 'password'
-
-DELIMITERS = "[,;:\b\s]"
-
-UUID_LENGTH = 36
-
-UNPLUGGED = '(detached)'
-
-ASSOCIATION_STATUS = 'association_status'
-
-ATTACHED = 'attached'
-
-DETACHED = 'detached'
diff --git a/quantum/plugins/linuxbridge/common/exceptions.py b/quantum/plugins/linuxbridge/common/exceptions.py
deleted file mode 100644 (file)
index 897db0a..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-#
-# Copyright 2012 Cisco Systems, Inc.  All rights reserved.
-#
-#    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.
-#
-# @author: Sumit Naiksatam, Cisco Systems, Inc.
-# @author: Rohit Agarwalla, Cisco Systems, Inc.
-
-"""
-Exceptions used by the LinuxBridge plugin
-"""
-
-from quantum.common import exceptions
-
-
-class NetworksLimit(exceptions.QuantumException):
-    """Total number of network objects limit has been hit"""
-    message = _("Unable to create new network. Number of networks"
-                "for the system has exceeded the limit")
-
-
-class NetworkVlanBindingAlreadyExists(exceptions.QuantumException):
-    """Binding cannot be created, since it already exists"""
-    message = _("NetworkVlanBinding for %(vlan_id)s and network "
-                "%(network_id)s already exists")
-
-
-class NetworkVlanBindingNotFound(exceptions.QuantumException):
-    """Binding could not be found"""
-    message = _("NetworkVlanBinding for network "
-                "%(network_id)s does not exist")
-
-
-class VlanIDNotFound(exceptions.QuantumException):
-    """VLAN ID cannot be found"""
-    message = _("Vlan ID %(vlan_id)s not found")
-
-
-class VlanIDNotAvailable(exceptions.QuantumException):
-    """No VLAN ID available"""
-    message = _("No Vlan ID available")
-
-
-class UnableToChangeVlanRange(exceptions.QuantumException):
-    """No VLAN ID available"""
-    message = _("Current VLAN ID range %(range_start)s to %(range_end)s "
-                "cannot be changed. Please check plugin conf file.")
diff --git a/quantum/plugins/linuxbridge/db/l2network_db.py b/quantum/plugins/linuxbridge/db/l2network_db.py
deleted file mode 100644 (file)
index 498fdcb..0000000
+++ /dev/null
@@ -1,311 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2012, Cisco Systems, Inc.
-#
-#    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.
-# @author: Rohit Agarwalla, Cisco Systems, Inc.
-
-import logging
-
-from sqlalchemy import func
-from sqlalchemy.orm import exc
-
-from quantum.api import api_common
-from quantum.common import exceptions as q_exc
-import quantum.db.api as db
-from quantum.db import models_v2
-from quantum.openstack.common import cfg
-from quantum.plugins.linuxbridge.common import config
-from quantum.plugins.linuxbridge.common import exceptions as c_exc
-from quantum.plugins.linuxbridge.db import l2network_models_v2
-
-LOG = logging.getLogger(__name__)
-
-# The global variable for the database version model
-L2_MODEL = l2network_models_v2
-
-
-def initialize(base=None):
-    global L2_MODEL
-    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})
-    if base:
-        options.update({"base": base})
-    db.configure_db(options)
-    create_vlanids()
-
-
-def create_vlanids():
-    """Prepopulate the vlan_bindings table"""
-    LOG.debug("create_vlanids() called")
-    session = db.get_session()
-    start = cfg.CONF.VLANS.vlan_start
-    end = cfg.CONF.VLANS.vlan_end
-    try:
-        vlanid = session.query(L2_MODEL.VlanID).one()
-    except exc.MultipleResultsFound:
-        """
-        TODO (Sumit): Salvatore rightly points out that this will not handle
-        change in VLAN ID range across server reboots. This is currently not
-        a supported feature. This logic will need to change if this feature
-        has to be supported.
-        Per Dan's suggestion we just throw a server exception for now.
-        """
-        current_start = (
-            int(session.query(func.min(L2_MODEL.VlanID.vlan_id)).
-                one()[0]))
-        current_end = (
-            int(session.query(func.max(L2_MODEL.VlanID.vlan_id)).
-                one()[0]))
-        if current_start != start or current_end != end:
-            LOG.debug("Old VLAN range %s-%s" % (current_start, current_end))
-            LOG.debug("New VLAN range %s-%s" % (start, end))
-            raise c_exc.UnableToChangeVlanRange(range_start=current_start,
-                                                range_end=current_end)
-    except exc.NoResultFound:
-        LOG.debug("Setting VLAN range to %s-%s" % (start, end))
-        while start <= end:
-            vlanid = L2_MODEL.VlanID(start)
-            session.add(vlanid)
-            start += 1
-        session.flush()
-    return
-
-
-def get_all_vlanids():
-    """Get all the vlanids"""
-    LOG.debug("get_all_vlanids() called")
-    session = db.get_session()
-    try:
-        vlanids = (session.query(L2_MODEL.VlanID).
-                   all())
-        return vlanids
-    except exc.NoResultFound:
-        return []
-
-
-def is_vlanid_used(vlan_id):
-    """Check if a vlanid is in use"""
-    LOG.debug("is_vlanid_used() called")
-    session = db.get_session()
-    try:
-        vlanid = (session.query(L2_MODEL.VlanID).
-                  filter_by(vlan_id=vlan_id).
-                  one())
-        return vlanid["vlan_used"]
-    except exc.NoResultFound:
-        raise c_exc.VlanIDNotFound(vlan_id=vlan_id)
-
-
-def release_vlanid(vlan_id):
-    """Set the vlanid state to be unused, and delete if not in range"""
-    LOG.debug("release_vlanid() called")
-    session = db.get_session()
-    try:
-        vlanid = (session.query(L2_MODEL.VlanID).
-                  filter_by(vlan_id=vlan_id).
-                  one())
-        vlanid["vlan_used"] = False
-        if (vlan_id >= cfg.CONF.VLANS.vlan_start and
-            vlan_id <= cfg.CONF.VLANS.vlan_end):
-            session.merge(vlanid)
-        else:
-            session.delete(vlanid)
-        session.flush()
-        return vlanid["vlan_used"]
-    except exc.NoResultFound:
-        raise c_exc.VlanIDNotFound(vlan_id=vlan_id)
-    return
-
-
-def delete_vlanid(vlan_id):
-    """Delete a vlanid entry from db"""
-    LOG.debug("delete_vlanid() called")
-    session = db.get_session()
-    try:
-        vlanid = (session.query(L2_MODEL.VlanID).
-                  filter_by(vlan_id=vlan_id).
-                  one())
-        session.delete(vlanid)
-        session.flush()
-        return vlanid
-    except exc.NoResultFound:
-        raise c_exc.VlanIDNotFound(vlan_id=vlan_id)
-
-
-def reserve_vlanid():
-    """Reserve the first unused vlanid"""
-    LOG.debug("reserve_vlanid() called")
-    session = db.get_session()
-    try:
-        rvlan = (session.query(L2_MODEL.VlanID).
-                 first())
-        if not rvlan:
-            create_vlanids()
-
-        rvlan = (session.query(L2_MODEL.VlanID).
-                 filter_by(vlan_used=False).
-                 first())
-        if not rvlan:
-            raise c_exc.VlanIDNotAvailable()
-
-        rvlanid = (session.query(L2_MODEL.VlanID).
-                   filter_by(vlan_id=rvlan["vlan_id"]).
-                   one())
-        rvlanid["vlan_used"] = True
-        session.merge(rvlanid)
-        session.flush()
-        return rvlan["vlan_id"]
-    except exc.NoResultFound:
-        raise c_exc.VlanIDNotAvailable()
-
-
-def reserve_specific_vlanid(vlan_id):
-    """Reserve a specific vlanid"""
-    LOG.debug("reserve_specific_vlanid() called")
-    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)
-    session = db.get_session()
-    try:
-        rvlanid = (session.query(l2network_models_v2.VlanID).
-                   filter_by(vlan_id=vlan_id).
-                   one())
-        if rvlanid["vlan_used"]:
-            raise q_exc.VlanIdInUse(vlan_id=vlan_id)
-        LOG.debug("reserving dynamic vlanid %s" % vlan_id)
-        rvlanid["vlan_used"] = True
-        session.merge(rvlanid)
-    except exc.NoResultFound:
-        rvlanid = l2network_models_v2.VlanID(vlan_id)
-        LOG.debug("reserving non-dynamic vlanid %s" % vlan_id)
-        rvlanid["vlan_used"] = True
-        session.add(rvlanid)
-    session.flush()
-
-
-def get_all_vlanids_used():
-    """Get all the vlanids used"""
-    LOG.debug("get_all_vlanids() called")
-    session = db.get_session()
-    try:
-        vlanids = (session.query(L2_MODEL.VlanID).
-                   filter_by(vlan_used=True).
-                   all())
-        return vlanids
-    except exc.NoResultFound:
-        return []
-
-
-def get_all_vlan_bindings():
-    """List all the vlan to network associations"""
-    LOG.debug("get_all_vlan_bindings() called")
-    session = db.get_session()
-    try:
-        bindings = (session.query(L2_MODEL.VlanBinding).
-                    all())
-        return bindings
-    except exc.NoResultFound:
-        return []
-
-
-def get_vlan_binding(netid):
-    """List the vlan given a network_id"""
-    LOG.debug("get_vlan_binding() called")
-    session = db.get_session()
-    try:
-        binding = (session.query(L2_MODEL.VlanBinding).
-                   filter_by(network_id=netid).
-                   one())
-        return binding
-    except exc.NoResultFound:
-        raise c_exc.NetworkVlanBindingNotFound(network_id=netid)
-
-
-def add_vlan_binding(vlanid, netid):
-    """Add a vlan to network association"""
-    LOG.debug("add_vlan_binding() called")
-    session = db.get_session()
-    try:
-        binding = (session.query(L2_MODEL.VlanBinding).
-                   filter_by(vlan_id=vlanid).
-                   one())
-        raise c_exc.NetworkVlanBindingAlreadyExists(vlan_id=vlanid,
-                                                    network_id=netid)
-    except exc.NoResultFound:
-        binding = L2_MODEL.VlanBinding(vlanid, netid)
-        session.add(binding)
-        session.flush()
-        return binding
-
-
-def remove_vlan_binding(netid):
-    """Remove a vlan to network association"""
-    LOG.debug("remove_vlan_binding() called")
-    session = db.get_session()
-    try:
-        binding = (session.query(L2_MODEL.VlanBinding).
-                   filter_by(network_id=netid).
-                   one())
-        session.delete(binding)
-        session.flush()
-        return binding
-    except exc.NoResultFound:
-        pass
-
-
-def update_vlan_binding(netid, newvlanid=None):
-    """Update a vlan to network association"""
-    LOG.debug("update_vlan_binding() called")
-    session = db.get_session()
-    try:
-        binding = (session.query(L2_MODEL.VlanBinding).
-                   filter_by(network_id=netid).
-                   one())
-        if newvlanid:
-            binding["vlan_id"] = newvlanid
-        session.merge(binding)
-        session.flush()
-        return binding
-    except exc.NoResultFound:
-        raise q_exc.NetworkNotFound(net_id=netid)
-
-
-def get_port_from_device(device):
-    """Get port from database"""
-    LOG.debug("get_port_from_device() called")
-    session = db.get_session()
-    ports = session.query(models_v2.Port).all()
-    if not ports:
-        return
-    for port in ports:
-        if port['id'].startswith(device):
-            return port
-    return
-
-
-def set_port_status(port_id, status):
-    """Set the port status"""
-    LOG.debug("set_port_status as %s called", status)
-    session = db.get_session()
-    try:
-        port = session.query(models_v2.Port).filter_by(id=port_id).one()
-        port['status'] = status
-        if status == api_common.PORT_STATUS_DOWN:
-            port['device_id'] = ''
-        session.merge(port)
-        session.flush()
-    except exc.NoResultFound:
-        raise q_exc.PortNotFound(port_id=port_id)
diff --git a/quantum/plugins/linuxbridge/db/l2network_db_v2.py b/quantum/plugins/linuxbridge/db/l2network_db_v2.py
new file mode 100644 (file)
index 0000000..35bfd0c
--- /dev/null
@@ -0,0 +1,196 @@
+# 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.
+
+import logging
+
+from sqlalchemy.orm import exc
+
+from quantum.api import api_common
+from quantum.common import exceptions as q_exc
+import quantum.db.api as db
+from quantum.db import models_v2
+from quantum.openstack.common import cfg
+from quantum.plugins.linuxbridge.common import config
+from quantum.plugins.linuxbridge.db import l2network_models_v2
+
+LOG = logging.getLogger(__name__)
+
+
+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 sync_network_states(network_vlan_ranges):
+    """Synchronize network_states table with current configured VLAN ranges."""
+
+    # 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))
+
+        session = db.get_session()
+        with session.begin():
+            # remove from table unallocated vlans not currently allocatable
+            try:
+                states = (session.query(l2network_models_v2.NetworkState).
+                          filter_by(physical_network=physical_network).
+                          all())
+                for state in states:
+                    try:
+                        # see if vlan is allocatable
+                        vlan_ids.remove(state.vlan_id)
+                    except KeyError:
+                        # it's not allocatable, so check if its allocated
+                        if not state.allocated:
+                            # it's not, so remove it from table
+                            LOG.debug("removing vlan %s on physical network "
+                                      "%s from pool" %
+                                      (state.vlan_id, physical_network))
+                            session.delete(state)
+            except exc.NoResultFound:
+                pass
+
+            # add missing allocatable vlans to table
+            for vlan_id in sorted(vlan_ids):
+                state = l2network_models_v2.NetworkState(physical_network,
+                                                         vlan_id)
+                session.add(state)
+
+
+def get_network_state(physical_network, vlan_id):
+    """Get state of specified network"""
+
+    session = db.get_session()
+    try:
+        state = (session.query(l2network_models_v2.NetworkState).
+                 filter_by(physical_network=physical_network,
+                           vlan_id=vlan_id).
+                 one())
+        return state
+    except exc.NoResultFound:
+        return None
+
+
+def reserve_network(session):
+    with session.begin(subtransactions=True):
+        state = (session.query(l2network_models_v2.NetworkState).
+                 filter_by(allocated=False).
+                 first())
+        if not state:
+            raise q_exc.NoNetworkAvailable()
+        LOG.debug("reserving vlan %s on physical network %s from pool" %
+                  (state.vlan_id, state.physical_network))
+        state.allocated = True
+    return (state.physical_network, state.vlan_id)
+
+
+def reserve_specific_network(session, physical_network, vlan_id):
+    with session.begin(subtransactions=True):
+        try:
+            state = (session.query(l2network_models_v2.NetworkState).
+                     filter_by(physical_network=physical_network,
+                               vlan_id=vlan_id).
+                     one())
+            if state.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))
+            state.allocated = True
+        except exc.NoResultFound:
+            LOG.debug("reserving specific vlan %s on physical network %s "
+                      "outside pool" % (vlan_id, physical_network))
+            state = l2network_models_v2.NetworkState(physical_network, vlan_id)
+            state.allocated = True
+            session.add(state)
+
+
+def release_network(session, physical_network, vlan_id, network_vlan_ranges):
+    with session.begin(subtransactions=True):
+        try:
+            state = (session.query(l2network_models_v2.NetworkState).
+                     filter_by(physical_network=physical_network,
+                               vlan_id=vlan_id).
+                     one())
+            state.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 inside:
+                LOG.debug("releasing vlan %s on physical network %s to pool" %
+                          (vlan_id, physical_network))
+            else:
+                LOG.debug("releasing vlan %s on physical network %s outside "
+                          "pool" % (vlan_id, physical_network))
+                session.delete(state)
+        except exc.NoResultFound:
+            LOG.warning("vlan_id %s on physical network %s not found" %
+                        (vlan_id, physical_network))
+
+
+def add_network_binding(session, network_id, physical_network, vlan_id):
+    with session.begin(subtransactions=True):
+        binding = l2network_models_v2.NetworkBinding(network_id,
+                                                     physical_network, vlan_id)
+        session.add(binding)
+
+
+def get_network_binding(session, network_id):
+    try:
+        binding = (session.query(l2network_models_v2.NetworkBinding).
+                   filter_by(network_id=network_id).
+                   one())
+        return binding
+    except exc.NoResultFound:
+        return
+
+
+def get_port_from_device(device):
+    """Get port from database"""
+    LOG.debug("get_port_from_device() called")
+    session = db.get_session()
+    ports = session.query(models_v2.Port).all()
+    if not ports:
+        return
+    for port in ports:
+        if port['id'].startswith(device):
+            return port
+    return
+
+
+def set_port_status(port_id, status):
+    """Set the port status"""
+    LOG.debug("set_port_status as %s called", status)
+    session = db.get_session()
+    try:
+        port = session.query(models_v2.Port).filter_by(id=port_id).one()
+        port['status'] = status
+        if status == api_common.PORT_STATUS_DOWN:
+            port['device_id'] = ''
+        session.merge(port)
+        session.flush()
+    except exc.NoResultFound:
+        raise q_exc.PortNotFound(port_id=port_id)
index 925be3157c76b29f828da89d90a75fccfbd2f9f8..ca840a285fc4c0c73db815008e3e2755819ac783 100644 (file)
 # limitations under the License.
 
 import sqlalchemy as sa
-from sqlalchemy import orm
 
 from quantum.db import model_base
 
 
-class VlanID(model_base.BASEV2):
-    """Represents a vlan_id usage"""
-    __tablename__ = 'vlan_ids'
+class NetworkState(model_base.BASEV2):
+    """Represents state of vlan_id on physical network"""
+    __tablename__ = 'network_states'
 
-    vlan_id = sa.Column(sa.Integer, nullable=False, primary_key=True)
-    vlan_used = sa.Column(sa.Boolean, nullable=False)
+    physical_network = sa.Column(sa.String(64), nullable=False,
+                                 primary_key=True)
+    vlan_id = sa.Column(sa.Integer, nullable=False, primary_key=True,
+                        autoincrement=False)
+    allocated = sa.Column(sa.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 "<NetworkState(%s,%d,%s)>" % (self.physical_network,
+                                             self.vlan_id, self.allocated)
 
 
-class VlanBinding(model_base.BASEV2):
-    """Represents a binding of vlan_id to network_id"""
-    __tablename__ = 'vlan_bindings'
+class NetworkBinding(model_base.BASEV2):
+    """Represents binding of virtual network to physical_network and vlan_id"""
+    __tablename__ = 'network_bindings'
 
-    network_id = sa.Column(sa.String(36), sa.ForeignKey('networks.id',
-                                                        ondelete="CASCADE"),
+    network_id = sa.Column(sa.String(36),
+                           sa.ForeignKey('networks.id', ondelete="CASCADE"),
                            primary_key=True)
+    physical_network = sa.Column(sa.String(64), nullable=False)
     vlan_id = sa.Column(sa.Integer, nullable=False)
 
-    def __init__(self, vlan_id, network_id):
-        self.vlan_id = vlan_id
+    def __init__(self, network_id, physical_network, vlan_id):
         self.network_id = network_id
+        self.physical_network = physical_network
+        self.vlan_id = vlan_id
 
     def __repr__(self):
-        return "<VlanBinding(%d,%s)>" % (self.vlan_id, self.network_id)
+        return "<NetworkBinding(%s,%s,%d)>" % (self.network_id,
+                                               self.physical_network,
+                                               self.vlan_id)
index 80f88b9d489d0119574de7ef0052d811f62e0fe2..534a1e1c0a87abb7791281ae502733fb020ca2e8 100644 (file)
 # limitations under the License.
 
 import logging
+import sys
 
 from quantum.api import api_common
 from quantum.api.v2 import attributes
+from quantum.common import exceptions as q_exc
 from quantum.common import topics
+from quantum.db import api as db_api
 from quantum.db import db_base_plugin_v2
 from quantum.db import models_v2
 from quantum.openstack.common import context
@@ -25,7 +28,8 @@ 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.linuxbridge.db import l2network_db as cdb
+from quantum.plugins.linuxbridge.common import constants
+from quantum.plugins.linuxbridge.db import l2network_db_v2 as db
 from quantum import policy
 
 
@@ -39,8 +43,8 @@ class LinuxBridgeRpcCallbacks():
     # Device names start with "tap"
     TAP_PREFIX_LEN = 3
 
-    def __init__(self, context):
-        self.context = context
+    def __init__(self, rpc_context):
+        self.rpc_context = rpc_context
 
     def create_rpc_dispatcher(self):
         '''Get the rpc dispatcher for this manager.
@@ -50,38 +54,40 @@ class LinuxBridgeRpcCallbacks():
         '''
         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 = cdb.get_port_from_device(device[self.TAP_PREFIX_LEN:])
+        port = db.get_port_from_device(device[self.TAP_PREFIX_LEN:])
         if port:
-            vlan_binding = cdb.get_vlan_binding(port['network_id'])
+            binding = db.get_network_binding(db_api.get_session(),
+                                             port['network_id'])
             entry = {'device': device,
-                     'vlan_id': vlan_binding['vlan_id'],
+                     'physical_network': binding.physical_network,
+                     'vlan_id': binding.vlan_id,
                      'network_id': port['network_id'],
                      'port_id': port['id'],
                      'admin_state_up': port['admin_state_up']}
             # Set the port status to UP
-            cdb.set_port_status(port['id'], api_common.PORT_STATUS_UP)
+            db.set_port_status(port['id'], api_common.PORT_STATUS_UP)
         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')
         device = kwargs.get('device')
         LOG.debug("Device %s no longer exists on %s", device, agent_id)
-        port = cdb.get_port_from_device(device[self.TAP_PREFIX_LEN:])
+        port = db.get_port_from_device(device[self.TAP_PREFIX_LEN:])
         if port:
             entry = {'device': device,
                      'exists': True}
             # Set port status to DOWN
-            cdb.set_port_status(port['id'], api_common.PORT_STATUS_UP)
+            db.set_port_status(port['id'], api_common.PORT_STATUS_DOWN)
         else:
             entry = {'device': device,
                      'exists': False}
@@ -115,10 +121,11 @@ class AgentNotifierApi(proxy.RpcProxy):
                                        network_id=network_id),
                          topic=self.topic_network_delete)
 
-    def port_update(self, context, port, vlan_id):
+    def port_update(self, context, port, physical_network, vlan_id):
         self.fanout_cast(context,
                          self.make_msg('port_update',
                                        port=port,
+                                       physical_network=physical_network,
                                        vlan_id=vlan_id),
                          topic=self.topic_port_update)
 
@@ -137,24 +144,29 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2):
     be updated to take advantage of it.
     """
 
+    # This attribute specifies whether the plugin supports or not
+    # bulk operations. Name mangling is used in order to ensure it
+    # is qualified by class
+    __native_bulk_support = True
+
     supported_extension_aliases = ["provider"]
 
     def __init__(self):
-        cdb.initialize(base=models_v2.model_base.BASEV2)
+        db.initialize()
+        self._parse_network_vlan_ranges()
+        db.sync_network_states(self.network_vlan_ranges)
         self.rpc = cfg.CONF.AGENT.rpc
-        if cfg.CONF.AGENT.rpc and cfg.CONF.AGENT.target_v2_api:
-            self.setup_rpc()
-        if not cfg.CONF.AGENT.target_v2_api:
-            self.rpc = False
+        if self.rpc:
+            self._setup_rpc()
         LOG.debug("Linux Bridge Plugin initialization complete")
 
-    def setup_rpc(self):
+    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.callbacks = LinuxBridgeRpcCallbacks(self.context)
+        self.callbacks = LinuxBridgeRpcCallbacks(self.rpc_context)
         self.dispatcher = self.callbacks.create_rpc_dispatcher()
         self.conn.create_consumer(self.topic, self.dispatcher,
                                   fanout=False)
@@ -162,7 +174,32 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2):
         self.conn.consume_in_thread()
         self.notifier = AgentNotifierApi(topics.AGENT)
 
-    # TODO(rkukura) Use core mechanism for attribute authorization
+    def _parse_network_vlan_ranges(self):
+        self.network_vlan_ranges = {}
+        for entry in cfg.CONF.VLANS.network_vlan_ranges:
+            if ':' in entry:
+                try:
+                    physical_network, vlan_min, vlan_max = entry.split(':')
+                    self._add_network_vlan_range(physical_network,
+                                                 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] = []
+
+    # REVISIT(rkukura) Use core mechanism for attribute authorization
     # when available.
 
     def _check_provider_view_auth(self, context, network):
@@ -177,40 +214,119 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2):
 
     def _extend_network_dict(self, context, network):
         if self._check_provider_view_auth(context, network):
-            vlan_binding = cdb.get_vlan_binding(network['id'])
-            network['provider:vlan_id'] = vlan_binding['vlan_id']
+            binding = db.get_network_binding(context.session, network['id'])
+            network['provider:physical_network'] = binding.physical_network
+            if binding.vlan_id == constants.FLAT_VLAN_ID:
+                network['provider:network_type'] = 'flat'
+                network['provider:vlan_id'] = None
+            else:
+                network['provider:network_type'] = 'vlan'
+                network['provider:vlan_id'] = binding.vlan_id
+
+    def _process_provider_create(self, context, attrs):
+        network_type = attrs.get('provider:network_type')
+        physical_network = attrs.get('provider:physical_network')
+        vlan_id = attrs.get('provider:vlan_id')
+
+        network_type_set = attributes.is_attr_set(network_type)
+        physical_network_set = attributes.is_attr_set(physical_network)
+        vlan_id_set = attributes.is_attr_set(vlan_id)
+
+        if not (network_type_set or physical_network_set or vlan_id_set):
+            return (None, None, None)
+
+        # Authorize before exposing plugin details to client
+        self._enforce_provider_set_auth(context, attrs)
+
+        if not network_type_set:
+            msg = _("provider:network_type required")
+            raise q_exc.InvalidInput(error_message=msg)
+        elif network_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 == 'vlan':
+            if not vlan_id_set:
+                msg = _("provider:vlan_id required")
+                raise q_exc.InvalidInput(error_message=msg)
+        else:
+            msg = _("invalid provider:network_type %s" % network_type)
+            raise q_exc.InvalidInput(error_message=msg)
+
+        if physical_network_set:
+            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)
+
+        return (network_type, physical_network, vlan_id)
+
+    def _check_provider_update(self, context, attrs):
+        network_type = attrs.get('provider:network_type')
+        physical_network = attrs.get('provider:physical_network')
+        vlan_id = attrs.get('provider:vlan_id')
+
+        network_type_set = attributes.is_attr_set(network_type)
+        physical_network_set = attributes.is_attr_set(physical_network)
+        vlan_id_set = attributes.is_attr_set(vlan_id)
+
+        if not (network_type_set or physical_network_set or vlan_id_set):
+            return
+
+        # Authorize before exposing plugin details to client
+        self._enforce_provider_set_auth(context, attrs)
+
+        msg = _("plugin does not support updating provider attributes")
+        raise q_exc.InvalidInput(error_message=msg)
 
     def create_network(self, context, network):
-        net = super(LinuxBridgePluginV2, self).create_network(context,
-                                                              network)
-        try:
-            vlan_id = network['network'].get('provider:vlan_id')
-            if vlan_id not in (None, attributes.ATTR_NOT_SPECIFIED):
-                self._enforce_provider_set_auth(context, net)
-                cdb.reserve_specific_vlanid(int(vlan_id))
+        (network_type, physical_network,
+         vlan_id) = self._process_provider_create(context,
+                                                  network['network'])
+
+        session = context.session
+        with session.begin(subtransactions=True):
+            if not network_type:
+                physical_network, vlan_id = db.reserve_network(session)
             else:
-                vlan_id = cdb.reserve_vlanid()
-            cdb.add_vlan_binding(vlan_id, net['id'])
+                db.reserve_specific_network(session, physical_network, vlan_id)
+            net = super(LinuxBridgePluginV2, self).create_network(context,
+                                                                  network)
+            db.add_network_binding(session, net['id'],
+                                   physical_network, vlan_id)
             self._extend_network_dict(context, net)
-        except:
-            super(LinuxBridgePluginV2, self).delete_network(context,
-                                                            net['id'])
-            raise
+            # note - exception will rollback entire transaction
         return net
 
     def update_network(self, context, id, network):
-        net = super(LinuxBridgePluginV2, self).update_network(context, id,
-                                                              network)
-        self._extend_network_dict(context, net)
+        self._check_provider_update(context, network['network'])
+
+        session = context.session
+        with session.begin(subtransactions=True):
+            net = super(LinuxBridgePluginV2, self).update_network(context, id,
+                                                                  network)
+            self._extend_network_dict(context, net)
         return net
 
     def delete_network(self, context, id):
-        vlan_binding = cdb.get_vlan_binding(id)
-        result = super(LinuxBridgePluginV2, self).delete_network(context, id)
-        cdb.release_vlanid(vlan_binding['vlan_id'])
+        session = context.session
+        with session.begin(subtransactions=True):
+            binding = db.get_network_binding(session, id)
+            result = super(LinuxBridgePluginV2, self).delete_network(context,
+                                                                     id)
+            db.release_network(session, binding.physical_network,
+                               binding.vlan_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.rpc:
-            self.notifier.network_delete(self.context, id)
-        return result
+            self.notifier.network_delete(self.rpc_context, id)
 
     def get_network(self, context, id, fields=None, verbose=None):
         net = super(LinuxBridgePluginV2, self).get_network(context, id,
@@ -233,7 +349,9 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2):
         port = super(LinuxBridgePluginV2, self).update_port(context, id, port)
         if self.rpc:
             if original_port['admin_state_up'] != port['admin_state_up']:
-                vlan_binding = cdb.get_vlan_binding(port['network_id'])
-                self.notifier.port_update(self.context, port,
-                                          vlan_binding['vlan_id'])
+                binding = db.get_network_binding(context.session,
+                                                 port['network_id'])
+                self.notifier.port_update(self.rpc_context, port,
+                                          binding.physical_network,
+                                          binding.vlan_id)
         return port
diff --git a/quantum/plugins/linuxbridge/tests/unit/test_defaults.py b/quantum/plugins/linuxbridge/tests/unit/test_defaults.py
new file mode 100644 (file)
index 0000000..f3361e7
--- /dev/null
@@ -0,0 +1,42 @@
+# 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.
+
+import unittest2 as unittest
+
+from quantum.openstack.common import cfg
+from quantum.plugins.linuxbridge.common import config
+
+
+class ConfigurationTest(unittest.TestCase):
+
+    def test_defaults(self):
+        self.assertEqual('sqlite://',
+                         cfg.CONF.DATABASE.sql_connection)
+        self.assertEqual(-1,
+                         cfg.CONF.DATABASE.sql_max_retries)
+        self.assertEqual(2,
+                         cfg.CONF.DATABASE.reconnect_interval)
+        self.assertEqual(2,
+                         cfg.CONF.AGENT.polling_interval)
+        self.assertEqual('sudo',
+                         cfg.CONF.AGENT.root_helper)
+
+        ranges = cfg.CONF.VLANS.network_vlan_ranges
+        self.assertEqual(1, len(ranges))
+        self.assertEqual('default:1000:2999', ranges[0])
+
+        mappings = cfg.CONF.LINUX_BRIDGE.physical_interface_mappings
+        self.assertEqual(1, len(mappings))
+        self.assertEqual('default:eth1', mappings[0])
diff --git a/quantum/plugins/linuxbridge/tests/unit/test_lb_db.py b/quantum/plugins/linuxbridge/tests/unit/test_lb_db.py
new file mode 100644 (file)
index 0000000..6579b4c
--- /dev/null
@@ -0,0 +1,125 @@
+# 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.
+
+import unittest2
+
+from quantum.common import exceptions as q_exc
+from quantum.db import api as db
+from quantum.plugins.linuxbridge.db import l2network_db_v2 as lb_db
+
+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)]}
+TEST_NETWORK_ID = 'abcdefghijklmnopqrstuvwxyz'
+
+
+class NetworkStatesTest(unittest2.TestCase):
+    def setUp(self):
+        lb_db.initialize()
+        lb_db.sync_network_states(VLAN_RANGES)
+        self.session = db.get_session()
+
+    def tearDown(self):
+        db.clear_db()
+
+    def test_sync_network_states(self):
+        self.assertIsNone(lb_db.get_network_state(PHYS_NET,
+                                                  VLAN_MIN - 1))
+        self.assertFalse(lb_db.get_network_state(PHYS_NET,
+                                                 VLAN_MIN).allocated)
+        self.assertFalse(lb_db.get_network_state(PHYS_NET,
+                                                 VLAN_MIN + 1).allocated)
+        self.assertFalse(lb_db.get_network_state(PHYS_NET,
+                                                 VLAN_MAX).allocated)
+        self.assertIsNone(lb_db.get_network_state(PHYS_NET,
+                                                  VLAN_MAX + 1))
+
+        lb_db.sync_network_states(UPDATED_VLAN_RANGES)
+
+        self.assertIsNone(lb_db.get_network_state(PHYS_NET,
+                                                  VLAN_MIN + 5 - 1))
+        self.assertFalse(lb_db.get_network_state(PHYS_NET,
+                                                 VLAN_MIN + 5).allocated)
+        self.assertFalse(lb_db.get_network_state(PHYS_NET,
+                                                 VLAN_MIN + 5 + 1).allocated)
+        self.assertFalse(lb_db.get_network_state(PHYS_NET,
+                                                 VLAN_MAX + 5).allocated)
+        self.assertIsNone(lb_db.get_network_state(PHYS_NET,
+                                                  VLAN_MAX + 5 + 1))
+
+    def test_network_pool(self):
+        vlan_ids = set()
+        for x in xrange(VLAN_MIN, VLAN_MAX + 1):
+            physical_network, vlan_id = lb_db.reserve_network(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):
+            physical_network, vlan_id = lb_db.reserve_network(self.session)
+
+        for vlan_id in vlan_ids:
+            lb_db.release_network(self.session, PHYS_NET, vlan_id, VLAN_RANGES)
+
+    def test_specific_network_inside_pool(self):
+        vlan_id = VLAN_MIN + 5
+        self.assertFalse(lb_db.get_network_state(PHYS_NET,
+                                                 vlan_id).allocated)
+        lb_db.reserve_specific_network(self.session, PHYS_NET, vlan_id)
+        self.assertTrue(lb_db.get_network_state(PHYS_NET,
+                                                vlan_id).allocated)
+
+        with self.assertRaises(q_exc.VlanIdInUse):
+            lb_db.reserve_specific_network(self.session, PHYS_NET, vlan_id)
+
+        lb_db.release_network(self.session, PHYS_NET, vlan_id, VLAN_RANGES)
+        self.assertFalse(lb_db.get_network_state(PHYS_NET,
+                                                 vlan_id).allocated)
+
+    def test_specific_network_outside_pool(self):
+        vlan_id = VLAN_MAX + 5
+        self.assertIsNone(lb_db.get_network_state(PHYS_NET, vlan_id))
+        lb_db.reserve_specific_network(self.session, PHYS_NET, vlan_id)
+        self.assertTrue(lb_db.get_network_state(PHYS_NET,
+                                                vlan_id).allocated)
+
+        with self.assertRaises(q_exc.VlanIdInUse):
+            lb_db.reserve_specific_network(self.session, PHYS_NET, vlan_id)
+
+        lb_db.release_network(self.session, PHYS_NET, vlan_id, VLAN_RANGES)
+        self.assertIsNone(lb_db.get_network_state(PHYS_NET, vlan_id))
+
+
+class NetworkBindingsTest(unittest2.TestCase):
+    def setUp(self):
+        lb_db.initialize()
+        self.session = db.get_session()
+
+    def tearDown(self):
+        db.clear_db()
+
+    def test_add_network_binding(self):
+        self.assertIsNone(lb_db.get_network_binding(self.session,
+                                                    TEST_NETWORK_ID))
+        lb_db.add_network_binding(self.session, TEST_NETWORK_ID, PHYS_NET,
+                                  1234)
+        binding = lb_db.get_network_binding(self.session, TEST_NETWORK_ID)
+        self.assertIsNotNone(binding)
+        self.assertEqual(binding.network_id, TEST_NETWORK_ID)
+        self.assertEqual(binding.physical_network, PHYS_NET)
+        self.assertEqual(binding.vlan_id, 1234)
index 1fc3f6fd6a1d3a79baf251504417b437a580a96f..04ed559f02dd924b969e7ac0b5e9d22ea756be47 100644 (file)
@@ -74,7 +74,9 @@ class rpcApiTestCase(unittest2.TestCase):
                                                 topics.PORT,
                                                 topics.UPDATE),
                           'port_update', rpc_method='fanout_cast',
-                          port='fake_port', vlan_id='fake_vlan_id')
+                          port='fake_port',
+                          physical_network='fake_net',
+                          vlan_id='fake_vlan_id')
 
     def test_device_details(self):
         rpcapi = agent_rpc.PluginApi(topics.PLUGIN)
index d7c29c1bb4216cf43cfa797dd89238ce0ecb9fbb..06f3716d1840e5136fe850dd94ea189a2aea8dc8 100644 (file)
@@ -140,7 +140,9 @@ def reserve_specific_vlan_id(vlan_id, session):
                       filter_by(vlan_id=vlan_id).
                       one())
             if record.vlan_used:
-                raise q_exc.VlanIdInUse(vlan_id=vlan_id)
+                # 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
         except exc.NoResultFound:
index 6792cd54f54687eb3077ab7cbb6f16c5065b6c79..9bc15886ba37ec5998f86203cfdd5ac340cafb88 100644 (file)
@@ -234,15 +234,86 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
                 network['provider:vlan_id'] = ovs_db_v2.get_vlan(
                     network['id'], context.session)
 
+    def _process_provider_create(self, context, attrs):
+        network_type = attrs.get('provider:network_type')
+        physical_network = attrs.get('provider:physical_network')
+        vlan_id = attrs.get('provider:vlan_id')
+
+        network_type_set = attributes.is_attr_set(network_type)
+        physical_network_set = attributes.is_attr_set(physical_network)
+        vlan_id_set = attributes.is_attr_set(vlan_id)
+
+        if not (network_type_set or physical_network_set or vlan_id_set):
+            return (None, None, None)
+
+        # Authorize before exposing plugin details to client
+        self._enforce_provider_set_auth(context, attrs)
+
+        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':
+            if not vlan_id_set:
+                msg = _("provider:vlan_id required")
+                raise q_exc.InvalidInput(error_message=msg)
+        else:
+            msg = _("invalid provider:network_type %s" % network_type)
+            raise q_exc.InvalidInput(error_message=msg)
+
+        if physical_network_set:
+            msg = _("plugin does not support specifying physical_network")
+            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)
+
+    def _check_provider_update(self, context, attrs):
+        network_type = attrs.get('provider:network_type')
+        physical_network = attrs.get('provider:physical_network')
+        vlan_id = attrs.get('provider:vlan_id')
+
+        network_type_set = attributes.is_attr_set(network_type)
+        physical_network_set = attributes.is_attr_set(physical_network)
+        vlan_id_set = attributes.is_attr_set(vlan_id)
+
+        if not (network_type_set or physical_network_set or vlan_id_set):
+            return
+
+        # Authorize before exposing plugin details to client
+        self._enforce_provider_set_auth(context, attrs)
+
+        msg = _("plugin does not support updating provider attributes")
+        raise q_exc.InvalidInput(error_message=msg)
+
     def create_network(self, context, network):
+        (network_type, physical_network,
+         vlan_id) = self._process_provider_create(context,
+                                                  network['network'])
+
         net = super(OVSQuantumPluginV2, self).create_network(context, network)
         try:
-            vlan_id = network['network'].get('provider:vlan_id')
-            if vlan_id not in (None, attributes.ATTR_NOT_SPECIFIED):
-                self._enforce_provider_set_auth(context, net)
-                ovs_db_v2.reserve_specific_vlan_id(vlan_id, context.session)
-            else:
+            if not network_type:
                 vlan_id = ovs_db_v2.reserve_vlan_id(context.session)
+            else:
+                ovs_db_v2.reserve_specific_vlan_id(vlan_id, context.session)
         except Exception:
             super(OVSQuantumPluginV2, self).delete_network(context, net['id'])
             raise
@@ -253,6 +324,8 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
         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)
index 1987d8ea63aa13f0f807926764db0e966872455d..f8e7f1ec7cd862f44ed2adea5cf0dc96084ca895 100644 (file)
@@ -16,6 +16,7 @@
 import unittest
 
 from quantum.openstack.common import cfg
+from quantum.plugins.openvswitch.common import config
 
 
 class ConfigurationTest(unittest.TestCase):
index 0dbb4a2a2739cfba58bbff2d2df403a300372189..961af504208a496fdf3e0063feaf6a02a97b2acf 100644 (file)
@@ -16,3 +16,6 @@ api_extensions_path = unit/extensions
 
 # Paste configuration file
 api_paste_config = api-paste.ini.test
+
+# The messaging module to use, defaults to kombu.
+rpc_backend = quantum.openstack.common.rpc.impl_fake