]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
blueprint quantum-linux-bridge-plugin
authorSumit Naiksatam <snaiksat@cisco.com>
Sun, 22 Jan 2012 09:35:45 +0000 (01:35 -0800)
committerSumit Naiksatam <snaiksat@cisco.com>
Wed, 8 Feb 2012 21:32:55 +0000 (13:32 -0800)
Squashed commit of the following:

commit 6c4995736a56349923d34353031eb301780fc6d2
Author: Sumit Naiksatam <snaiksat@cisco.com>
Date:   Sat Jan 21 22:31:09 2012 -0800

    Some more explanation in the README.

    Changing defaults in the conf file.

commit 924b118468d7bd21737e9e2cf468ff83d0a20764
Author: Shweta <shpadubi@cisco.com>
Date:   Sat Jan 21 20:58:39 2012 -0500

    Adding Unit Tests for LinuxBridge Agent

commit 12115650257483172c5e2bc889634dbdf3596d27
Author: Sumit Naiksatam <snaiksat@cisco.com>
Date:   Sat Jan 21 05:21:24 2012 -0800

    Adding sqlite requirement

    Changing default mysql port number

    Fixing log statement

commit 0ad1400e5dfc445b94e9024d92321eb3cd0588a5
Merge: 1b7ba8f 9c5c2ca
Author: Sumit Naiksatam <snaiksat@cisco.com>
Date:   Sat Jan 21 05:12:44 2012 -0800

    Merge remote branch 'upstream/master' into snaiksat/linux-bridge-plugin

commit 1b7ba8f7e7b6657734b669194ddfdcfcbfc833be
Author: Sumit Naiksatam <snaiksat@cisco.com>
Date:   Sat Jan 21 04:04:50 2012 -0800

    Fixes to get the tests to run correctly.

    Also incorporated changes to be able to run both sqlite and mysql DBs.

commit 4cead17576c293319dfdfd363dd18e81ba196b3b
Author: Sumit Naiksatam <snaiksat@cisco.com>
Date:   Fri Jan 20 15:32:35 2012 -0800

    Fixed inccorect calls to the DB

commit c4f325729fbd06ee3f5d3520da4d23b2cd8c353b
Author: Sumit Naiksatam <snaiksat@cisco.com>
Date:   Fri Jan 20 12:18:18 2012 -0800

    Removing the specialized db modules (which used InnoDB engine) and
    instead using the Quantum DB now.
    Incorporated changes to setup so that the Linux Bridge plugin can be
    installed.
    Other changes to README and tests.

commit b9498939d723e353808cface87f4453e33e94b41
Author: Sumit Naiksatam <snaiksat@cisco.com>
Date:   Mon Jan 16 20:00:14 2012 -0800

    Adding unit tests

commit a0ab990fdcbf67a950d08c6b5b6d20ceb850619a
Merge: 60e38cc f268b5e
Author: Sumit Naiksatam <snaiksat@cisco.com>
Date:   Mon Jan 16 18:02:55 2012 -0800

    Merge remote branch 'upstream/master' into snaiksat/linux-bridge-plugin

commit 60e38cc44886b5c8c9e47d89d8912d1dee23fbd1
Author: Sumit Naiksatam <snaiksat@cisco.com>
Date:   Mon Jan 16 13:44:37 2012 -0800

    This contains a fix for the earlier reported issue with the DHCP
    failing.

    The gateway IP address is now applied both to the bridge, and the
    gateway interface.

commit ffea86a3465b8a5ed93b13f818e0afaefa6787ee
Author: Sumit Naiksatam <snaiksat@cisco.com>
Date:   Sun Jan 15 20:00:38 2012 -0800

    Fixing an issue in the agent, sometimes the bridges for deleted networks
    were not getting cleaned up

commit 87f76fc34f1c70cd82576b8698d704853c892422
Merge: c8b097a 60d171e
Author: Sumit Naiksatam <snaiksat@cisco.com>
Date:   Sun Jan 15 19:40:33 2012 -0800

    Merge remote branch 'upstream/master' into snaiksat/linux-bridge-plugin

commit c8b097abc2060b2eae01d84f9fed2c89851d93fd
Author: Sumit Naiksatam <snaiksat@cisco.com>
Date:   Sun Jan 15 19:37:08 2012 -0800

    Simplified the logic for creating the bridge on the nova network host.

commit 499dbacd4c5352c5320f3b6e5e8cd7f3629dbcc8
Author: Sumit Naiksatam <snaiksat@cisco.com>
Date:   Fri Jan 13 16:07:53 2012 -0800

    Fix for the DHCP issue, now applying Gateway IP to the bridge

    Also MAC address from original tap device are applied to bridge
    and vlan subinterface

commit 6b4a2aea59702e2c12eeacc86101df9f6770d5ec
Author: Sumit Naiksatam <snaiksat@cisco.com>
Date:   Sat Jan 7 14:29:00 2012 -0800

    Optimizations for processing in the loop

commit 01aa47d3572439b193077432c63bf2b85c629edb
Merge: 184f5dd 05df087
Author: Sumit Naiksatam <snaiksat@cisco.com>
Date:   Sun Jan 1 19:04:17 2012 -0800

    Changes to incorporate Operational Status
    Merge remote branch 'origin' into snaiksat/linux-bridge-plugin

    Conflicts:
     quantum/db/api.py

commit 05df0870191fac0353fe12a33efff68ef7ed0784
Merge: 31d586b 5b23b5e
Author: Sumit Naiksatam <snaiksat@cisco.com>
Date:   Fri Dec 30 12:30:05 2011 -0800

    Merge remote branch 'upstream/master'

commit 184f5dd8b73bc51025509792c15203ee73cd015e
Author: Sumit Naiksatam <snaiksat@cisco.com>
Date:   Mon Dec 12 02:09:12 2011 -0800

    Initial checkin for Linux Bridge L2 plugin.

Change-Id: I5b27538be6a768a6f7eb77409197f7d8b4bbc628

24 files changed:
MANIFEST.in
etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini [new file with mode: 0644]
quantum/plugins/linuxbridge/LinuxBridgePlugin.py [new file with mode: 0644]
quantum/plugins/linuxbridge/README [new file with mode: 0644]
quantum/plugins/linuxbridge/__init__.py [new file with mode: 0644]
quantum/plugins/linuxbridge/agent/__init__.py [new file with mode: 0644]
quantum/plugins/linuxbridge/agent/linuxbridge_quantum_agent.py [new file with mode: 0755]
quantum/plugins/linuxbridge/common/__init__.py [new file with mode: 0644]
quantum/plugins/linuxbridge/common/configparser.py [new file with mode: 0644]
quantum/plugins/linuxbridge/common/constants.py [new file with mode: 0644]
quantum/plugins/linuxbridge/common/exceptions.py [new file with mode: 0644]
quantum/plugins/linuxbridge/common/utils.py [new file with mode: 0644]
quantum/plugins/linuxbridge/db/__init__.py [new file with mode: 0644]
quantum/plugins/linuxbridge/db/l2network_db.py [new file with mode: 0644]
quantum/plugins/linuxbridge/db/l2network_models.py [new file with mode: 0644]
quantum/plugins/linuxbridge/nova/linux_net_linux_bridge.py [new file with mode: 0755]
quantum/plugins/linuxbridge/nova/vif_linuxbridge_quantum.py [new file with mode: 0644]
quantum/plugins/linuxbridge/plugin_configuration.py [new file with mode: 0644]
quantum/plugins/linuxbridge/run_tests.py [new file with mode: 0644]
quantum/plugins/linuxbridge/tests/__init__.py [new file with mode: 0644]
quantum/plugins/linuxbridge/tests/unit/__init__.py [new file with mode: 0644]
quantum/plugins/linuxbridge/tests/unit/_test_linuxbridgeAgent.py [new file with mode: 0644]
quantum/plugins/linuxbridge/tests/unit/test_database.py [new file with mode: 0644]
setup.py

index 9ce200097196bfd6a1f34398f845d454b6222971..3d36e9b0e0363a83f91f5661a251a8c8d97213a5 100644 (file)
@@ -3,3 +3,4 @@ include etc/*
 include etc/init.d/*
 include etc/quantum/plugins/openvswitch/*
 include etc/quantum/plugins/cisco/*
+include etc/quantum/plugins/linuxbridge/*
diff --git a/etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini b/etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini
new file mode 100644 (file)
index 0000000..8bead52
--- /dev/null
@@ -0,0 +1,24 @@
+[VLANS]
+vlan_start=1000
+vlan_end=3000
+
+[DATABASE]
+# Use the following when running the tests for the in-memory DB
+connection = sqlite
+# Uncomment the following for using the MySQL DB when actually running the plugin,
+# also remove the earlier sqlite connection configuration
+#connection = mysql
+name = quantum_linux_bridge
+user = <mysql_user_name_here>
+pass = <mysql_password_here>
+host = <hostname_or_IP_address_of_Quantum_server>
+# If you use a non-default port for the DB, change the following
+port = 3306
+
+[LINUX_BRIDGE]
+#this is the interface connected to the switch on your Quantum network
+physical_interface = eth1
+
+[AGENT]
+#agent's polling interval in seconds
+polling_interval = 2
diff --git a/quantum/plugins/linuxbridge/LinuxBridgePlugin.py b/quantum/plugins/linuxbridge/LinuxBridgePlugin.py
new file mode 100644 (file)
index 0000000..1037ccb
--- /dev/null
@@ -0,0 +1,257 @@
+"""
+# 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: Sumit Naiksatam, Cisco Systems, Inc.
+"""
+
+import logging
+
+from quantum.api.api_common import OperationalStatus
+from quantum.common import exceptions as exc
+from quantum.db import api as db
+from quantum.plugins.linuxbridge import plugin_configuration as conf
+from quantum.plugins.linuxbridge.common import constants as const
+from quantum.plugins.linuxbridge.common import utils as cutil
+from quantum.plugins.linuxbridge.db import l2network_db as cdb
+from quantum.quantum_plugin_base import QuantumPluginBase
+
+
+LOG = logging.getLogger(__name__)
+
+
+class LinuxBridgePlugin(QuantumPluginBase):
+    """
+    LinuxBridgePlugin provides support for Quantum abstractions
+    using LinuxBridge. A new VLAN is created for each network.
+    It relies on an agent to perform the actual bridge configuration
+    on each host.
+    """
+
+    def __init__(self, configfile=None):
+        cdb.initialize()
+        LOG.debug("Linux Bridge Plugin initialization done successfully")
+
+    def _get_vlan_for_tenant(self, tenant_id, **kwargs):
+        """Get an available VLAN ID"""
+        try:
+            return cdb.reserve_vlanid()
+        except:
+            raise Exception("Failed to reserve VLAN ID for network")
+
+    def _release_vlan_for_tenant(self, tenant_id, net_id, **kwargs):
+        """Release the ID"""
+        vlan_binding = cdb.get_vlan_binding(net_id)
+        return cdb.release_vlanid(vlan_binding[const.VLANID])
+
+    def _validate_port_state(self, port_state):
+        if port_state.upper() not in ('ACTIVE', 'DOWN'):
+            raise exc.StateInvalid(port_state=port_state)
+        return True
+
+    def get_all_networks(self, tenant_id, **kwargs):
+        """
+        Returns a dictionary containing all
+        <network_uuid, network_name> for
+        the specified tenant.
+        """
+        LOG.debug("LinuxBridgePlugin.get_all_networks() called")
+        networks_list = db.network_list(tenant_id)
+        new_networks_list = []
+        for network in networks_list:
+            new_network_dict = cutil.make_net_dict(network[const.UUID],
+                                                   network[const.NETWORKNAME],
+                                                   [], network[const.OPSTATUS])
+            new_networks_list.append(new_network_dict)
+
+        # This plugin does not perform filtering at the moment
+        return new_networks_list
+
+    def get_network_details(self, tenant_id, net_id):
+        """
+        retrieved a list of all the remote vifs that
+        are attached to the network
+        """
+        LOG.debug("LinuxBridgePlugin.get_network_details() called")
+        network = db.network_get(net_id)
+        ports_list = db.port_list(net_id)
+        ports_on_net = []
+        for port in ports_list:
+            new_port = cutil.make_port_dict(port)
+            ports_on_net.append(new_port)
+
+        new_network = cutil.make_net_dict(network[const.UUID],
+                                          network[const.NETWORKNAME],
+                                          ports_on_net,
+                                          network[const.OPSTATUS])
+
+        return new_network
+
+    def create_network(self, tenant_id, net_name, **kwargs):
+        """
+        Creates a new Virtual Network, and assigns it
+        a symbolic name.
+        """
+        LOG.debug("LinuxBridgePlugin.create_network() called")
+        new_network = db.network_create(tenant_id, net_name,
+                                        op_status=OperationalStatus.UP)
+        new_net_id = new_network[const.UUID]
+        vlan_id = self._get_vlan_for_tenant(tenant_id)
+        cdb.add_vlan_binding(vlan_id, new_net_id)
+        new_net_dict = {const.NET_ID: new_net_id,
+                        const.NET_NAME: net_name,
+                        const.NET_PORTS: [],
+                        const.NET_OP_STATUS: new_network[const.OPSTATUS]}
+        return new_net_dict
+
+    def delete_network(self, tenant_id, net_id):
+        """
+        Deletes the network with the specified network identifier
+        belonging to the specified tenant.
+        """
+        LOG.debug("LinuxBridgePlugin.delete_network() called")
+        net = db.network_get(net_id)
+        if net:
+            ports_on_net = db.port_list(net_id)
+            if len(ports_on_net) > 0:
+                for port in ports_on_net:
+                    if port[const.INTERFACEID]:
+                        raise exc.NetworkInUse(net_id=net_id)
+                for port in ports_on_net:
+                    self.delete_port(tenant_id, net_id, port[const.UUID])
+
+            net_dict = cutil.make_net_dict(net[const.UUID],
+                                           net[const.NETWORKNAME],
+                                           [], net[const.OPSTATUS])
+            try:
+                self._release_vlan_for_tenant(tenant_id, net_id)
+                cdb.remove_vlan_binding(net_id)
+            except Exception as excp:
+                LOG.warning("Exception: %s" % excp)
+                db.network_update(net_id, tenant_id, {const.OPSTATUS:
+                                                      OperationalStatus.DOWN})
+            db.network_destroy(net_id)
+            return net_dict
+        # Network not found
+        raise exc.NetworkNotFound(net_id=net_id)
+
+    def update_network(self, tenant_id, net_id, **kwargs):
+        """
+        Updates the attributes of a particular Virtual Network.
+        """
+        LOG.debug("LinuxBridgePlugin.update_network() called")
+        network = db.network_update(net_id, tenant_id, **kwargs)
+        net_dict = cutil.make_net_dict(network[const.UUID],
+                                       network[const.NETWORKNAME],
+                                       [], network[const.OPSTATUS])
+        return net_dict
+
+    def get_all_ports(self, tenant_id, net_id, **kwargs):
+        """
+        Retrieves all port identifiers belonging to the
+        specified Virtual Network.
+        """
+        LOG.debug("LinuxBridgePlugin.get_all_ports() called")
+        network = db.network_get(net_id)
+        ports_list = db.port_list(net_id)
+        ports_on_net = []
+        for port in ports_list:
+            new_port = cutil.make_port_dict(port)
+            ports_on_net.append(new_port)
+
+        # This plugin does not perform filtering at the moment
+        return ports_on_net
+
+    def get_port_details(self, tenant_id, net_id, port_id):
+        """
+        This method allows the user to retrieve a remote interface
+        that is attached to this particular port.
+        """
+        LOG.debug("LinuxBridgePlugin.get_port_details() called")
+        network = db.network_get(net_id)
+        port = db.port_get(port_id, net_id)
+        new_port_dict = cutil.make_port_dict(port)
+        return new_port_dict
+
+    def create_port(self, tenant_id, net_id, port_state=None, **kwargs):
+        """
+        Creates a port on the specified Virtual Network.
+        """
+        LOG.debug("LinuxBridgePlugin.create_port() called")
+        port = db.port_create(net_id, port_state,
+                                op_status=OperationalStatus.DOWN)
+        unique_port_id_string = port[const.UUID]
+        new_port_dict = cutil.make_port_dict(port)
+        return new_port_dict
+
+    def update_port(self, tenant_id, net_id, port_id, **kwargs):
+        """
+        Updates the attributes of a port on the specified Virtual Network.
+        """
+        LOG.debug("LinuxBridgePlugin.update_port() called")
+        network = db.network_get(net_id)
+        self._validate_port_state(kwargs["state"])
+        port = db.port_update(port_id, net_id, **kwargs)
+
+        new_port_dict = cutil.make_port_dict(port)
+        return new_port_dict
+
+    def delete_port(self, tenant_id, net_id, port_id):
+        """
+        Deletes a port on a specified Virtual Network,
+        if the port contains a remote interface attachment,
+        the remote interface is first un-plugged and then the port
+        is deleted.
+        """
+        LOG.debug("LinuxBridgePlugin.delete_port() called")
+        network = db.network_get(net_id)
+        port = db.port_get(port_id, net_id)
+        attachment_id = port[const.INTERFACEID]
+        if not attachment_id:
+            db.port_destroy(port_id, net_id)
+            new_port_dict = cutil.make_port_dict(port)
+            return new_port_dict
+        else:
+            raise exc.PortInUse(port_id=port_id, net_id=net_id,
+                                att_id=attachment_id)
+
+    def plug_interface(self, tenant_id, net_id, port_id, remote_interface_id):
+        """
+        Attaches a remote interface to the specified port on the
+        specified Virtual Network.
+        """
+        LOG.debug("LinuxBridgePlugin.plug_interface() called")
+        network = db.network_get(net_id)
+        port = db.port_get(port_id, net_id)
+        attachment_id = port[const.INTERFACEID]
+        if attachment_id:
+            raise exc.PortInUse(port_id=port_id, net_id=net_id,
+                                att_id=attachment_id)
+        db.port_set_attachment(port_id, net_id, remote_interface_id)
+
+    def unplug_interface(self, tenant_id, net_id, port_id):
+        """
+        Detaches a remote interface from the specified port on the
+        specified Virtual Network.
+        """
+        LOG.debug("LinuxBridgePlugin.unplug_interface() called")
+        network = db.network_get(net_id)
+        port = db.port_get(port_id, net_id)
+        attachment_id = port[const.INTERFACEID]
+        if attachment_id == None:
+            raise exc.InvalidDetach(port_id=port_id, net_id=net_id,
+                                    att_id=remote_interface_id)
+        db.port_unset_attachment(port_id, net_id)
+        db.port_update(port_id, net_id, op_status=OperationalStatus.DOWN)
diff --git a/quantum/plugins/linuxbridge/README b/quantum/plugins/linuxbridge/README
new file mode 100644 (file)
index 0000000..c439099
--- /dev/null
@@ -0,0 +1,155 @@
+# -- Background
+
+The Quantum Linux Bridge plugin is a plugin that allows you to manage
+connectivity between VMs on hosts that are capable of running a Linux Bridge.
+
+The Quantum Linux Bridge plugin consists of three components:
+
+1) The plugin itself: The plugin uses a database backend (mysql for
+   now) to store configuration and mappings that are used by the
+   agent.  The mysql server runs on a central server (often the same
+   host as nova itself).
+
+2) The quantum service host which will be running quantum.  This can
+   be run on the server running nova.
+
+3) An agent which runs on the host and communicates with the host operating
+   system. The agent gathers the configuration and mappings from
+   the mysql database running on the quantum host.
+
+The sections below describe how to configure and run the quantum
+service with the Linux Bridge plugin.
+
+# -- Python library dependencies
+
+   Make sure you have the following package(s) installedi on quantum server
+   host as well as any hosts which run the agent:
+   python-configobj
+   bridge-utils
+   python-mysqldb
+   sqlite3
+
+# -- Nova configuration (controller node)
+
+1) Make sure to set up nova using the quantum network manager in the
+   nova.conf on the node that will be running nova-network.
+
+--network_manager=nova.network.quantum.manager.QuantumManager
+
+# -- Nova configuration (compute node(s))
+
+1) Configure the vif driver, and libvirt/vif type
+
+--connection_type=libvirt
+--libvirt_type=qemu
+--libvirt_vif_type=ethernet
+--libvirt_vif_driver=nova.virt.libvirt.vif_linuxbridge_quantum.QuantumLibvirtLinuxBridgeDriver
+--linuxnet_interface_driver=nova.network.quantum.linux_net_linux_bridge.QuantumLibvirtLinuxBridgeDriver
+
+The above two drivers are packaged with Quantum in the location:
+quantum/plugins/linuxbridge/nova
+
+These need to copied to the compute node in the appropriate locations.
+
+a) Copy:
+   quantum/plugins/linuxbridge/nova/vif_linuxbridge_quantum.py
+   to:
+   nova/virt/libvirt/vif_linuxbridge_quantum.py
+
+b) Copy:
+   quantum/plugins/linuxbridge/nova/linux_net_linux_bridge.py
+   to:
+   nova/network/quantum/linux_net_linux_bridge.py
+
+2) If you want a DHCP server to be run for the VMs to acquire IPs,
+   add the following flag to your nova.conf file:
+
+--quantum_use_dhcp
+
+(Note: For more details on how to work with Quantum using Nova, i.e. how to create networks and such,
+ please refer to the top level Quantum README which points to the relevant documentation.)
+
+# -- Quantum configuration
+
+Make the Linux Bridge plugin the current quantum plugin
+
+- edit etc/plugins.ini and change the provider line to be:
+provider = quantum.plugins.linuxbridge.LinuxBridgePlugin.LinuxBridgePlugin
+
+# -- Database config.
+
+(Note: The plugin ships with a default SQLite in-memory database configuration,
+ and can be used to run tests without performing the suggested DB config below.)
+
+The Linux Bridge quantum plugin requires access to a mysql database in order
+to store configuration and mappings that will be used by the agent.  Here is
+how to set up the database on the host that you will be running the quantum
+service on.
+
+MySQL should be installed on the host, and all plugins and clients
+must be configured with access to the database.
+
+To prep mysql, run:
+
+$ mysql -u root -p -e "create database quantum_linux_bridge"
+
+# log in to mysql service
+$ mysql -u root -p
+# The Linux Bridge Quantum agent running on each compute node must be able to
+# make a mysql connection back to the main database server.
+mysql> GRANT USAGE ON *.* to root@'yourremotehost' IDENTIFIED BY 'newpassword';
+# force update of authorization changes
+mysql> FLUSH PRIVILEGES;
+
+(Note: If the remote connection fails to MySQL, you might need to add the IP address,
+ and/or fully-qualified hostname, and/or unqualified hostname in the above GRANT sql
+ command. Also, you might need to specify "ALL" instead of "USAGE".)
+
+# -- Plugin configuration
+
+- Edit the configuration file:
+  etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini
+  Make sure it matches your mysql configuration.  This file must be updated
+  with the addresses and credentials to access the database.
+
+  Note: When running the tests, set the connection type to sqlite, and when
+  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.
+
+# -- Agent configuration
+
+- Edit the configuration file:
+  etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini
+
+- Copy quantum/plugins/linuxbridge/agent/linuxbridge_quantum_agent.py
+  and etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini
+  to the compute node.
+
+$ Run the following:
+  sudo python linuxbridge_quantum_agent.py linuxbridge_conf.ini
+  (Use --verbose option to see the logs)
+
+# -- Running Tests
+
+(Note: The plugin ships with a default SQLite in-memory database configuration,
+ and can be used to run tests out of the box. Alternatively you can perform the
+ DB configuration for a persistent database as mentioned in the Database
+ Configuration section.)
+
+- To run tests related to the Plugin and the VLAN management (run the
+  following from the top level Quantum directory):
+  PLUGIN_DIR=quantum/plugins/linuxbridge ./run_tests.sh -N
+
+- The above will not however run the tests for the agent (which deals
+  with creating the bridge and interfaces). To run the agent tests, run the
+  following from the top level Quantum directory:
+  sudo PLUGIN_DIR=quantum/plugins/linuxbridge ./run_tests.sh -N tests.unit._test_linuxbridgeAgent
+
+  (Note: To run the agent tests you should have the environment setup as
+   indicated in the Agent Configuration, and also have the necessary dependencies
+   insalled.)
diff --git a/quantum/plugins/linuxbridge/__init__.py b/quantum/plugins/linuxbridge/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/quantum/plugins/linuxbridge/agent/__init__.py b/quantum/plugins/linuxbridge/agent/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/quantum/plugins/linuxbridge/agent/linuxbridge_quantum_agent.py b/quantum/plugins/linuxbridge/agent/linuxbridge_quantum_agent.py
new file mode 100755 (executable)
index 0000000..3481754
--- /dev/null
@@ -0,0 +1,470 @@
+#!/usr/bin/env python
+# 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.
+#
+#
+# Performs per host Linux Bridge configuration for Quantum.
+# Based on the structure of the OpenVSwitch agent in the
+# Quantum OpenVSwitch Plugin.
+# @author: Sumit Naiksatam, Cisco Systems, Inc.
+#
+
+from optparse import OptionParser
+from subprocess import *
+
+import ConfigParser
+import logging as LOG
+import MySQLdb
+import os
+import signal
+import sqlite3
+import sys
+import time
+
+
+BRIDGE_NAME_PREFIX = "brq"
+GATEWAY_INTERFACE_PREFIX = "gw-"
+TAP_INTERFACE_PREFIX = "tap"
+BRIDGE_FS = "/sys/devices/virtual/net/"
+BRIDGE_NAME_PLACEHOLDER = "bridge_name"
+BRIDGE_INTERFACES_FS = BRIDGE_FS + BRIDGE_NAME_PLACEHOLDER + "/brif/"
+PORT_OPSTATUS_UPDATESQL = "UPDATE ports SET op_status = '%s' WHERE uuid = '%s'"
+DEVICE_NAME_PLACEHOLDER = "device_name"
+BRIDGE_PORT_FS_FOR_DEVICE = BRIDGE_FS + DEVICE_NAME_PLACEHOLDER + "/brport"
+VLAN_BINDINGS = "vlan_bindings"
+PORT_BINDINGS = "port_bindings"
+OP_STATUS_UP = "UP"
+OP_STATUS_DOWN = "DOWN"
+DB_CONNECTION = None
+
+
+class LinuxBridge:
+    def __init__(self, br_name_prefix, physical_interface):
+        self.br_name_prefix = br_name_prefix
+        self.physical_interface = physical_interface
+
+    def run_cmd(self, args):
+        LOG.debug("Running command: " + " ".join(args))
+        p = Popen(args, stdout=PIPE)
+        retval = p.communicate()[0]
+        if p.returncode == -(signal.SIGALRM):
+            LOG.debug("Timeout running command: " + " ".join(args))
+        if retval:
+            LOG.debug("Command returned: %s" % retval)
+        return retval
+
+    def device_exists(self, device):
+        """Check if ethernet device exists."""
+        retval = self.run_cmd(['ip', 'link', 'show', 'dev', device])
+        if retval:
+            return True
+        else:
+            return False
+
+    def get_bridge_name(self, network_id):
+        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]
+        return bridge_name
+
+    def get_subinterface_name(self, 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)
+        return subinterface_name
+
+    def get_tap_device_name(self, interface_id):
+        if not interface_id:
+            LOG.warning("Invalid Interface ID, will lead to incorrect " \
+                      "tap device name")
+        tap_device_name = TAP_INTERFACE_PREFIX + interface_id[0:11]
+        return tap_device_name
+
+    def get_all_quantum_bridges(self):
+        quantum_bridge_list = []
+        bridge_list = os.listdir(BRIDGE_FS)
+        for bridge in bridge_list:
+            if bridge.startswith(BRIDGE_NAME_PREFIX):
+                quantum_bridge_list.append(bridge)
+        return quantum_bridge_list
+
+    def get_interfaces_on_bridge(self, bridge_name):
+        if self.device_exists(bridge_name):
+            bridge_interface_path = \
+                    BRIDGE_INTERFACES_FS.replace(BRIDGE_NAME_PLACEHOLDER,
+                                                 bridge_name)
+            return os.listdir(bridge_interface_path)
+
+    def get_all_tap_devices(self):
+        tap_devices = []
+        retval = self.run_cmd(['ip', 'tuntap'])
+        rows = retval.split('\n')
+        for row in rows:
+            split_row = row.split(':')
+            if split_row[0].startswith(TAP_INTERFACE_PREFIX):
+                tap_devices.append(split_row[0])
+
+        return tap_devices
+
+    def get_all_gateway_devices(self):
+        gw_devices = []
+        retval = self.run_cmd(['ip', 'tuntap'])
+        rows = retval.split('\n')
+        for row in rows:
+            split_row = row.split(':')
+            if split_row[0].startswith(GATEWAY_INTERFACE_PREFIX):
+                gw_devices.append(split_row[0])
+
+        return gw_devices
+
+    def get_bridge_for_tap_device(self, tap_device_name):
+        bridges = self.get_all_quantum_bridges()
+        for bridge in bridges:
+            interfaces = self.get_interfaces_on_bridge(bridge)
+            if tap_device_name in interfaces:
+                return bridge
+
+        return None
+
+    def is_device_on_bridge(self, device_name):
+        if not device_name:
+            return False
+        else:
+            bridge_port_path = \
+                    BRIDGE_PORT_FS_FOR_DEVICE.replace(DEVICE_NAME_PLACEHOLDER,
+                                                      device_name)
+            return os.path.exists(bridge_port_path)
+
+    def ensure_vlan_bridge(self, network_id, vlan_id):
+        """Create a vlan and bridge unless they already exist."""
+        interface = self.ensure_vlan(vlan_id)
+        bridge_name = self.get_bridge_name(network_id)
+        self.ensure_bridge(bridge_name, interface)
+        return interface
+
+    def ensure_vlan(self, vlan_id):
+        """Create a vlan unless it already exists."""
+        interface = self.get_subinterface_name(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))
+            if self.run_cmd(['ip', 'link', 'add', 'link',
+                             self.physical_interface,
+                             'name', interface, 'type', 'vlan', 'id',
+                             vlan_id]):
+                return
+            if self.run_cmd(['ip', 'link', 'set', interface, 'up']):
+                return
+            LOG.debug("Done creating subinterface %s" % interface)
+        return interface
+
+    def ensure_bridge(self, bridge_name, interface):
+        """
+        Create a bridge unless it already exists.
+        """
+        if not self.device_exists(bridge_name):
+            LOG.debug("Starting bridge %s for subinterface %s" % (bridge_name,
+                                                                interface))
+            if self.run_cmd(['brctl', 'addbr', bridge_name]):
+                return
+            if self.run_cmd(['brctl', 'setfd', bridge_name, str(0)]):
+                return
+            if self.run_cmd(['brctl', 'stp', bridge_name, 'off']):
+                return
+            if self.run_cmd(['ip', 'link', 'set', bridge_name, 'up']):
+                return
+            LOG.debug("Done starting bridge %s for subinterface %s" %
+                      (bridge_name, interface))
+
+        self.run_cmd(['brctl', 'addif', bridge_name, interface])
+
+    def add_tap_interface(self, network_id, 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
+        """
+        if not tap_device_name:
+            return False
+
+        if not self.device_exists(tap_device_name):
+            LOG.debug("Tap device: %s does not exist on this host, skipped" %
+                      tap_device_name)
+            return False
+
+        current_bridge_name = \
+                self.get_bridge_for_tap_device(tap_device_name)
+        bridge_name = self.get_bridge_name(network_id)
+        if bridge_name == current_bridge_name:
+            return False
+        LOG.debug("Adding device %s to bridge %s" % (tap_device_name,
+                                                     bridge_name))
+        if current_bridge_name:
+            if self.run_cmd(['brctl', 'delif', current_bridge_name,
+                             tap_device_name]):
+                return False
+
+        self.ensure_vlan_bridge(network_id, vlan_id)
+        if self.run_cmd(['brctl', 'addif', bridge_name, tap_device_name]):
+            return False
+        LOG.debug("Done adding device %s to bridge %s" % (tap_device_name,
+                                                          bridge_name))
+        return True
+
+    def add_interface(self, network_id, 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
+        if interface_id.startswith(GATEWAY_INTERFACE_PREFIX):
+            return self.add_tap_interface(network_id, 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)
+
+    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)
+
+            LOG.debug("Deleting bridge %s" % bridge_name)
+            if self.run_cmd(['ip', 'link', 'set', bridge_name, 'down']):
+                return
+            if self.run_cmd(['brctl', 'delbr', bridge_name]):
+                return
+            LOG.debug("Done deleting bridge %s" % bridge_name)
+
+        else:
+            LOG.error("Cannot delete bridge %s, does not exist" % bridge_name)
+
+    def remove_interface(self, bridge_name, interface_name):
+        if self.device_exists(bridge_name):
+            if not self.is_device_on_bridge(interface_name):
+                return True
+            LOG.debug("Removing device %s from bridge %s" % \
+                      (interface_name, bridge_name))
+            if self.run_cmd(['brctl', 'delif', bridge_name, interface_name]):
+                return False
+            LOG.debug("Done removing device %s from bridge %s" % \
+                      (interface_name, bridge_name))
+            return True
+        else:
+            LOG.debug("Cannot remove device %s, bridge %s does not exist" % \
+                      (interface_name, bridge_name))
+            return False
+
+    def delete_vlan(self, interface):
+        if self.device_exists(interface):
+            LOG.debug("Deleting subinterface %s for vlan" % interface)
+            if self.run_cmd(['ip', 'link', 'set', interface, 'down']):
+                return
+            if self.run_cmd(['ip', 'link', 'delete', interface]):
+                return
+            LOG.debug("Done deleting subinterface %s" % interface)
+
+
+class LinuxBridgeQuantumAgent:
+
+    def __init__(self, br_name_prefix, physical_interface, polling_interval):
+        self.polling_interval = int(polling_interval)
+        self.setup_linux_bridge(br_name_prefix, physical_interface)
+
+    def setup_linux_bridge(self, br_name_prefix, physical_interface):
+        self.linux_br = LinuxBridge(br_name_prefix, physical_interface)
+
+    def process_port_binding(self, port_id, network_id, interface_id,
+                             vlan_id):
+        return self.linux_br.add_interface(network_id, vlan_id, interface_id)
+
+    def process_unplugged_interfaces(self, plugged_interfaces):
+        """
+        If there are any tap devices that are not corresponding to the
+        list of attached VIFs, then those are corresponding to recently
+        unplugged VIFs, so we need to remove those tap devices from their
+        current bridge association
+        """
+        plugged_tap_device_names = []
+        plugged_gateway_device_names = []
+        for interface in plugged_interfaces:
+            if interface.startswith(GATEWAY_INTERFACE_PREFIX):
+                """
+                The name for the gateway devices is set by the linux net
+                driver, hence we use the name as is
+                """
+                plugged_gateway_device_names.append(interface)
+            else:
+                tap_device_name = self.linux_br.get_tap_device_name(interface)
+                plugged_tap_device_names.append(tap_device_name)
+
+        LOG.debug("plugged tap device names %s" % plugged_tap_device_names)
+        for tap_device in self.linux_br.get_all_tap_devices():
+            if tap_device not in plugged_tap_device_names:
+                current_bridge_name = \
+                        self.linux_br.get_bridge_for_tap_device(tap_device)
+                if current_bridge_name:
+                    self.linux_br.remove_interface(current_bridge_name,
+                                                   tap_device)
+
+        for gw_device in self.linux_br.get_all_gateway_devices():
+            if gw_device not in plugged_gateway_device_names:
+                current_bridge_name = \
+                        self.linux_br.get_bridge_for_tap_device(gw_device)
+                if current_bridge_name:
+                    self.linux_br.remove_interface(current_bridge_name,
+                                                   gw_device)
+
+    def process_deleted_networks(self, vlan_bindings):
+        current_quantum_networks = vlan_bindings.keys()
+        current_quantum_bridge_names = []
+        for network in current_quantum_networks:
+            bridge_name = self.linux_br.get_bridge_name(network)
+            current_quantum_bridge_names.append(bridge_name)
+
+        quantum_bridges_on_this_host = self.linux_br.get_all_quantum_bridges()
+        for bridge in quantum_bridges_on_this_host:
+            if bridge not in current_quantum_bridge_names:
+                self.linux_br.delete_vlan_bridge(bridge)
+
+    def manage_networks_on_host(self, conn, old_vlan_bindings,
+                                old_port_bindings):
+        if DB_CONNECTION != 'sqlite':
+            cursor = MySQLdb.cursors.DictCursor(conn)
+        else:
+            cursor = conn.cursor()
+        cursor.execute("SELECT * FROM vlan_bindings")
+        rows = cursor.fetchall()
+        cursor.close()
+        vlan_bindings = {}
+        vlans_string = ""
+        for row in rows:
+            vlan_bindings[row['network_id']] = row
+            vlans_string = "%s %s" % (vlans_string, row)
+
+        plugged_interfaces = []
+        cursor = MySQLdb.cursors.DictCursor(conn)
+        cursor.execute("SELECT * FROM ports where state = 'ACTIVE'")
+        port_bindings = cursor.fetchall()
+        cursor.close()
+
+        ports_string = ""
+        for pb in port_bindings:
+            ports_string = "%s %s" % (ports_string, pb)
+            if pb['interface_id']:
+                vlan_id = \
+                        str(vlan_bindings[pb['network_id']]['vlan_id'])
+                if self.process_port_binding(pb['uuid'],
+                                             pb['network_id'],
+                                             pb['interface_id'],
+                                             vlan_id):
+                    cursor = MySQLdb.cursors.DictCursor(conn)
+                    sql = PORT_OPSTATUS_UPDATESQL % (pb['uuid'],
+                                                     OP_STATUS_UP)
+                    cursor.execute(sql)
+                    cursor.close()
+                plugged_interfaces.append(pb['interface_id'])
+
+        if old_port_bindings != port_bindings:
+            LOG.debug("Port-bindings: %s" % ports_string)
+
+        self.process_unplugged_interfaces(plugged_interfaces)
+
+        if old_vlan_bindings != vlan_bindings:
+            LOG.debug("VLAN-bindings: %s" % vlans_string)
+
+        self.process_deleted_networks(vlan_bindings)
+
+        conn.commit()
+        return {VLAN_BINDINGS: vlan_bindings,
+                PORT_BINDINGS: port_bindings}
+
+    def daemon_loop(self, conn):
+        old_vlan_bindings = {}
+        old_port_bindings = {}
+
+        while True:
+            bindings = self.manage_networks_on_host(conn,
+                                                    old_vlan_bindings,
+                                                    old_port_bindings)
+            old_vlan_bindings = bindings[VLAN_BINDINGS]
+            old_port_bindings = bindings[PORT_BINDINGS]
+            time.sleep(self.polling_interval)
+
+if __name__ == "__main__":
+    usagestr = "%prog [OPTIONS] <config file>"
+    parser = OptionParser(usage=usagestr)
+    parser.add_option("-v", "--verbose", dest="verbose",
+      action="store_true", default=False, help="turn on verbose logging")
+
+    options, args = parser.parse_args()
+
+    if options.verbose:
+        LOG.basicConfig(level=LOG.DEBUG)
+    else:
+        LOG.basicConfig(level=LOG.WARN)
+
+    if len(args) != 1:
+        parser.print_help()
+        sys.exit(1)
+
+    config_file = args[0]
+    config = ConfigParser.ConfigParser()
+    conn = None
+    try:
+        fh = open(config_file)
+        fh.close()
+        config.read(config_file)
+        br_name_prefix = BRIDGE_NAME_PREFIX
+        physical_interface = config.get("LINUX_BRIDGE", "physical_interface")
+        polling_interval = config.get("AGENT", "polling_interval")
+        'Establish database connection and load models'
+        DB_CONNECTION = config.get("DATABASE", "connection")
+        if DB_CONNECTION == 'sqlite':
+            LOG.info("Connecting to sqlite DB")
+            conn = sqlite3.connect(":memory:")
+            conn.row_factory = sqlite3.Row
+        else:
+            db_name = config.get("DATABASE", "name")
+            db_user = config.get("DATABASE", "user")
+            db_pass = config.get("DATABASE", "pass")
+            db_host = config.get("DATABASE", "host")
+            db_port = int(config.get("DATABASE", "port"))
+            LOG.info("Connecting to database %s on %s" % (db_name, db_host))
+            conn = MySQLdb.connect(host=db_host, user=db_user, port=db_port,
+                                   passwd=db_pass, db=db_name)
+    except Exception, e:
+        LOG.error("Unable to parse config file \"%s\": \nException%s"
+                  % (config_file, str(e)))
+        sys.exit(1)
+
+    try:
+        plugin = LinuxBridgeQuantumAgent(br_name_prefix, physical_interface,
+                                         polling_interval)
+        LOG.info("Agent initialized successfully, now running...")
+        plugin.daemon_loop(conn)
+    finally:
+        if conn:
+            conn.close()
+
+    sys.exit(0)
diff --git a/quantum/plugins/linuxbridge/common/__init__.py b/quantum/plugins/linuxbridge/common/__init__.py
new file mode 100644 (file)
index 0000000..f77db6e
--- /dev/null
@@ -0,0 +1,20 @@
+"""
+# 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.
+#
+"""
diff --git a/quantum/plugins/linuxbridge/common/configparser.py b/quantum/plugins/linuxbridge/common/configparser.py
new file mode 100644 (file)
index 0000000..2b170a2
--- /dev/null
@@ -0,0 +1,34 @@
+"""
+# 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.
+#
+"""
+
+from configobj import ConfigObj
+
+
+class ConfigParser(ConfigObj):
+    """Config Parser based on the ConfigObj module"""
+
+    def __init__(self, filename):
+        super(ConfigParser, self).__init__(filename, raise_errors=True,
+                                                file_error=True)
+
+    def dummy(self, section, key):
+        """Dummy function to return the same key, used in walk"""
+        return section[key]
diff --git a/quantum/plugins/linuxbridge/common/constants.py b/quantum/plugins/linuxbridge/common/constants.py
new file mode 100644 (file)
index 0000000..0aff3ff
--- /dev/null
@@ -0,0 +1,65 @@
+"""
+# 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.
+#
+"""
+
+PORT_STATE = 'port-state'
+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
new file mode 100644 (file)
index 0000000..40dafd3
--- /dev/null
@@ -0,0 +1,72 @@
+# 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.")
+
+
+try:
+    _("test")
+except NameError:
+
+    def _(a_string):
+        """
+        Default implementation of the gettext string
+        translation function: no translation
+        """
+        return a_string
+except TypeError:
+    # during doctesting, _ might mean something else
+    pass
diff --git a/quantum/plugins/linuxbridge/common/utils.py b/quantum/plugins/linuxbridge/common/utils.py
new file mode 100644 (file)
index 0000000..569eaa3
--- /dev/null
@@ -0,0 +1,50 @@
+"""
+# 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.
+#
+"""
+
+import logging
+
+from quantum.api.api_common import OperationalStatus
+from quantum.plugins.linuxbridge.common import constants as const
+
+LOG = logging.getLogger(__name__)
+
+
+def make_net_dict(net_id, net_name, ports, op_status):
+    """Helper funciton"""
+    res = {const.NET_ID: net_id, const.NET_NAME: net_name, const.NET_OP_STATUS:
+          op_status}
+    if ports:
+        res[const.NET_PORTS] = ports
+    return res
+
+
+def make_port_dict(port):
+    """Helper funciton"""
+    if port[const.PORTSTATE] == const.PORT_UP:
+        op_status = port[const.OPSTATUS]
+    else:
+        op_status = OperationalStatus.DOWN
+
+    return {const.PORT_ID: str(port[const.UUID]),
+            const.PORT_STATE: port[const.PORTSTATE],
+            const.PORT_OP_STATUS: op_status,
+            const.NET_ID: port[const.NETWORKID],
+            const.ATTACHMENT: port[const.INTERFACEID]}
diff --git a/quantum/plugins/linuxbridge/db/__init__.py b/quantum/plugins/linuxbridge/db/__init__.py
new file mode 100644 (file)
index 0000000..33daf1f
--- /dev/null
@@ -0,0 +1,18 @@
+# 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.
+#
diff --git a/quantum/plugins/linuxbridge/db/l2network_db.py b/quantum/plugins/linuxbridge/db/l2network_db.py
new file mode 100644 (file)
index 0000000..c6d64a4
--- /dev/null
@@ -0,0 +1,255 @@
+# 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.
+
+from sqlalchemy import func
+from sqlalchemy.orm import exc
+
+from quantum.common import exceptions as q_exc
+from quantum.plugins.linuxbridge import plugin_configuration as conf
+from quantum.plugins.linuxbridge.common import exceptions as c_exc
+from quantum.plugins.linuxbridge.db import l2network_models
+
+import logging
+
+import quantum.db.api as db
+
+
+LOG = logging.getLogger(__name__)
+
+
+def initialize():
+    'Establish database connection and load models'
+    if conf.DB_CONNECTION == 'sqlite':
+        options = {"sql_connection": "sqlite://"}
+    else:
+        options = {"sql_connection": "mysql://%s:%s@%s:%s/%s" % (conf.DB_USER,
+                                                                 conf.DB_PASS,
+                                                                 conf.DB_HOST,
+                                                                 conf.DB_PORT,
+                                                                 conf.DB_NAME)}
+
+    db.configure_db(options)
+    create_vlanids()
+
+
+def create_vlanids():
+    """Prepopulates the vlan_bindings table"""
+    LOG.debug("create_vlanids() called")
+    session = db.get_session()
+    start = int(conf.VLAN_START)
+    end = int(conf.VLAN_END)
+    try:
+        vlanid = session.query(l2network_models.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(l2network_models.VlanID.vlan_id)).
+                    one()[0])
+        current_end = \
+                int(session.query(func.max(l2network_models.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 = l2network_models.VlanID(start)
+            session.add(vlanid)
+            start += 1
+        session.flush()
+    return
+
+
+def get_all_vlanids():
+    """Gets all the vlanids"""
+    LOG.debug("get_all_vlanids() called")
+    session = db.get_session()
+    try:
+        vlanids = session.query(l2network_models.VlanID).\
+          all()
+        return vlanids
+    except exc.NoResultFound:
+        return []
+
+
+def is_vlanid_used(vlan_id):
+    """Checks if a vlanid is in use"""
+    LOG.debug("is_vlanid_used() called")
+    session = db.get_session()
+    try:
+        vlanid = session.query(l2network_models.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):
+    """Sets the vlanid state to be unused"""
+    LOG.debug("release_vlanid() called")
+    session = db.get_session()
+    try:
+        vlanid = session.query(l2network_models.VlanID).\
+         filter_by(vlan_id=vlan_id).\
+          one()
+        vlanid["vlan_used"] = False
+        session.merge(vlanid)
+        session.flush()
+        return vlanid["vlan_used"]
+    except exc.NoResultFound:
+        raise c_exc.VlanIDNotFound(vlan_id=vlan_id)
+    return
+
+
+def delete_vlanid(vlan_id):
+    """Deletes a vlanid entry from db"""
+    LOG.debug("delete_vlanid() called")
+    session = db.get_session()
+    try:
+        vlanid = session.query(l2network_models.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():
+    """Reserves the first unused vlanid"""
+    LOG.debug("reserve_vlanid() called")
+    session = db.get_session()
+    try:
+        rvlan = session.query(l2network_models.VlanID).\
+          first()
+        if not rvlan:
+            create_vlanids()
+
+        rvlan = session.query(l2network_models.VlanID).\
+         filter_by(vlan_used=False).\
+          first()
+        if not rvlan:
+            raise c_exc.VlanIDNotAvailable()
+
+        rvlanid = session.query(l2network_models.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 get_all_vlanids_used():
+    """Gets all the vlanids used"""
+    LOG.debug("get_all_vlanids() called")
+    session = db.get_session()
+    try:
+        vlanids = session.query(l2network_models.VlanID).\
+        filter_by(vlan_used=True).\
+          all()
+        return vlanids
+    except exc.NoResultFound:
+        return []
+
+
+def get_all_vlan_bindings():
+    """Lists all the vlan to network associations"""
+    LOG.debug("get_all_vlan_bindings() called")
+    session = db.get_session()
+    try:
+        bindings = session.query(l2network_models.VlanBinding).\
+          all()
+        return bindings
+    except exc.NoResultFound:
+        return []
+
+
+def get_vlan_binding(netid):
+    """Lists the vlan given a network_id"""
+    LOG.debug("get_vlan_binding() called")
+    session = db.get_session()
+    try:
+        binding = session.query(l2network_models.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):
+    """Adds a vlan to network association"""
+    LOG.debug("add_vlan_binding() called")
+    session = db.get_session()
+    try:
+        binding = session.query(l2network_models.VlanBinding).\
+          filter_by(vlan_id=vlanid).\
+          one()
+        raise c_exc.NetworkVlanBindingAlreadyExists(vlan_id=vlanid,
+                                                    network_id=netid)
+    except exc.NoResultFound:
+        binding = l2network_models.VlanBinding(vlanid, netid)
+        session.add(binding)
+        session.flush()
+        return binding
+
+
+def remove_vlan_binding(netid):
+    """Removes a vlan to network association"""
+    LOG.debug("remove_vlan_binding() called")
+    session = db.get_session()
+    try:
+        binding = session.query(l2network_models.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):
+    """Updates a vlan to network association"""
+    LOG.debug("update_vlan_binding() called")
+    session = db.get_session()
+    try:
+        binding = session.query(l2network_models.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)
diff --git a/quantum/plugins/linuxbridge/db/l2network_models.py b/quantum/plugins/linuxbridge/db/l2network_models.py
new file mode 100644 (file)
index 0000000..b5613b3
--- /dev/null
@@ -0,0 +1,57 @@
+# 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 uuid
+
+from sqlalchemy import Column, Integer, String, Boolean
+from sqlalchemy.orm import relation, object_mapper
+
+from quantum.db.models import BASE
+from quantum.db.models import QuantumBase
+from quantum.db import models
+
+
+class VlanID(BASE, QuantumBase):
+    """Represents a vlan_id usage"""
+    __tablename__ = 'vlan_ids'
+
+    vlan_id = Column(Integer, primary_key=True)
+    vlan_used = Column(Boolean)
+
+    def __init__(self, vlan_id):
+        self.vlan_id = vlan_id
+        self.vlan_used = False
+
+    def __repr__(self):
+        return "<VlanID(%d,%s)>" % \
+          (self.vlan_id, self.vlan_used)
+
+
+class VlanBinding(BASE, QuantumBase):
+    """Represents a binding of vlan_id to network_id"""
+    __tablename__ = 'vlan_bindings'
+
+    vlan_id = Column(Integer, primary_key=True)
+    network_id = Column(String(255), nullable=False)
+
+    def __init__(self, vlan_id, network_id):
+        self.vlan_id = vlan_id
+        self.network_id = network_id
+
+    def __repr__(self):
+        return "<VlanBinding(%d,%s,%s)>" % \
+          (self.vlan_id, self.network_id)
diff --git a/quantum/plugins/linuxbridge/nova/linux_net_linux_bridge.py b/quantum/plugins/linuxbridge/nova/linux_net_linux_bridge.py
new file mode 100755 (executable)
index 0000000..1c8e6ae
--- /dev/null
@@ -0,0 +1,117 @@
+# 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.
+#
+# Extends the linux_net.py kvm/linux network driver in Nova,
+# borrows structure and code
+# @author: Sumit Naiksatam, Cisco Systems, Inc.
+#
+
+
+"""Extends the linux_net driver when using the Linux Bridge plugin with
+QuantumManager"""
+
+
+from nova import exception
+from nova import log as logging
+from nova import utils
+
+from nova.network.linux_net import *
+
+
+LOG = logging.getLogger(__name__)
+
+
+BRDIGE_NAME_PREFIX = "brq"
+GATEWAY_INTERFACE_PREFIX = "gw-"
+
+
+def _device_exists(device):
+    """Check if ethernet device exists."""
+    (_out, err) = utils.execute('ip', 'link', 'show', 'dev', device,
+                           check_exit_code=False)
+    return not err
+
+
+# plugs interfaces using Linux Bridge when using QuantumManager
+class QuantumLibvirtLinuxBridgeDriver(LinuxNetInterfaceDriver):
+
+    def plug(self, network, mac_address, gateway=True):
+        LOG.debug(_("inside plug()"))
+        dev = self.get_dev(network)
+        bridge = self.get_bridge(network)
+        if not gateway:
+            # If we weren't instructed to act as a gateway then add the
+            # appropriate flows to block all non-dhcp traffic.
+            # .. and make sure iptbles won't forward it as well.
+            iptables_manager.ipv4['filter'].add_rule('FORWARD',
+                    '--in-interface %s -j DROP' % bridge)
+            iptables_manager.ipv4['filter'].add_rule('FORWARD',
+                    '--out-interface %s -j DROP' % bridge)
+            return bridge
+        else:
+            iptables_manager.ipv4['filter'].add_rule('FORWARD',
+                    '--in-interface %s -j ACCEPT' % bridge)
+            iptables_manager.ipv4['filter'].add_rule('FORWARD',
+                    '--out-interface %s -j ACCEPT' % bridge)
+
+        if not _device_exists(dev):
+            try:
+                # First, try with 'ip'
+                utils.execute('ip', 'tuntap', 'add', dev, 'mode', 'tap',
+                          run_as_root=True)
+            except exception.ProcessExecutionError:
+                # Second option: tunctl
+                utils.execute('tunctl', '-b', '-t', dev, run_as_root=True)
+            utils.execute('ip', 'link', 'set', dev, "address", mac_address,
+                          run_as_root=True)
+            utils.execute('ip', 'link', 'set', dev, 'up', run_as_root=True)
+
+        if not _device_exists(bridge):
+            LOG.debug(_("Starting bridge %s "), bridge)
+            utils.execute('brctl', 'addbr', bridge, run_as_root=True)
+            utils.execute('brctl', 'setfd', bridge, str(0), run_as_root=True)
+            utils.execute('brctl', 'stp', bridge, 'off', run_as_root=True)
+            utils.execute('ip', 'link', 'set', bridge, "address", mac_address,
+                          run_as_root=True)
+            utils.execute('ip', 'link', 'set', bridge, 'up', run_as_root=True)
+            LOG.debug(_("Done starting bridge %s"), bridge)
+
+        full_ip = '%s/%s' % (network['dhcp_server'],
+                             network['cidr'].rpartition('/')[2])
+        utils.execute('ip', 'address', 'add', full_ip, 'dev', bridge,
+                run_as_root=True)
+
+        return dev
+
+    def unplug(self, network):
+        LOG.debug(_("inside unplug()"))
+        dev = self.get_dev(network)
+        try:
+            utils.execute('ip', 'link', 'delete', dev, run_as_root=True)
+        except exception.ProcessExecutionError:
+            LOG.warning(_("Failed while unplugging gateway interface '%s'"),
+                        dev)
+            raise
+        LOG.debug(_("Unplugged gateway interface '%s'"), dev)
+        return dev
+
+    def get_dev(self, network):
+        dev = GATEWAY_INTERFACE_PREFIX + str(network['uuid'][0:11])
+        return dev
+
+    def get_bridge(self, network):
+        bridge = BRDIGE_NAME_PREFIX + str(network['uuid'][0:11])
+        return bridge
diff --git a/quantum/plugins/linuxbridge/nova/vif_linuxbridge_quantum.py b/quantum/plugins/linuxbridge/nova/vif_linuxbridge_quantum.py
new file mode 100644 (file)
index 0000000..1e2a40f
--- /dev/null
@@ -0,0 +1,73 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright (C) 2012 Midokura KK
+# Copyright (C) 2012 Nicira, Inc
+# Copyright (C) 2012 Cisco Systems, Inc
+# Copyright 2012 OpenStack LLC.
+# 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.
+
+"""
+VIF driver for libvirt when QuantumManager is configured with Linux Bridge
+plugin
+"""
+
+from nova import flags
+from nova import log as logging
+from nova.network import linux_net
+from nova.virt import netutils
+from nova import utils
+from nova.virt.vif import VIFDriver
+from nova import exception
+
+LOG = logging.getLogger('nova.virt.libvirt.vif_linuxbridge_quantum')
+
+FLAGS = flags.FLAGS
+
+
+class QuantumLibvirtLinuxBridgeDriver(VIFDriver):
+    """VIF driver for Linux Bridge."""
+
+    def get_dev_name(_self, iface_id):
+        return "tap" + iface_id[0:11]
+
+    def plug(self, instance, network, mapping):
+        iface_id = mapping['vif_uuid']
+        dev = self.get_dev_name(iface_id)
+        if not linux_net._device_exists(dev):
+            try:
+                # First, try with 'ip'
+                utils.execute('ip', 'tuntap', 'add', dev, 'mode', 'tap',
+                          run_as_root=True)
+            except exception.ProcessExecutionError:
+                # Second option: tunctl
+                utils.execute('tunctl', '-b', '-t', dev, run_as_root=True)
+            utils.execute('ip', 'link', 'set', dev, 'up', run_as_root=True)
+
+        result = {
+            'script': '',
+            'name': dev,
+            'mac_address': mapping['mac']}
+        return result
+
+    def unplug(self, instance, network, mapping):
+        """Unplug the VIF from the network by deleting the port from
+        the bridge."""
+        dev = self.get_dev_name(mapping['vif_uuid'])
+        try:
+            utils.execute('ip', 'link', 'delete', dev, run_as_root=True)
+        except exception.ProcessExecutionError:
+            LOG.warning(_("Failed while unplugging vif of instance '%s'"),
+                        instance['name'])
+            raise
diff --git a/quantum/plugins/linuxbridge/plugin_configuration.py b/quantum/plugins/linuxbridge/plugin_configuration.py
new file mode 100644 (file)
index 0000000..cc6cdd6
--- /dev/null
@@ -0,0 +1,48 @@
+"""
+# 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.
+"""
+
+import os
+
+from quantum.common.config import find_config_file
+from quantum.plugins.linuxbridge.common import configparser as confp
+
+
+CONF_FILE = find_config_file({'plugin': 'linuxbridge'}, None,
+                             "linuxbridge_conf.ini")
+CONF_PARSER_OBJ = confp.ConfigParser(CONF_FILE)
+
+
+"""
+Reading the conf for the linuxbridge_plugin
+"""
+SECTION_CONF = CONF_PARSER_OBJ['VLANS']
+VLAN_START = SECTION_CONF['vlan_start']
+VLAN_END = SECTION_CONF['vlan_end']
+
+
+SECTION_CONF = CONF_PARSER_OBJ['DATABASE']
+DB_CONNECTION = SECTION_CONF['connection']
+if DB_CONNECTION != 'sqlite':
+    DB_NAME = SECTION_CONF['name']
+    DB_USER = SECTION_CONF['user']
+    DB_PASS = SECTION_CONF['pass']
+    DB_HOST = SECTION_CONF['host']
+    DB_PORT = SECTION_CONF['port']
diff --git a/quantum/plugins/linuxbridge/run_tests.py b/quantum/plugins/linuxbridge/run_tests.py
new file mode 100644 (file)
index 0000000..8059226
--- /dev/null
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 OpenStack, LLC
+# 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.
+
+
+"""Unittest runner for quantum Linux Bridge plugin
+
+This file should be run from the top dir in the quantum directory
+
+To run all tests::
+    PLUGIN_DIR=quantum/plugins/linuxbridge ./run_tests.sh
+"""
+
+import gettext
+import logging
+import os
+import unittest
+import sys
+
+from nose import config
+from nose import core
+
+sys.path.append(os.getcwd())
+sys.path.append(os.path.dirname(__file__))
+
+
+from quantum.api.api_common import OperationalStatus
+from quantum.common.test_lib import run_tests, test_config
+from quantum.plugins.linuxbridge.LinuxBridgePlugin import LinuxBridgePlugin
+import quantum.tests.unit
+from tests.unit.test_database import L2networkDBTest
+
+
+if __name__ == '__main__':
+    exit_status = False
+
+    # if a single test case was specified,
+    # we should only invoked the tests once
+    invoke_once = len(sys.argv) > 1
+
+    test_config['plugin_name'] = "LinuxBridgePlugin.LinuxBridgePlugin"
+    test_config['default_net_op_status'] = OperationalStatus.UP
+    test_config['default_port_op_status'] = OperationalStatus.DOWN
+
+    cwd = os.getcwd()
+    c = config.Config(stream=sys.stdout,
+                      env=os.environ,
+                      verbosity=3,
+                      includeExe=True,
+                      traverseNamespace=True,
+                      plugins=core.DefaultPluginManager())
+    c.configureWhere(quantum.tests.unit.__path__)
+    exit_status = run_tests(c)
+
+    if invoke_once:
+        sys.exit(0)
+
+    os.chdir(cwd)
+
+    working_dir = os.path.abspath("quantum/plugins/linuxbridge")
+    c = config.Config(stream=sys.stdout,
+                      env=os.environ,
+                      verbosity=3,
+                      workingDir=working_dir)
+    exit_status = exit_status or run_tests(c)
+
+    sys.exit(exit_status)
diff --git a/quantum/plugins/linuxbridge/tests/__init__.py b/quantum/plugins/linuxbridge/tests/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/quantum/plugins/linuxbridge/tests/unit/__init__.py b/quantum/plugins/linuxbridge/tests/unit/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/quantum/plugins/linuxbridge/tests/unit/_test_linuxbridgeAgent.py b/quantum/plugins/linuxbridge/tests/unit/_test_linuxbridgeAgent.py
new file mode 100644 (file)
index 0000000..9452189
--- /dev/null
@@ -0,0 +1,451 @@
+# 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: Shweta Padubidri, Cisco Systems, Inc.
+#
+
+import ConfigParser
+import logging as LOG
+import unittest
+import sys
+import os
+import signal
+from subprocess import *
+
+import quantum.plugins.linuxbridge.agent.linuxbridge_quantum_agent\
+                                                     as linux_agent
+from quantum.plugins.linuxbridge.common import constants as lconst
+from quantum.plugins.linuxbridge import LinuxBridgePlugin
+from quantum.plugins.linuxbridge.db import l2network_db as cdb
+import quantum.db.api as db
+
+
+LOG.getLogger(__name__)
+
+
+class LinuxBridgeAgentTest(unittest.TestCase):
+
+    def test_add_gateway_interface(
+                  self, tenant_id="test_tenant", network_name="test_network",
+                  interface_id='fe701ddf-26a2-42ea-b9e6-7313d1c522cc',
+                  mac_address='fe:16:3e:51:60:dd'):
+
+        LOG.debug("test_tap_gateway_interface - START")
+        new_network =\
+               self._linuxbridge_plugin.create_network(tenant_id, network_name)
+        new_port = self._linuxbridge_plugin.create_port(
+                         tenant_id, new_network[lconst.NET_ID], lconst.PORT_UP)
+        self._linuxbridge_plugin.plug_interface(
+                         tenant_id, new_network[lconst.NET_ID],
+                         new_port[lconst.PORT_ID], interface_id)
+        bridge_name = self.br_name_prefix + new_network[lconst.NET_ID][0:11]
+        self.create_bridge(bridge_name)
+        device_name = self.gw_name_prefix + new_network[lconst.NET_ID][0:11]
+        self.create_device(device_name, mac_address)
+
+        vlan_bind = cdb.get_vlan_binding(new_network[lconst.NET_ID])
+        vlan_id = vlan_bind[lconst.VLANID]
+
+        self._linuxbridge_quantum_agent.process_port_binding(
+                         new_port[lconst.PORT_ID], new_network[lconst.NET_ID],
+                         device_name, str(vlan_id))
+        list_interface = self._linuxbridge_quantum_agent.linux_br.\
+                               get_interfaces_on_bridge(bridge_name)
+
+        self.assertTrue(device_name in list_interface)
+        for interface in list_interface:
+            self._linuxbridge_quantum_agent.linux_br.remove_interface(
+                                               bridge_name, interface)
+            self.delete_device(interface)
+        self.delete_bridge(bridge_name)
+        self.tearDownUnplugInterface(tenant_id, new_network[lconst.NET_ID],
+                                     new_port[lconst.PORT_ID])
+
+        LOG.debug("test_add_gateway_interface - END")
+
+    def test_add_tap_interface(
+                  self, tenant_id="test_tenant", network_name="test_network",
+                  interface_id='fe701ddf-26a2-42ea-b9e6-7313d1c522cc',
+                  mac_address='fe:16:3e:51:60:dd'):
+
+        LOG.debug("test_add_tap_interface - START")
+        new_network =\
+               self._linuxbridge_plugin.create_network(tenant_id, network_name)
+        new_port = self._linuxbridge_plugin.create_port(
+                         tenant_id, new_network[lconst.NET_ID], lconst.PORT_UP)
+        self._linuxbridge_plugin.plug_interface(
+                         tenant_id, new_network[lconst.NET_ID],
+                         new_port[lconst.PORT_ID], interface_id)
+        bridge_name = self.br_name_prefix + new_network[lconst.NET_ID][0:11]
+        self.create_bridge(bridge_name)
+        device_name = self.tap_name_prefix + interface_id[0:11]
+        self.create_device(device_name, mac_address)
+
+        vlan_bind = cdb.get_vlan_binding(new_network[lconst.NET_ID])
+        vlan_id = vlan_bind[lconst.VLANID]
+
+        self._linuxbridge_quantum_agent.process_port_binding(
+                         new_port[lconst.PORT_ID], new_network[lconst.NET_ID],
+                         interface_id, str(vlan_id))
+        list_interface = self._linuxbridge_quantum_agent.linux_br.\
+                                        get_interfaces_on_bridge(bridge_name)
+
+        self.assertTrue(device_name in list_interface)
+        for interface in list_interface:
+            self._linuxbridge_quantum_agent.linux_br.remove_interface(
+                                               bridge_name, interface)
+            self.delete_device(interface)
+        self.delete_bridge(bridge_name)
+        self.tearDownUnplugInterface(tenant_id, new_network[lconst.NET_ID],
+                                     new_port[lconst.PORT_ID])
+
+        LOG.debug("test_add_tap_interface -END")
+
+    def test_remove_interface(
+                  self, tenant_id="test_tenant", network_name="test_network",
+                  interface_id='fe701ddf-26a2-42ea-b9e6-7313d1c522cc',
+                  mac_address='fe:16:3e:51:60:dd'):
+
+        LOG.debug("test_remove_interface - START")
+        new_network =\
+               self._linuxbridge_plugin.create_network(tenant_id, network_name)
+        new_port = self._linuxbridge_plugin.create_port(
+                         tenant_id, new_network[lconst.NET_ID], lconst.PORT_UP)
+        self._linuxbridge_plugin.plug_interface(
+                         tenant_id, new_network[lconst.NET_ID],
+                         new_port[lconst.PORT_ID], interface_id)
+        bridge_name = self.br_name_prefix + new_network[lconst.NET_ID][0:11]
+        self.create_bridge(bridge_name)
+        device_name = self.tap_name_prefix + interface_id[0:11]
+        self.create_device(device_name, mac_address)
+
+        vlan_bind = cdb.get_vlan_binding(new_network[lconst.NET_ID])
+        vlan_id = vlan_bind[lconst.VLANID]
+
+        self._linuxbridge_quantum_agent.process_port_binding(
+                         new_port[lconst.PORT_ID], new_network[lconst.NET_ID],
+                         interface_id, str(vlan_id))
+        list_interface = self._linuxbridge_quantum_agent.linux_br.\
+                              get_interfaces_on_bridge(bridge_name)
+
+        self._linuxbridge_quantum_agent.linux_br.remove_interface(bridge_name,
+                                                            device_name)
+        list_interface = self._linuxbridge_quantum_agent.linux_br.\
+                              get_interfaces_on_bridge(bridge_name)
+        self.assertFalse(device_name in list_interface)
+        for interface in list_interface:
+            self._linuxbridge_quantum_agent.linux_br.remove_interface(
+                                               bridge_name, interface)
+            self.delete_device(interface)
+        self.delete_device(device_name)
+        self.delete_bridge(bridge_name)
+        self.tearDownUnplugInterface(tenant_id, new_network[lconst.NET_ID],
+                                     new_port[lconst.PORT_ID])
+
+        LOG.debug("test_remove_interface -END")
+
+    def test_ensure_vlan_bridge(
+                  self, tenant_id="test_tenant", network_name="test_network",
+                  interface_id='fe701ddf-26a2-42ea-b9e6-7313d1c522cc'):
+
+        LOG.debug("test_ensure_vlan_bridge - START")
+        new_network =\
+               self._linuxbridge_plugin.create_network(tenant_id, network_name)
+        new_port = self._linuxbridge_plugin.create_port(
+                         tenant_id, new_network[lconst.NET_ID], lconst.PORT_UP)
+        self._linuxbridge_plugin.plug_interface(
+                         tenant_id, new_network[lconst.NET_ID],
+                         new_port[lconst.PORT_ID], interface_id)
+        bridge_name = self.br_name_prefix + new_network[lconst.NET_ID][0:11]
+        vlan_bind = cdb.get_vlan_binding(new_network[lconst.NET_ID])
+        vlan_id = vlan_bind[lconst.VLANID]
+        vlan_subinterface = self.physical_interface + '.' + str(vlan_id)
+
+        self._linuxbridge_quantum_agent.linux_br.ensure_vlan_bridge(
+                         new_network[lconst.NET_ID], str(vlan_id))
+        list_quantum_bridges = self._linuxbridge_quantum_agent.linux_br.\
+                                    get_all_quantum_bridges()
+        self.assertTrue(bridge_name in list_quantum_bridges)
+        list_interface = self._linuxbridge_quantum_agent.linux_br.\
+                              get_interfaces_on_bridge(bridge_name)
+        self.assertTrue(vlan_subinterface in list_interface)
+
+        for interface in list_interface:
+            self._linuxbridge_quantum_agent.linux_br.remove_interface(
+                                               bridge_name, interface)
+            self.delete_device(interface)
+        self.delete_bridge(bridge_name)
+        self.tearDownUnplugInterface(tenant_id, new_network[lconst.NET_ID],
+                                     new_port[lconst.PORT_ID])
+
+        LOG.debug("test_ensure_vlan_bridge -END")
+
+    def test_delete_vlan_bridge(
+                  self, tenant_id="test_tenant", network_name="test_network",
+                  interface_id='fe701ddf-26a2-42ea-b9e6-7313d1c522cc'):
+
+        LOG.debug("test_delete_vlan_bridge - START")
+        new_network =\
+               self._linuxbridge_plugin.create_network(tenant_id, network_name)
+        new_port = self._linuxbridge_plugin.create_port(
+                         tenant_id, new_network[lconst.NET_ID], lconst.PORT_UP)
+        self._linuxbridge_plugin.plug_interface(
+                         tenant_id, new_network[lconst.NET_ID],
+                         new_port[lconst.PORT_ID], interface_id)
+        bridge_name = self.br_name_prefix + new_network[lconst.NET_ID][0:11]
+        vlan_bind = cdb.get_vlan_binding(new_network[lconst.NET_ID])
+        vlan_id = vlan_bind[lconst.VLANID]
+        vlan_subinterface = self.physical_interface + '.' + str(vlan_id)
+
+        self._linuxbridge_quantum_agent.linux_br.ensure_vlan_bridge(
+                         new_network[lconst.NET_ID], str(vlan_id))
+        self._linuxbridge_quantum_agent.linux_br.delete_vlan_bridge(
+                                                 bridge_name)
+
+        self.assertEquals(self.device_exists(vlan_subinterface), False)
+        self.assertEquals(self.device_exists(bridge_name), False)
+        self.tearDownUnplugInterface(tenant_id, new_network[lconst.NET_ID],
+                                     new_port[lconst.PORT_ID])
+
+        LOG.debug("test_delete_vlan_bridge - END")
+
+    def test_process_deleted_networks(
+                  self, tenant_id="test_tenant", network_name="test_network",
+                  interface_id='fe701ddf-26a2-42ea-b9e6-7313d1c522cc'):
+
+        LOG.debug("test_delete_vlan_bridge - START")
+        new_network =\
+               self._linuxbridge_plugin.create_network(tenant_id, network_name)
+        new_port = self._linuxbridge_plugin.create_port(
+                         tenant_id, new_network[lconst.NET_ID], lconst.PORT_UP)
+        self._linuxbridge_plugin.plug_interface(
+                         tenant_id, new_network[lconst.NET_ID],
+                         new_port[lconst.PORT_ID], interface_id)
+        bridge_name = self.br_name_prefix + new_network[lconst.NET_ID][0:11]
+        vlan_bindings = {}
+        vlan_bindings[new_network[lconst.NET_ID]] =\
+                       cdb.get_vlan_binding(new_network[lconst.NET_ID])
+        vlan_id = vlan_bindings[new_network[lconst.NET_ID]][lconst.VLANID]
+        vlan_subinterface = self.physical_interface + '.' + str(vlan_id)
+
+        self._linuxbridge_quantum_agent.linux_br.ensure_vlan_bridge(
+                         new_network[lconst.NET_ID], str(vlan_id))
+        self.tearDownUnplugInterface(tenant_id, new_network[lconst.NET_ID],
+                                     new_port[lconst.PORT_ID])
+        vlan_bindings = {}
+        self._linuxbridge_quantum_agent.process_deleted_networks(vlan_bindings)
+
+        self.assertEquals(self.device_exists(vlan_subinterface), False)
+        self.assertEquals(self.device_exists(bridge_name), False)
+        LOG.debug("test_delete_vlan_bridge - END")
+
+    def test_process_unplugged_tap_interface(
+                  self, tenant_id="test_tenant", network_name="test_network",
+                  interface_id='fe701ddf-26a2-42ea-b9e6-7313d1c522cc',
+                  mac_address='fe:16:3e:51:60:dd'):
+
+        LOG.debug("test_process_unplugged_tap_interface - START")
+        new_network =\
+               self._linuxbridge_plugin.create_network(tenant_id, network_name)
+        new_port = self._linuxbridge_plugin.create_port(
+                         tenant_id, new_network[lconst.NET_ID], lconst.PORT_UP)
+        self._linuxbridge_plugin.plug_interface(
+                         tenant_id, new_network[lconst.NET_ID],
+                         new_port[lconst.PORT_ID], interface_id)
+        bridge_name = self.br_name_prefix + new_network[lconst.NET_ID][0:11]
+        self.create_bridge(bridge_name)
+        device_name = self.tap_name_prefix + interface_id[0:11]
+        self.create_device(device_name, mac_address)
+
+        vlan_bind = cdb.get_vlan_binding(new_network[lconst.NET_ID])
+        vlan_id = vlan_bind[lconst.VLANID]
+
+        self._linuxbridge_quantum_agent.process_port_binding(
+                         new_port[lconst.PORT_ID], new_network[lconst.NET_ID],
+                         interface_id, str(vlan_id))
+        list_interface = self._linuxbridge_quantum_agent.linux_br.\
+                              get_interfaces_on_bridge(bridge_name)
+        self._linuxbridge_plugin.unplug_interface(tenant_id,
+                                                  new_network[lconst.NET_ID],
+                                                  new_port[lconst.PORT_ID])
+        plugged_interface = []
+        self._linuxbridge_quantum_agent.process_unplugged_interfaces(
+                                                          plugged_interface)
+        list_interface = self._linuxbridge_quantum_agent.linux_br.\
+                              get_interfaces_on_bridge(bridge_name)
+        self.assertFalse(device_name in list_interface)
+        for interface in list_interface:
+            self._linuxbridge_quantum_agent.linux_br.remove_interface(
+                                               bridge_name, interface)
+            self.delete_device(interface)
+        self.delete_device(device_name)
+        self.delete_bridge(bridge_name)
+        self.tearDownNetworkPort(tenant_id, new_network[lconst.NET_ID],
+                                     new_port[lconst.PORT_ID])
+
+        LOG.debug("test_test_process_unplugged_tap_interface -END")
+
+    def test_process_unplugged_gw_interface(
+                  self, tenant_id="test_tenant", network_name="test_network",
+                  interface_id='fe701ddf-26a2-42ea-b9e6-7313d1c522cc',
+                  mac_address='fe:16:3e:51:60:dd'):
+
+        LOG.debug("test_process_unplugged_gw_interface - START")
+        new_network =\
+               self._linuxbridge_plugin.create_network(tenant_id, network_name)
+        new_port = self._linuxbridge_plugin.create_port(
+                         tenant_id, new_network[lconst.NET_ID], lconst.PORT_UP)
+        self._linuxbridge_plugin.plug_interface(
+                         tenant_id, new_network[lconst.NET_ID],
+                         new_port[lconst.PORT_ID], interface_id)
+        bridge_name = self.br_name_prefix + new_network[lconst.NET_ID][0:11]
+        self.create_bridge(bridge_name)
+        device_name = self.gw_name_prefix + new_network[lconst.NET_ID][0:11]
+        self.create_device(device_name, mac_address)
+
+        vlan_bind = cdb.get_vlan_binding(new_network[lconst.NET_ID])
+        vlan_id = vlan_bind[lconst.VLANID]
+
+        self._linuxbridge_quantum_agent.process_port_binding(
+                         new_port[lconst.PORT_ID], new_network[lconst.NET_ID],
+                         interface_id, str(vlan_id))
+        list_interface = self._linuxbridge_quantum_agent.linux_br.\
+                              get_interfaces_on_bridge(bridge_name)
+        self._linuxbridge_plugin.unplug_interface(tenant_id,
+                                                  new_network[lconst.NET_ID],
+                                                  new_port[lconst.PORT_ID])
+        plugged_interface = []
+        self._linuxbridge_quantum_agent.process_unplugged_interfaces(
+                                                          plugged_interface)
+        list_interface = self._linuxbridge_quantum_agent.linux_br.\
+                              get_interfaces_on_bridge(bridge_name)
+        self.assertFalse(device_name in list_interface)
+        for interface in list_interface:
+            self._linuxbridge_quantum_agent.linux_br.remove_interface(
+                                               bridge_name, interface)
+            self.delete_device(interface)
+        self.delete_device(device_name)
+        self.delete_bridge(bridge_name)
+        self.tearDownNetworkPort(tenant_id, new_network[lconst.NET_ID],
+                                     new_port[lconst.PORT_ID])
+
+        LOG.debug("test_test_process_unplugged_gw_interface -END")
+
+    def create_bridge(self, bridge_name):
+        """
+        Create a bridge
+        """
+        self.run_cmd(['brctl', 'addbr', bridge_name])
+        self.run_cmd(['brctl', 'setfd', bridge_name, str(0)])
+        self.run_cmd(['brctl', 'stp', bridge_name, 'off'])
+        self.run_cmd(['ip', 'link', 'set', bridge_name, 'up'])
+
+    def delete_bridge(self, bridge_name):
+        """
+        Delete a bridge
+        """
+        self.run_cmd(['ip', 'link', 'set', bridge_name, 'down'])
+        self.run_cmd(['brctl', 'delbr', bridge_name])
+
+    def create_device(self, dev, mac_address):
+        self.run_cmd(['ip', 'tuntap', 'add', dev, 'mode', 'tap'])
+        self.run_cmd(['ip', 'link', 'set', dev, "address", mac_address])
+        self.run_cmd(['ip', 'link', 'set', dev, 'up'])
+
+    def delete_device(self, dev):
+        self.run_cmd(['ip', 'link', 'delete', dev])
+
+    def setUp(self):
+        """
+        Set up function
+        """
+        self.tenant_id = "test_tenant"
+        self.network_name = "test_network"
+        self.config_file = os.path.join(os.path.dirname(__file__), os.pardir,
+                                        os.pardir, os.pardir, os.pardir,
+                                        os.pardir, "etc", "quantum",
+                                        "plugins", "linuxbridge",
+                                        "linuxbridge_conf.ini")
+
+        config = ConfigParser.ConfigParser()
+        self.br_name_prefix = "brq"
+        self.gw_name_prefix = "gw-"
+        self.tap_name_prefix = "tap"
+        self._linuxbridge_plugin = LinuxBridgePlugin.LinuxBridgePlugin()
+        try:
+            fh = open(self.config_file)
+            fh.close()
+            config.read(self.config_file)
+            self.physical_interface = config.get("LINUX_BRIDGE",
+                                                 "physical_interface")
+            self.polling_interval = config.get("AGENT", "polling_interval")
+        except Exception, e:
+            LOG.error("Unable to parse config file \"%s\": \nException%s"
+                      % (self.config_file, str(e)))
+            sys.exit(1)
+        self._linuxbridge = linux_agent.LinuxBridge(self.br_name_prefix,
+                                                    self.physical_interface)
+        self._linuxbridge_quantum_agent = linux_agent.LinuxBridgeQuantumAgent(
+                                                    self.br_name_prefix,
+                                                    self.physical_interface,
+                                                    self.polling_interval)
+
+    def run_cmd(self, args):
+        LOG.debug("Running command: " + " ".join(args))
+        p = Popen(args, stdout=PIPE)
+        retval = p.communicate()[0]
+        if p.returncode == -(signal.SIGALRM):
+            LOG.debug("Timeout running command: " + " ".join(args))
+        if retval:
+            LOG.debug("Command returned: %s" % retval)
+        return retval
+
+    def device_exists(self, device):
+        """Check if ethernet device exists."""
+        retval = self.run_cmd(['ip', 'link', 'show', 'dev', device])
+        if retval:
+            return True
+        else:
+            return False
+
+    """
+         Clean up functions after the tests
+    """
+    def tearDown(self):
+        """Clear the test environment(Clean the Database)"""
+        db.clear_db()
+
+    def tearDownNetwork(self, tenant_id, network_dict_id):
+        """
+        Tear down the Network
+        """
+        self._linuxbridge_plugin.delete_network(tenant_id, network_dict_id)
+
+    def tearDownUnplugInterface(self, tenant_id, network_dict_id, port_id):
+        """
+        Tear down the port
+        """
+        self._linuxbridge_plugin.unplug_interface(tenant_id, network_dict_id,
+                                                  port_id)
+        self.tearDownNetworkPort(tenant_id, network_dict_id, port_id)
+
+    def tearDownNetworkPort(self, tenant_id, network_dict_id, port_id):
+        """
+        Tear down Network Port
+        """
+        self._linuxbridge_plugin.delete_port(tenant_id, network_dict_id,
+                                             port_id)
+        self.tearDownNetwork(tenant_id, network_dict_id)
diff --git a/quantum/plugins/linuxbridge/tests/unit/test_database.py b/quantum/plugins/linuxbridge/tests/unit/test_database.py
new file mode 100644 (file)
index 0000000..3ce70af
--- /dev/null
@@ -0,0 +1,362 @@
+"""
+# 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.
+"""
+
+"""
+test_database.py is an independent test suite
+that tests the database api method calls
+"""
+import logging as LOG
+import unittest
+
+from common import constants as const
+
+import quantum.db.api as db
+import db.l2network_db as l2network_db
+
+
+LOG.getLogger(__name__)
+
+
+class L2networkDB(object):
+
+    """Class conisting of methods to call L2network db methods"""
+    def get_all_vlan_bindings(self):
+        """Get all vlan binding into a list of dict"""
+        vlans = []
+        try:
+            for vlan_bind in l2network_db.get_all_vlan_bindings():
+                LOG.debug("Getting vlan bindings for vlan: %s" %
+                            vlan_bind.vlan_id)
+                vlan_dict = {}
+                vlan_dict["vlan-id"] = str(vlan_bind.vlan_id)
+                vlan_dict["net-id"] = str(vlan_bind.network_id)
+                vlans.append(vlan_dict)
+        except Exception, exc:
+            LOG.error("Failed to get all vlan bindings: %s" % str(exc))
+        return vlans
+
+    def get_vlan_binding(self, network_id):
+        """Get a vlan binding"""
+        vlan = []
+        try:
+            for vlan_bind in l2network_db.get_vlan_binding(network_id):
+                LOG.debug("Getting vlan binding for vlan: %s"
+                           % vlan_bind.vlan_id)
+                vlan_dict = {}
+                vlan_dict["vlan-id"] = str(vlan_bind.vlan_id)
+                vlan_dict["net-id"] = str(vlan_bind.network_id)
+                vlan.append(vlan_dict)
+        except Exception, exc:
+            LOG.error("Failed to get vlan binding: %s" % str(exc))
+        return vlan
+
+    def create_vlan_binding(self, vlan_id, network_id):
+        """Create a vlan binding"""
+        vlan_dict = {}
+        try:
+            res = l2network_db.add_vlan_binding(vlan_id, network_id)
+            LOG.debug("Created vlan binding for vlan: %s" % res.vlan_id)
+            vlan_dict["vlan-id"] = str(res.vlan_id)
+            vlan_dict["net-id"] = str(res.network_id)
+            return vlan_dict
+        except Exception, exc:
+            LOG.error("Failed to create vlan binding: %s" % str(exc))
+
+    def delete_vlan_binding(self, network_id):
+        """Delete a vlan binding"""
+        try:
+            res = l2network_db.remove_vlan_binding(network_id)
+            LOG.debug("Deleted vlan binding for vlan: %s" % res.vlan_id)
+            vlan_dict = {}
+            vlan_dict["vlan-id"] = str(res.vlan_id)
+            return vlan_dict
+        except Exception, exc:
+            raise Exception("Failed to delete vlan binding: %s" % str(exc))
+
+    def update_vlan_binding(self, network_id, vlan_id):
+        """Update a vlan binding"""
+        try:
+            res = l2network_db.update_vlan_binding(network_id, vlan_id)
+            LOG.debug("Updating vlan binding for vlan: %s" % res.vlan_id)
+            vlan_dict = {}
+            vlan_dict["vlan-id"] = str(res.vlan_id)
+            vlan_dict["net-id"] = str(res.network_id)
+            return vlan_dict
+        except Exception, exc:
+            raise Exception("Failed to update vlan binding: %s" % str(exc))
+
+
+class QuantumDB(object):
+    """Class conisting of methods to call Quantum db methods"""
+    def get_all_networks(self, tenant_id):
+        """Get all networks"""
+        nets = []
+        try:
+            for net in db.network_list(tenant_id):
+                LOG.debug("Getting network: %s" % net.uuid)
+                net_dict = {}
+                net_dict["tenant-id"] = net.tenant_id
+                net_dict["net-id"] = str(net.uuid)
+                net_dict["net-name"] = net.name
+                nets.append(net_dict)
+        except Exception, exc:
+            LOG.error("Failed to get all networks: %s" % str(exc))
+        return nets
+
+    def get_network(self, network_id):
+        """Get a network"""
+        net = []
+        try:
+            for net in db.network_get(network_id):
+                LOG.debug("Getting network: %s" % net.uuid)
+                net_dict = {}
+                net_dict["tenant-id"] = net.tenant_id
+                net_dict["net-id"] = str(net.uuid)
+                net_dict["net-name"] = net.name
+                net.append(net_dict)
+        except Exception, exc:
+            LOG.error("Failed to get network: %s" % str(exc))
+        return net
+
+    def create_network(self, tenant_id, net_name):
+        """Create a network"""
+        net_dict = {}
+        try:
+            res = db.network_create(tenant_id,
+                                    net_name,
+                                    op_status="UP")
+            LOG.debug("Created network: %s" % res.uuid)
+            net_dict["tenant-id"] = res.tenant_id
+            net_dict["net-id"] = str(res.uuid)
+            net_dict["net-name"] = res.name
+            return net_dict
+        except Exception, exc:
+            LOG.error("Failed to create network: %s" % str(exc))
+
+    def delete_network(self, net_id):
+        """Delete a network"""
+        try:
+            net = db.network_destroy(net_id)
+            LOG.debug("Deleted network: %s" % net.uuid)
+            net_dict = {}
+            net_dict["net-id"] = str(net.uuid)
+            return net_dict
+        except Exception, exc:
+            raise Exception("Failed to delete port: %s" % str(exc))
+
+    def update_network(self, tenant_id, net_id, **kwargs):
+        """Update a network"""
+        try:
+            net = db.network_update(net_id, tenant_id, **kwargs)
+            LOG.debug("Updated network: %s" % net.uuid)
+            net_dict = {}
+            net_dict["net-id"] = str(net.uuid)
+            net_dict["net-name"] = net.name
+            return net_dict
+        except Exception, exc:
+            raise Exception("Failed to update network: %s" % str(exc))
+
+    def get_all_ports(self, net_id):
+        """Get all ports"""
+        ports = []
+        try:
+            for port in db.port_list(net_id):
+                LOG.debug("Getting port: %s" % port.uuid)
+                port_dict = {}
+                port_dict["port-id"] = str(port.uuid)
+                port_dict["net-id"] = str(port.network_id)
+                port_dict["int-id"] = port.interface_id
+                port_dict["state"] = port.state
+                port_dict["net"] = port.network
+                ports.append(port_dict)
+            return ports
+        except Exception, exc:
+            LOG.error("Failed to get all ports: %s" % str(exc))
+
+    def get_port(self, net_id, port_id):
+        """Get a port"""
+        port_list = []
+        port = db.port_get(net_id, port_id)
+        try:
+            LOG.debug("Getting port: %s" % port.uuid)
+            port_dict = {}
+            port_dict["port-id"] = str(port.uuid)
+            port_dict["net-id"] = str(port.network_id)
+            port_dict["int-id"] = port.interface_id
+            port_dict["state"] = port.state
+            port_list.append(port_dict)
+            return port_list
+        except Exception, exc:
+            LOG.error("Failed to get port: %s" % str(exc))
+
+    def create_port(self, net_id):
+        """Add a port"""
+        port_dict = {}
+        try:
+            port = db.port_create(net_id)
+            LOG.debug("Creating port %s" % port.uuid)
+            port_dict["port-id"] = str(port.uuid)
+            port_dict["net-id"] = str(port.network_id)
+            port_dict["int-id"] = port.interface_id
+            port_dict["state"] = port.state
+            return port_dict
+        except Exception, exc:
+            LOG.error("Failed to create port: %s" % str(exc))
+
+    def delete_port(self, net_id, port_id):
+        """Delete a port"""
+        try:
+            port = db.port_destroy(net_id, port_id)
+            LOG.debug("Deleted port %s" % port.uuid)
+            port_dict = {}
+            port_dict["port-id"] = str(port.uuid)
+            return port_dict
+        except Exception, exc:
+            raise Exception("Failed to delete port: %s" % str(exc))
+
+    def update_port(self, net_id, port_id, port_state):
+        """Update a port"""
+        try:
+            port = db.port_set_state(net_id, port_id, port_state)
+            LOG.debug("Updated port %s" % port.uuid)
+            port_dict = {}
+            port_dict["port-id"] = str(port.uuid)
+            port_dict["net-id"] = str(port.network_id)
+            port_dict["int-id"] = port.interface_id
+            port_dict["state"] = port.state
+            return port_dict
+        except Exception, exc:
+            raise Exception("Failed to update port state: %s" % str(exc))
+
+    def plug_interface(self, net_id, port_id, int_id):
+        """Plug interface to a port"""
+        try:
+            port = db.port_set_attachment(net_id, port_id, int_id)
+            LOG.debug("Attached interface to port %s" % port.uuid)
+            port_dict = {}
+            port_dict["port-id"] = str(port.uuid)
+            port_dict["net-id"] = str(port.network_id)
+            port_dict["int-id"] = port.interface_id
+            port_dict["state"] = port.state
+            return port_dict
+        except Exception, exc:
+            raise Exception("Failed to plug interface: %s" % str(exc))
+
+    def unplug_interface(self, net_id, port_id):
+        """Unplug interface to a port"""
+        try:
+            port = db.port_unset_attachment(net_id, port_id)
+            LOG.debug("Detached interface from port %s" % port.uuid)
+            port_dict = {}
+            port_dict["port-id"] = str(port.uuid)
+            port_dict["net-id"] = str(port.network_id)
+            port_dict["int-id"] = port.interface_id
+            port_dict["state"] = port.state
+            return port_dict
+        except Exception, exc:
+            raise Exception("Failed to unplug interface: %s" % str(exc))
+
+
+class L2networkDBTest(unittest.TestCase):
+    """Class conisting of L2network DB unit tests"""
+    def setUp(self):
+        """Setup for tests"""
+        l2network_db.initialize()
+        l2network_db.create_vlanids()
+        self.dbtest = L2networkDB()
+        self.quantum = QuantumDB()
+        LOG.debug("Setup")
+
+    def tearDown(self):
+        """Tear Down"""
+        db.clear_db()
+
+    def testa_create_vlanbinding(self):
+        """test add vlan binding"""
+        net1 = self.quantum.create_network("t1", "netid1")
+        vlan1 = self.dbtest.create_vlan_binding(10, net1["net-id"])
+        self.assertTrue(vlan1["vlan-id"] == "10")
+        self.teardown_vlanbinding()
+        self.teardown_network()
+
+    def testb_getall_vlanbindings(self):
+        """test get all vlan binding"""
+        net1 = self.quantum.create_network("t1", "netid1")
+        net2 = self.quantum.create_network("t1", "netid2")
+        vlan1 = self.dbtest.create_vlan_binding(10, net1["net-id"])
+        self.assertTrue(vlan1["vlan-id"] == "10")
+        vlan2 = self.dbtest.create_vlan_binding(20, net2["net-id"])
+        self.assertTrue(vlan2["vlan-id"] == "20")
+        vlans = self.dbtest.get_all_vlan_bindings()
+        self.assertTrue(len(vlans) == 2)
+        self.teardown_vlanbinding()
+        self.teardown_network()
+
+    def testc_delete_vlanbinding(self):
+        """test delete vlan binding"""
+        net1 = self.quantum.create_network("t1", "netid1")
+        vlan1 = self.dbtest.create_vlan_binding(10, net1["net-id"])
+        self.assertTrue(vlan1["vlan-id"] == "10")
+        self.dbtest.delete_vlan_binding(net1["net-id"])
+        vlans = self.dbtest.get_all_vlan_bindings()
+        count = 0
+        for vlan in vlans:
+            if vlan["vlan-id"] is "10":
+                count += 1
+        self.assertTrue(count == 0)
+        self.teardown_vlanbinding()
+        self.teardown_network()
+
+    def testd_update_vlanbinding(self):
+        """test update vlan binding"""
+        net1 = self.quantum.create_network("t1", "netid1")
+        vlan1 = self.dbtest.create_vlan_binding(10, net1["net-id"])
+        self.assertTrue(vlan1["vlan-id"] == "10")
+        vlan1 = self.dbtest.update_vlan_binding(net1["net-id"], 11)
+        self.assertTrue(vlan1["vlan-id"] == "11")
+        self.teardown_vlanbinding()
+        self.teardown_network()
+
+    def teste_test_vlanids(self):
+        """test vlanid methods"""
+        l2network_db.create_vlanids()
+        vlanids = l2network_db.get_all_vlanids()
+        self.assertTrue(len(vlanids) > 0)
+        vlanid = l2network_db.reserve_vlanid()
+        used = l2network_db.is_vlanid_used(vlanid)
+        self.assertTrue(used)
+        used = l2network_db.release_vlanid(vlanid)
+        self.assertFalse(used)
+        #counting on default teardown here to clear db
+
+    def teardown_network(self):
+        """tearDown Network table"""
+        LOG.debug("Tearing Down Network")
+        nets = self.quantum.get_all_networks("t1")
+        for net in nets:
+            netid = net["net-id"]
+            self.quantum.delete_network(netid)
+
+    def teardown_vlanbinding(self):
+        """tearDown VlanBinding table"""
+        LOG.debug("Tearing Down Vlan Binding")
+        vlans = self.dbtest.get_all_vlan_bindings()
+        for vlan in vlans:
+            netid = vlan["net-id"]
+            self.dbtest.delete_vlan_binding(netid)
index b39821f1df483f719d1100fb2ebdaf5fef28847d..788be9f26bcfc1450e2d558373019df3cd8fb064 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -70,6 +70,7 @@ config_path = 'etc/quantum/'
 init_path = 'etc/init.d'
 ovs_plugin_config_path = 'etc/quantum/plugins/openvswitch'
 cisco_plugin_config_path = 'etc/quantum/plugins/cisco'
+linuxbridge_plugin_config_path = 'etc/quantum/plugins/linuxbridge'
 
 DataFiles = [
     (config_path,
@@ -84,6 +85,8 @@ DataFiles = [
          'etc/quantum/plugins/cisco/ucs.ini',
          'etc/quantum/plugins/cisco/cisco_plugins.ini',
          'etc/quantum/plugins/cisco/db_conn.ini']),
+    (linuxbridge_plugin_config_path,
+        ['etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini']),
 ]
 
 setup(