]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Adds Brocade Plugin implementation
authorShiv Haris <sharis@brocade.com>
Wed, 23 Jan 2013 03:33:55 +0000 (03:33 +0000)
committerShiv Haris <sharis@brocade.com>
Thu, 14 Feb 2013 17:22:38 +0000 (09:22 -0800)
blueprint brocade-quantum-plugin

This plugin is meant to orchestrate Brocade VCS switches
running NOS, examples of these are:
   1. VDX 67xx series of switches
   2. VDX 87xx series of switches

Change-Id: Ia8c91ba0e70d3dc7fa79e7f0e09f869542846954

19 files changed:
etc/quantum/plugins/brocade/brocade.ini [new file with mode: 0644]
quantum/plugins/brocade/QuantumPlugin.py [new file with mode: 0644]
quantum/plugins/brocade/README.md [new file with mode: 0644]
quantum/plugins/brocade/__init__.py [new file with mode: 0644]
quantum/plugins/brocade/db/__init__.py [new file with mode: 0644]
quantum/plugins/brocade/db/models.py [new file with mode: 0644]
quantum/plugins/brocade/nos/__init__.py [new file with mode: 0644]
quantum/plugins/brocade/nos/fake_nosdriver.py [new file with mode: 0644]
quantum/plugins/brocade/nos/nctemplates.py [new file with mode: 0644]
quantum/plugins/brocade/nos/nosdriver.py [new file with mode: 0644]
quantum/plugins/brocade/tests/README [new file with mode: 0644]
quantum/plugins/brocade/tests/noscli.py [new file with mode: 0644]
quantum/plugins/brocade/tests/nostest.py [new file with mode: 0644]
quantum/plugins/brocade/vlanbm.py [new file with mode: 0644]
quantum/tests/unit/brocade/__init__.py [new file with mode: 0644]
quantum/tests/unit/brocade/test_brocade_db.py [new file with mode: 0644]
quantum/tests/unit/brocade/test_brocade_plugin.py [new file with mode: 0644]
quantum/tests/unit/brocade/test_brocade_vlan.py [new file with mode: 0644]
setup.py

diff --git a/etc/quantum/plugins/brocade/brocade.ini b/etc/quantum/plugins/brocade/brocade.ini
new file mode 100644 (file)
index 0000000..58589f8
--- /dev/null
@@ -0,0 +1,48 @@
+[SWITCH]
+# username = <mgmt admin username>
+# password = <mgmt admin password>
+# address  = <switch mgmt ip address>
+# ostype   = NOS
+#
+# Example:
+# username = admin
+# password = password
+# address  = 10.24.84.38
+# ostype   = NOS
+
+[DATABASE]
+# sql_connection = sqlite://
+# Enable the use of eventlet's db_pool for MySQL. The flags sql_min_pool_size,
+# sql_max_pool_size and sql_idle_timeout are relevant only if this is enabled.
+# sql_dbpool_enable = False
+# Minimum number of SQL connections to keep open in a pool
+# sql_min_pool_size = 1
+# Maximum number of SQL connections to keep open in a pool
+# sql_max_pool_size = 5
+# Timeout in seconds before idle sql connections are reaped
+# sql_idle_timeout = 3600
+#
+# Example:
+# sql_connection = mysql://root:pass@localhost/brcd_quantum?charset=utf8
+
+[PHYSICAL_INTERFACE]
+# physical_interface = <physical network name>
+#
+# Example:
+# physical_interface = physnet1
+
+[VLANS]
+# network_vlan_ranges = <physical network name>:nnnn:mmmm
+#
+# Example:
+# network_vlan_ranges = physnet1:1000:2999
+
+[AGENT]
+# Example:
+# root_helper = sudo /usr/local/bin/quantum-rootwrap /etc/quantum/rootwrap.conf
+
+[LINUX_BRIDGE]
+# physical_interface_mappings = <physical network name>:<local interface>
+#
+# Example:
+# physical_interface_mappings = physnet1:em1
diff --git a/quantum/plugins/brocade/QuantumPlugin.py b/quantum/plugins/brocade/QuantumPlugin.py
new file mode 100644 (file)
index 0000000..6e1dd61
--- /dev/null
@@ -0,0 +1,453 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 Brocade Communications System, 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.
+#
+# Authors:
+# Shiv Haris (sharis@brocade.com)
+# Varma Bhupatiraju (vbhupati@#brocade.com)
+#
+# (Some parts adapted from LinuxBridge Plugin)
+# TODO (shiv) need support for security groups
+
+
+"""
+Implentation of Brocade Quantum Plugin.
+"""
+from quantum.agent import securitygroups_rpc as sg_rpc
+from quantum.common import rpc as q_rpc
+from quantum.common import topics
+from quantum.common import utils
+from quantum.db import api as db
+from quantum.db import db_base_plugin_v2
+from quantum.db import dhcp_rpc_base
+from quantum.db import l3_db
+from quantum.db import l3_rpc_base
+from quantum.db import securitygroups_rpc_base as sg_db_rpc
+from quantum.extensions import portbindings
+from quantum.extensions import securitygroup as ext_sg
+from quantum.openstack.common import cfg
+from quantum.openstack.common import context
+from quantum.openstack.common import importutils
+from quantum.openstack.common import log as logging
+from quantum.openstack.common import rpc
+from quantum.openstack.common.rpc import proxy
+from quantum.plugins.brocade.db import models as brocade_db
+from quantum.plugins.brocade import vlanbm as vbm
+from quantum import policy
+
+
+LOG = logging.getLogger(__name__)
+PLUGIN_VERSION = 0.88
+AGENT_OWNER_PREFIX = "network:"
+NOS_DRIVER = 'quantum.plugins.brocade.nos.nosdriver.NOSdriver'
+
+SWITCH_OPTS = [cfg.StrOpt('address', default=''),
+               cfg.StrOpt('username', default=''),
+               cfg.StrOpt('password', default=''),
+               cfg.StrOpt('ostype', default='NOS')
+               ]
+
+PHYSICAL_INTERFACE_OPTS = [cfg.StrOpt('physical_interface', default='eth0')
+                           ]
+
+cfg.CONF.register_opts(SWITCH_OPTS, "SWITCH")
+cfg.CONF.register_opts(PHYSICAL_INTERFACE_OPTS, "PHYSICAL_INTERFACE")
+
+
+class BridgeRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin,
+                         l3_rpc_base.L3RpcCallbackMixin,
+                         sg_db_rpc.SecurityGroupServerRpcCallbackMixin):
+    """Agent callback."""
+
+    RPC_API_VERSION = '1.1'
+    # Device names start with "tap"
+    # history
+    #   1.1 Support Security Group RPC
+    TAP_PREFIX_LEN = 3
+
+    def create_rpc_dispatcher(self):
+        '''Get the rpc dispatcher for this manager.
+
+        If a manager would like to set an rpc API version, or support more than
+        one class as the target of rpc messages, override this method.
+        '''
+        return q_rpc.PluginRpcDispatcher([self])
+
+    @classmethod
+    def get_port_from_device(cls, device):
+        """Get port from the brocade specific db."""
+
+        # TODO(shh) context is not being passed as
+        # an argument to this function;
+        #
+        # need to be fixed in:
+        # file: quantum/db/securtygroups_rpc_base.py
+        # function: securitygroup_rules_for_devices()
+        # which needs to pass context to us
+
+        # Doing what other plugins are doing
+        session = db.get_session()
+        port = brocade_db.get_port_from_device(
+            session, device[cls.TAP_PREFIX_LEN:])
+
+        # TODO(shiv): need to extend the db model to include device owners
+        # make it appears that the device owner is of type network
+        if port:
+            port['device'] = device
+            port['device_owner'] = AGENT_OWNER_PREFIX
+            port['binding:vif_type'] = 'bridge'
+        return port
+
+    def get_device_details(self, rpc_context, **kwargs):
+        """Agent requests device details."""
+
+        agent_id = kwargs.get('agent_id')
+        device = kwargs.get('device')
+        LOG.debug(_("Device %(device)s details requested from %(agent_id)s"),
+                  locals())
+        port = brocade_db.get_port(rpc_context, device[self.TAP_PREFIX_LEN:])
+        if port:
+            entry = {'device': device,
+                     'vlan_id': port.vlan_id,
+                     'network_id': port.network_id,
+                     'port_id': port.port_id,
+                     'physical_network': port.physical_interface,
+                     'admin_state_up': port.admin_state_up
+                     }
+
+        else:
+            entry = {'device': device}
+            LOG.debug(_("%s can not be found in database"), device)
+        return entry
+
+    def update_device_down(self, rpc_context, **kwargs):
+        """Device no longer exists on agent."""
+
+        device = kwargs.get('device')
+        port = self.get_port_from_device(device)
+        if port:
+            entry = {'device': device,
+                     'exists': True}
+            # Set port status to DOWN
+            port_id = port['port_id']
+            brocade_db.update_port_state(rpc_context, port_id, False)
+        else:
+            entry = {'device': device,
+                     'exists': False}
+            LOG.debug(_("%s can not be found in database"), device)
+        return entry
+
+
+class AgentNotifierApi(proxy.RpcProxy,
+                       sg_rpc.SecurityGroupAgentRpcApiMixin):
+    '''Agent side of the linux bridge rpc API.
+
+    API version history:
+        1.0 - Initial version.
+
+    '''
+
+    BASE_RPC_API_VERSION = '1.0'
+
+    def __init__(self, topic):
+        super(AgentNotifierApi, self).__init__(
+            topic=topic, default_version=self.BASE_RPC_API_VERSION)
+        self.topic = topic
+        self.topic_network_delete = topics.get_topic_name(topic,
+                                                          topics.NETWORK,
+                                                          topics.DELETE)
+        self.topic_port_update = topics.get_topic_name(topic,
+                                                       topics.PORT,
+                                                       topics.UPDATE)
+
+    def network_delete(self, context, network_id):
+        self.fanout_cast(context,
+                         self.make_msg('network_delete',
+                                       network_id=network_id),
+                         topic=self.topic_network_delete)
+
+    def port_update(self, context, port, physical_network, vlan_id):
+        self.fanout_cast(context,
+                         self.make_msg('port_update',
+                                       port=port,
+                                       physical_network=physical_network,
+                                       vlan_id=vlan_id),
+                         topic=self.topic_port_update)
+
+
+class BrocadePluginV2(db_base_plugin_v2.QuantumDbPluginV2,
+                      l3_db.L3_NAT_db_mixin,
+                      sg_db_rpc.SecurityGroupServerRpcMixin):
+    """BrocadePluginV2 is a Quantum plugin.
+
+    Provides L2 Virtual Network functionality using VDX. Upper
+    layer driver class that interfaces to NETCONF layer below.
+
+    """
+
+    def __init__(self):
+        """Initialize Brocade Plugin, specify switch address
+        and db configuration.
+        """
+
+        self.supported_extension_aliases = ["binding", "security-group"]
+        self.binding_view = "extension:port_binding:view"
+        self.binding_set = "extension:port_binding:set"
+
+        self.physical_interface = (cfg.CONF.PHYSICAL_INTERFACE.
+                                   physical_interface)
+        db.configure_db()
+        self.ctxt = context.get_admin_context()
+        self.ctxt.session = db.get_session()
+        self._vlan_bitmap = vbm.VlanBitmap(self.ctxt)
+        self._setup_rpc()
+        self.brocade_init()
+
+    def brocade_init(self):
+        """Brocade specific initialization."""
+
+        self._switch = {'address': cfg.CONF.SWITCH.address,
+                        'username': cfg.CONF.SWITCH.username,
+                        'password': cfg.CONF.SWITCH.password
+                        }
+        self._driver = importutils.import_object(NOS_DRIVER)
+
+    def _setup_rpc(self):
+        # RPC support
+        self.topic = topics.PLUGIN
+        self.rpc_context = context.RequestContext('quantum', 'quantum',
+                                                  is_admin=False)
+        self.conn = rpc.create_connection(new=True)
+        self.callbacks = BridgeRpcCallbacks()
+        self.dispatcher = self.callbacks.create_rpc_dispatcher()
+        self.conn.create_consumer(self.topic, self.dispatcher,
+                                  fanout=False)
+        # Consume from all consumers in a thread
+        self.conn.consume_in_thread()
+        self.notifier = AgentNotifierApi(topics.AGENT)
+
+    def create_network(self, context, network):
+        """This call to create network translates to creation of
+        port-profile on the physical switch.
+        """
+
+        with context.session.begin(subtransactions=True):
+            net = super(BrocadePluginV2, self).create_network(context, network)
+            net_uuid = net['id']
+            vlan_id = self._vlan_bitmap.get_next_vlan(None)
+            switch = self._switch
+            try:
+                self._driver.create_network(switch['address'],
+                                            switch['username'],
+                                            switch['password'],
+                                            vlan_id)
+            except Exception as e:
+                # Proper formatting
+                LOG.warning(_("Brocade NOS driver:"))
+                LOG.warning(_("%s"), e)
+                LOG.debug(_("Returning the allocated vlan (%d) to the pool"),
+                          vlan_id)
+                self._vlan_bitmap.release_vlan(int(vlan_id))
+                raise Exception("Brocade plugin raised exception, check logs")
+
+            brocade_db.create_network(context, net_uuid, vlan_id)
+
+        LOG.info(_("Allocated vlan (%d) from the pool"), vlan_id)
+        return net
+
+    def delete_network(self, context, net_id):
+        """This call to delete the network translates to removing
+        the port-profile on the physical switch.
+        """
+
+        with context.session.begin(subtransactions=True):
+            result = super(BrocadePluginV2, self).delete_network(context,
+                                                                 net_id)
+            # we must delete all ports in db first (foreign key constraint)
+            # there is no need to delete port in the driver (its a no-op)
+            # (actually: note there is no such call to the driver)
+            bports = brocade_db.get_ports(context, net_id)
+            for bport in bports:
+                brocade_db.delete_port(context, bport['port_id'])
+
+            # find the vlan for this network
+            net = brocade_db.get_network(context, net_id)
+            vlan_id = net['vlan']
+
+            # Tell hw to do remove PP
+            switch = self._switch
+            try:
+                self._driver.delete_network(switch['address'],
+                                            switch['username'],
+                                            switch['password'],
+                                            net_id)
+            except Exception as e:
+                # Proper formatting
+                LOG.warning(_("Brocade NOS driver:"))
+                LOG.warning(_("%s"), e)
+                raise Exception("Brocade plugin raised exception, check logs")
+
+            # now ok to delete the network
+            brocade_db.delete_network(context, net_id)
+
+        # relinquish vlan in bitmap
+        self._vlan_bitmap.release_vlan(int(vlan_id))
+        return result
+
+    def create_port(self, context, port):
+        """Create logical port on the switch."""
+
+        tenant_id = port['port']['tenant_id']
+        network_id = port['port']['network_id']
+        admin_state_up = port['port']['admin_state_up']
+
+        physical_interface = self.physical_interface
+
+        with context.session.begin(subtransactions=True):
+            bnet = brocade_db.get_network(context, network_id)
+            vlan_id = bnet['vlan']
+
+            quantum_port = super(BrocadePluginV2, self).create_port(context,
+                                                                    port)
+            interface_mac = quantum_port['mac_address']
+            port_id = quantum_port['id']
+
+            switch = self._switch
+
+            # convert mac format: xx:xx:xx:xx:xx:xx -> xxxx.xxxx.xxxx
+            mac = self.mac_reformat_62to34(interface_mac)
+            try:
+                self._driver.associate_mac_to_network(switch['address'],
+                                                      switch['username'],
+                                                      switch['password'],
+                                                      vlan_id,
+                                                      mac)
+            except Exception as e:
+                # Proper formatting
+                LOG.warning(_("Brocade NOS driver:"))
+                LOG.warning(_("%s"), e)
+                raise Exception("Brocade plugin raised exception, check logs")
+
+            # save to brocade persistent db
+            brocade_db.create_port(context, port_id, network_id,
+                                   physical_interface,
+                                   vlan_id, tenant_id, admin_state_up)
+
+        # apply any extensions
+        return self._extend_port_dict_binding(context, quantum_port)
+
+    def delete_port(self, context, port_id):
+        with context.session.begin(subtransactions=True):
+            super(BrocadePluginV2, self).delete_port(context, port_id)
+            brocade_db.delete_port(context, port_id)
+
+    def update_port(self, context, port_id, port):
+        original_port = self.get_port(context, port_id)
+        session = context.session
+        port_updated = False
+        with session.begin(subtransactions=True):
+            # delete the port binding and read it with the new rules
+            if ext_sg.SECURITYGROUPS in port['port']:
+                port['port'][ext_sg.SECURITYGROUPS] = (
+                    self._get_security_groups_on_port(context, port))
+                self._delete_port_security_group_bindings(context, port_id)
+                self._process_port_create_security_group(
+                    context,
+                    port_id,
+                    port['port'][ext_sg.SECURITYGROUPS])
+                port_updated = True
+
+            port = super(BrocadePluginV2, self).update_port(
+                context, port_id, port)
+            self._extend_port_dict_security_group(context, port)
+
+        if original_port['admin_state_up'] != port['admin_state_up']:
+            port_updated = True
+
+        if (original_port['fixed_ips'] != port['fixed_ips'] or
+            not utils.compare_elements(
+                original_port.get(ext_sg.SECURITYGROUPS),
+                port.get(ext_sg.SECURITYGROUPS))):
+            self.notifier.security_groups_member_updated(
+                context, port.get(ext_sg.SECURITYGROUPS))
+
+        if port_updated:
+            self._notify_port_updated(context, port)
+
+        return self._extend_port_dict_binding(context, port)
+
+    def get_port(self, context, port_id, fields=None):
+        with context.session.begin(subtransactions=True):
+            port = super(BrocadePluginV2, self).get_port(
+                context, port_id, fields)
+            self._extend_port_dict_security_group(context, port)
+            self._extend_port_dict_binding(context, port)
+
+        return self._fields(port, fields)
+
+    def get_ports(self, context, filters=None, fields=None):
+        res_ports = []
+        with context.session.begin(subtransactions=True):
+            ports = super(BrocadePluginV2, self).get_ports(context,
+                                                           filters,
+                                                           fields)
+            for port in ports:
+                self._extend_port_dict_security_group(context, port)
+                self._extend_port_dict_binding(context, port)
+                res_ports.append(self._fields(port, fields))
+
+        return res_ports
+
+    def _notify_port_updated(self, context, port):
+        port_id = port['id']
+        bport = brocade_db.get_port(context, port_id)
+        self.notifier.port_update(context, port,
+                                  bport.physical_interface,
+                                  bport.vlan_id)
+
+    def _extend_port_dict_binding(self, context, port):
+        if self._check_view_auth(context, port, self.binding_view):
+            port[portbindings.VIF_TYPE] = portbindings.VIF_TYPE_BRIDGE
+            port['binding:vif_type'] = portbindings.VIF_TYPE_BRIDGE
+            port[portbindings.CAPABILITIES] = {
+                portbindings.CAP_PORT_FILTER:
+                'security-group' in self.supported_extension_aliases}
+        return port
+
+    def _check_view_auth(self, context, resource, action):
+        return policy.check(context, action, resource)
+
+    def get_plugin_version(self):
+        """Get version number of the plugin."""
+        return PLUGIN_VERSION
+
+    @staticmethod
+    def mac_reformat_62to34(interface_mac):
+        """Transform MAC address format.
+
+        Transforms from 6 groups of 2 hexadecimal numbers delimited by ":"
+        to 3 groups of 4 hexadecimals numbers delimited by ".".
+
+        :param interface_mac: MAC address in the format xx:xx:xx:xx:xx:xx
+        :type interface_mac: string
+        :returns: MAC address in the format xxxx.xxxx.xxxx
+        :rtype: string
+
+        """
+
+        mac = interface_mac.replace(":", "")
+        mac = mac[0:4] + "." + mac[4:8] + "." + mac[8:12]
+        return mac
diff --git a/quantum/plugins/brocade/README.md b/quantum/plugins/brocade/README.md
new file mode 100644 (file)
index 0000000..645367c
--- /dev/null
@@ -0,0 +1,112 @@
+Brocade Openstack Quantum Plugin
+================================
+
+* up-to-date version of these instructions are located at:
+  http://wiki.openstack.org/brocade-quantum-plugin
+
+* N.B.: Please see Prerequisites section  regarding ncclient (netconf client library)
+
+* Supports VCS (Virtual Cluster of Switches)
+
+
+Openstack Brocade Quantum Plugin implements the Quantum v2.0 API.
+
+This plugin is meant to orchestrate Brocade VCS switches running NOS, examples of these are:
+
+   1. VDX 67xx series of switches
+   2. VDX 87xx series of switches
+
+Brocade Quantum plugin implements the Quantum v2.0 API. It uses NETCONF at the backend
+to configure the Brocade switch.
+
+             +------------+        +------------+          +-------------+
+             |            |        |            |          |             |
+             |            |        |            |          |   Brocade   |
+             | Openstack  |  v2.0  |  Brocade   |  NETCONF |  VCS Switch |
+             | Quantum    +--------+  Quantum   +----------+             |
+             |            |        |  Plugin    |          |  VDX 67xx   |
+             |            |        |            |          |  VDX 87xx   |
+             |            |        |            |          |             |
+             |            |        |            |          |             |
+             +------------+        +------------+          +-------------+
+
+
+Directory Structure
+===================
+
+Normally you will have your Openstack directory structure as follows:
+
+         /opt/stack/nova/
+         /opt/stack/horizon/
+         ...
+         /opt/stack/quantum/quantum/plugins/
+
+Within this structure, Brocade plugin resides at:
+
+         /opt/stack/quantum/quantum/plugins/brocade
+
+
+Prerequsites
+============
+
+This plugin requires installation of the python netconf client (ncclient) library:
+
+ncclient v0.3.1 - Python library for NETCONF clients available at http://github.com/brocade/ncclient
+
+  % git clone https://www.github.com/brocade/ncclient
+  % cd ncclient; sudo python ./setup.py install
+
+
+Configuration
+=============
+
+1. Specify to Quantum that you will be using the Brocade Plugin - this is done
+by setting the parameter core_plugin in Quantum:
+
+        core_plugin = quantum.plugins.brocade.QuantumPlugin.BrocadePluginV2
+
+2. Physical switch configuration parameters and Brocade specific database configuration is specified in
+the configuration file specified in the brocade.ini files:
+
+        % cat /etc/quantum/plugins/brocade/brocade.ini
+        [SWITCH]
+        username = admin
+        password = password
+        address  = <switch mgmt ip address>
+        ostype   = NOS
+
+        [DATABASE]
+        sql_connection = mysql://root:pass@localhost/brocade_quantum?charset=utf8
+
+        (please see list of more configuration parameters in the brocade.ini file)
+
+Running Setup.py
+================
+
+Running setup.py with appropriate permissions will copy the default configuration
+file to /etc/quantum/plugins/brocade/brocade.ini. This file MUST be edited to
+suit your setup/environment.
+
+      % cd /opt/stack/quantum/quantum/plugins/brocade
+      % python setup.py
+
+
+Devstack
+========
+
+Please see special notes for devstack at:
+http://wiki.openstack.org/brocade-quantum-plugin
+
+In order to use Brocade Quantum Plugin, add the following lines in localrc, if localrc file doe
+ not exist create one:
+
+ENABLED_SERVICES=g-api,g-reg,key,n-api,n-crt,n-obj,n-cpu,n-net,n-cond,cinder,c-sch,c-api,c-vol,n-sch,n-novnc,n-xvnc,n-cauth,horizon,rabbit,quantum,q-svc,q-agt
+Q_PLUGIN=brocade
+
+As part of running devstack/stack.sh, the configuration files is copied as:
+
+  % cp /opt/stack/quantum/etc/quantum/plugins/brocade/brocade.ini /etc/quantum/plugins/brocade/brocade.ini
+
+(hence it is important to make any changes to the configuration in:
+/opt/stack/quantum/etc/quantum/plugins/brocade/brocade.ini)
+
diff --git a/quantum/plugins/brocade/__init__.py b/quantum/plugins/brocade/__init__.py
new file mode 100644 (file)
index 0000000..c22f863
--- /dev/null
@@ -0,0 +1,16 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 Brocade Communications System, 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.
diff --git a/quantum/plugins/brocade/db/__init__.py b/quantum/plugins/brocade/db/__init__.py
new file mode 100644 (file)
index 0000000..c22f863
--- /dev/null
@@ -0,0 +1,16 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 Brocade Communications System, 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.
diff --git a/quantum/plugins/brocade/db/models.py b/quantum/plugins/brocade/db/models.py
new file mode 100644 (file)
index 0000000..ee2dd4b
--- /dev/null
@@ -0,0 +1,152 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 Brocade Communications System, 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.
+#
+# Authors:
+# Shiv Haris (sharis@brocade.com)
+# Varma Bhupatiraju (vbhupati@#brocade.com)
+
+
+"""
+Brocade specific database schema/model.
+"""
+
+import sqlalchemy as sa
+
+from quantum.db import model_base
+from quantum.db import models_v2
+
+
+class BrocadeNetwork(model_base.BASEV2, models_v2.HasId):
+    """Schema for brocade network."""
+    vlan = sa.Column(sa.String(10))
+
+
+class BrocadePort(model_base.BASEV2):
+    """Schema for brocade port."""
+
+    port_id = sa.Column(sa.String(36), primary_key=True, default="")
+    network_id = sa.Column(sa.String(36),
+                           sa.ForeignKey("brocadenetworks.id"),
+                           nullable=False)
+    admin_state_up = sa.Column(sa.Boolean, nullable=False)
+    physical_interface = sa.Column(sa.String(36))
+    vlan_id = sa.Column(sa.String(36))
+    tenant_id = sa.Column(sa.String(36))
+
+
+def create_network(context, net_id, vlan):
+    """Create a brocade specific network/port-profiles."""
+
+    session = context.session
+    with session.begin(subtransactions=True):
+        net = BrocadeNetwork(id=net_id, vlan=vlan)
+        session.add(net)
+
+    return net
+
+
+def delete_network(context, net_id):
+    """Delete a brocade specific network/port-profiles."""
+
+    session = context.session
+    with session.begin(subtransactions=True):
+        net = (session.query(BrocadeNetwork).filter_by(id=net_id).first())
+        if net is not None:
+            session.delete(net)
+
+
+def get_network(context, net_id, fields=None):
+    """Get brocade specific network, with vlan extension."""
+
+    session = context.session
+    return (session.query(BrocadeNetwork).filter_by(id=net_id).first())
+
+
+def get_networks(context, filters=None, fields=None):
+    """Get all brocade specific networks."""
+
+    session = context.session
+    try:
+        nets = session.query(BrocadeNetwork).all()
+        return nets
+    except sa.exc.SQLAlchemyError:
+        return None
+
+
+def create_port(context, port_id, network_id, physical_interface,
+                vlan_id, tenant_id, admin_state_up):
+    """Create a brocade specific port, has policy like vlan."""
+
+    # port_id is truncated: since the linux-bridge tap device names are
+    # based on truncated port id, this enables port lookups using
+    # tap devices
+    port_id = port_id[0:11]
+    session = context.session
+    with session.begin(subtransactions=True):
+        port = BrocadePort(port_id=port_id,
+                           network_id=network_id,
+                           physical_interface=physical_interface,
+                           vlan_id=vlan_id,
+                           admin_state_up=admin_state_up,
+                           tenant_id=tenant_id)
+        session.add(port)
+    return port
+
+
+def get_port(context, port_id):
+    """get a brocade specific port."""
+
+    port_id = port_id[0:11]
+    session = context.session
+    port = (session.query(BrocadePort).filter_by(port_id=port_id).first())
+    return port
+
+
+def get_ports(context, network_id=None):
+    """get a brocade specific port."""
+
+    session = context.session
+    ports = (session.query(BrocadePort).filter_by(network_id=network_id).all())
+    return ports
+
+
+def delete_port(context, port_id):
+    """delete brocade specific port."""
+
+    port_id = port_id[0:11]
+    session = context.session
+    with session.begin(subtransactions=True):
+        port = (session.query(BrocadePort).filter_by(port_id=port_id).first())
+        if port is not None:
+            session.delete(port)
+
+
+def get_port_from_device(session, port_id):
+    """get port from the tap device."""
+
+    # device is same as truncated port_id
+    port = (session.query(BrocadePort).filter_by(port_id=port_id).first())
+    return port
+
+
+def update_port_state(context, port_id, admin_state_up):
+    """Update port attributes."""
+
+    port_id = port_id[0:11]
+    session = context.session
+    session.query(BrocadePort).filter_by(
+        port_id=port_id).update({'admin_state_up': admin_state_up})
diff --git a/quantum/plugins/brocade/nos/__init__.py b/quantum/plugins/brocade/nos/__init__.py
new file mode 100644 (file)
index 0000000..9d4562b
--- /dev/null
@@ -0,0 +1,16 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright (c) 2013 Brocade Communications 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.
diff --git a/quantum/plugins/brocade/nos/fake_nosdriver.py b/quantum/plugins/brocade/nos/fake_nosdriver.py
new file mode 100644 (file)
index 0000000..495d23a
--- /dev/null
@@ -0,0 +1,118 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 Brocade Communications System, 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.
+#
+# Authors:
+# Varma Bhupatiraju (vbhupati@#brocade.com)
+# Shiv Haris (sharis@brocade.com)
+
+
+"""
+FAKE DRIVER, for unit tests purposes
+Brocade NOS Driver implements NETCONF over SSHv2 for
+Quantum network life-cycle management
+"""
+
+
+class NOSdriver():
+    """NOS NETCONF interface driver for Quantum network.
+
+    Fake: Handles life-cycle management of Quantum network,
+    leverages AMPP on NOS
+    (for use by unit tests, avoids touching any hardware)
+
+    """
+
+    def __init__(self):
+        pass
+
+    def connect(self, host, username, password):
+        """Connect via SSH and initialize the NETCONF session."""
+        pass
+
+    def create_network(self, host, username, password, net_id):
+        """Creates a new virtual network."""
+        pass
+
+    def delete_network(self, host, username, password, net_id):
+        """Deletes a virtual network."""
+        pass
+
+    def associate_mac_to_network(self, host, username, password,
+                                 net_id, mac):
+        """Associates a MAC address to virtual network."""
+        pass
+
+    def dissociate_mac_from_network(self, host, username, password,
+                                    net_id, mac):
+        """Dissociates a MAC address from virtual network."""
+        pass
+
+    def create_vlan_interface(self, mgr, vlan_id):
+        """Configures a VLAN interface."""
+        pass
+
+    def delete_vlan_interface(self, mgr, vlan_id):
+        """Deletes a VLAN interface."""
+        pass
+
+    def get_port_profiles(self, mgr):
+        """Retrieves all port profiles."""
+        pass
+
+    def get_port_profile(self, mgr, name):
+        """Retrieves a port profile."""
+        pass
+
+    def create_port_profile(self, mgr, name):
+        """Creates a port profile."""
+        pass
+
+    def delete_port_profile(self, mgr, name):
+        """Deletes a port profile."""
+        pass
+
+    def activate_port_profile(self, mgr, name):
+        """Activates a port profile."""
+        pass
+
+    def deactivate_port_profile(self, mgr, name):
+        """Deactivates a port profile."""
+        pass
+
+    def associate_mac_to_port_profile(self, mgr, name, mac_address):
+        """Associates a MAC address to a port profile."""
+        pass
+
+    def dissociate_mac_from_port_profile(self, mgr, name, mac_address):
+        """Dissociates a MAC address from a port profile."""
+        pass
+
+    def create_vlan_profile_for_port_profile(self, mgr, name):
+        """Creates VLAN sub-profile for port profile."""
+        pass
+
+    def configure_l2_mode_for_vlan_profile(self, mgr, name):
+        """Configures L2 mode for VLAN sub-profile."""
+        pass
+
+    def configure_trunk_mode_for_vlan_profile(self, mgr, name):
+        """Configures trunk mode for VLAN sub-profile."""
+        pass
+
+    def configure_allowed_vlans_for_vlan_profile(self, mgr, name, vlan_id):
+        """Configures allowed VLANs for VLAN sub-profile."""
+        pass
diff --git a/quantum/plugins/brocade/nos/nctemplates.py b/quantum/plugins/brocade/nos/nctemplates.py
new file mode 100644 (file)
index 0000000..1bbfc22
--- /dev/null
@@ -0,0 +1,204 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright (c) 2013 Brocade Communications 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.
+#
+# Authors:
+# Varma Bhupatiraju (vbhupati@#brocade.com)
+# Shiv Haris (sharis@brocade.com)
+
+
+"""
+NOS NETCONF XML Configuration Command Templates
+Interface Configuration Commands
+"""
+# Create VLAN (vlan_id)
+CREATE_VLAN_INTERFACE = """
+    <config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
+        <interface-vlan xmlns="urn:brocade.com:mgmt:brocade-interface">
+            <interface>
+                <vlan>
+                    <name>{vlan_id}</name>
+                </vlan>
+            </interface>
+        </interface-vlan>
+    </config>
+"""
+
+# Delete VLAN (vlan_id)
+DELETE_VLAN_INTERFACE = """
+    <config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
+        <interface-vlan xmlns="urn:brocade.com:mgmt:brocade-interface">
+            <interface>
+                <vlan operation="delete">
+                    <name>{vlan_id}</name>
+                </vlan>
+            </interface>
+        </interface-vlan>
+    </config>
+"""
+
+#
+# AMPP Life-cycle Management Configuration Commands
+#
+
+# Create AMPP port-profile (port_profile_name)
+CREATE_PORT_PROFILE = """
+    <config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
+        <port-profile xmlns="urn:brocade.com:mgmt:brocade-port-profile">
+            <name>{name}</name>
+        </port-profile>
+    </config>
+"""
+
+# Create VLAN sub-profile for port-profile (port_profile_name)
+CREATE_VLAN_PROFILE_FOR_PORT_PROFILE = """
+    <config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
+        <port-profile xmlns="urn:brocade.com:mgmt:brocade-port-profile">
+            <name>{name}</name>
+            <vlan-profile/>
+        </port-profile>
+    </config>
+"""
+
+# Configure L2 mode for VLAN sub-profile (port_profile_name)
+CONFIGURE_L2_MODE_FOR_VLAN_PROFILE = """
+    <config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
+        <port-profile xmlns="urn:brocade.com:mgmt:brocade-port-profile">
+            <name>{name}</name>
+            <vlan-profile>
+                <switchport/>
+            </vlan-profile>
+        </port-profile>
+    </config>
+"""
+
+# Configure trunk mode for VLAN sub-profile (port_profile_name)
+CONFIGURE_TRUNK_MODE_FOR_VLAN_PROFILE = """
+    <config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
+        <port-profile xmlns="urn:brocade.com:mgmt:brocade-port-profile">
+            <name>{name}</name>
+            <vlan-profile>
+                <switchport>
+                    <mode>
+                        <vlan-mode>trunk</vlan-mode>
+                    </mode>
+                </switchport>
+            </vlan-profile>
+        </port-profile>
+    </config>
+"""
+
+# Configure allowed VLANs for VLAN sub-profile
+# (port_profile_name, allowed_vlan, native_vlan)
+CONFIGURE_ALLOWED_VLANS_FOR_VLAN_PROFILE = """
+    <config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
+        <port-profile xmlns="urn:brocade.com:mgmt:brocade-port-profile">
+            <name>{name}</name>
+            <vlan-profile>
+                <switchport>
+                    <trunk>
+                        <allowed>
+                            <vlan>
+                                <add>{vlan_id}</add>
+                            </vlan>
+                        </allowed>
+                        <native-vlan>{vlan_id}</native-vlan>
+                    </trunk>
+                </switchport>
+            </vlan-profile>
+        </port-profile>
+    </config>
+"""
+
+# Delete port-profile (port_profile_name)
+DELETE_PORT_PROFILE = """
+    <config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
+        <port-profile
+xmlns="urn:brocade.com:mgmt:brocade-port-profile" operation="delete">
+            <name>{name}</name>
+        </port-profile>
+    </config>
+"""
+
+# Activate port-profile (port_profile_name)
+ACTIVATE_PORT_PROFILE = """
+    <config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
+        <port-profile-global xmlns="urn:brocade.com:mgmt:brocade-port-profile">
+            <port-profile>
+                <name>{name}</name>
+                <activate/>
+            </port-profile>
+        </port-profile-global>
+    </config>
+"""
+
+# Deactivate port-profile (port_profile_name)
+DEACTIVATE_PORT_PROFILE = """
+    <config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
+        <port-profile-global xmlns="urn:brocade.com:mgmt:brocade-port-profile">
+            <port-profile>
+                <name>{name}</name>
+                <activate
+xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" nc:operation="delete" />
+            </port-profile>
+        </port-profile-global>
+    </config>
+"""
+
+# Associate MAC address to port-profile (port_profile_name, mac_address)
+ASSOCIATE_MAC_TO_PORT_PROFILE = """
+    <config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
+        <port-profile-global xmlns="urn:brocade.com:mgmt:brocade-port-profile">
+            <port-profile>
+                <name>{name}</name>
+                <static>
+                    <mac-address>{mac_address}</mac-address>
+                </static>
+            </port-profile>
+        </port-profile-global>
+    </config>
+"""
+
+# Dissociate MAC address from port-profile (port_profile_name, mac_address)
+DISSOCIATE_MAC_FROM_PORT_PROFILE = """
+    <config xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0">
+        <port-profile-global xmlns="urn:brocade.com:mgmt:brocade-port-profile">
+            <port-profile>
+                <name>{name}</name>
+                <static
+xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" nc:operation="delete">
+                    <mac-address>{mac_address}</mac-address>
+                </static>
+            </port-profile>
+        </port-profile-global>
+    </config>
+"""
+
+#
+# Custom RPC Commands
+#
+
+
+#
+# Constants
+#
+
+# Port profile naming convention for Quantum networks
+OS_PORT_PROFILE_NAME = "openstack-profile-{id}"
+
+# Port profile filter expressions
+PORT_PROFILE_XPATH_FILTER = "/port-profile"
+PORT_PROFILE_NAME_XPATH_FILTER = "/port-profile[name='{name}']"
diff --git a/quantum/plugins/brocade/nos/nosdriver.py b/quantum/plugins/brocade/nos/nosdriver.py
new file mode 100644 (file)
index 0000000..be0c788
--- /dev/null
@@ -0,0 +1,202 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 Brocade Communications System, 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.
+#
+# Authors:
+# Varma Bhupatiraju (vbhupati@#brocade.com)
+# Shiv Haris (sharis@brocade.com)
+
+
+"""
+Brocade NOS Driver implements NETCONF over SSHv2 for
+Quantum network life-cycle management
+"""
+from ncclient import manager
+
+from quantum.openstack.common import log as logging
+from quantum.plugins.brocade.nos import nctemplates as template
+
+
+LOG = logging.getLogger(__name__)
+SSH_PORT = 22
+
+
+def nos_unknown_host_cb(host, fingerprint):
+    """An unknown host callback.
+
+    Returns `True` if it finds the key acceptable,
+    and `False` if not. This default callback for NOS always returns 'True'
+    (i.e. trusts all hosts for now).
+
+    """
+
+    return True
+
+
+class NOSdriver():
+    """NOS NETCONF interface driver for Quantum network.
+
+    Handles life-cycle management of Quantum network (leverages AMPP on NOS)
+
+    """
+
+    def __init__(self):
+        pass
+
+    def connect(self, host, username, password):
+        """Connect via SSH and initialize the NETCONF session."""
+        try:
+            mgr = manager.connect(host=host, port=SSH_PORT,
+                                  username=username, password=password,
+                                  unknown_host_cb=nos_unknown_host_cb)
+        except Exception as e:
+            LOG.debug(_("Connect failed to switch: %s"), e)
+            raise
+
+        LOG.debug(_("Connect success to host %s:%d"), host, SSH_PORT)
+        return mgr
+
+    def create_network(self, host, username, password, net_id):
+        """Creates a new virtual network."""
+
+        name = template.OS_PORT_PROFILE_NAME.format(id=net_id)
+        with self.connect(host, username, password) as mgr:
+            self.create_vlan_interface(mgr, net_id)
+            self.create_port_profile(mgr, name)
+            self.create_vlan_profile_for_port_profile(mgr, name)
+            self.configure_l2_mode_for_vlan_profile(mgr, name)
+            self.configure_trunk_mode_for_vlan_profile(mgr, name)
+            self.configure_allowed_vlans_for_vlan_profile(mgr, name, net_id)
+            self.activate_port_profile(mgr, name)
+
+    def delete_network(self, host, username, password, net_id):
+        """Deletes a virtual network."""
+
+        name = template.OS_PORT_PROFILE_NAME.format(id=net_id)
+        with self.connect(host, username, password) as mgr:
+            self.deactivate_port_profile(mgr, name)
+            self.delete_port_profile(mgr, name)
+            self.delete_vlan_interface(mgr, net_id)
+
+    def associate_mac_to_network(self, host, username, password,
+                                 net_id, mac):
+        """Associates a MAC address to virtual network."""
+
+        name = template.OS_PORT_PROFILE_NAME.format(id=net_id)
+        with self.connect(host, username, password) as mgr:
+            self.associate_mac_to_port_profile(mgr, name, mac)
+
+    def dissociate_mac_from_network(self, host, username, password,
+                                    net_id, mac):
+        """Dissociates a MAC address from virtual network."""
+
+        name = template.OS_PORT_PROFILE_NAME.format(id=net_id)
+        with self.connect(host, username, password) as mgr:
+            self.dissociate_mac_from_port_profile(mgr, name, mac)
+
+    def create_vlan_interface(self, mgr, vlan_id):
+        """Configures a VLAN interface."""
+
+        confstr = template.CREATE_VLAN_INTERFACE.format(vlan_id=vlan_id)
+        mgr.edit_config(target='running', config=confstr)
+
+    def delete_vlan_interface(self, mgr, vlan_id):
+        """Deletes a VLAN interface."""
+
+        confstr = template.DELETE_VLAN_INTERFACE.format(vlan_id=vlan_id)
+        mgr.edit_config(target='running', config=confstr)
+
+    def get_port_profiles(self, mgr):
+        """Retrieves all port profiles."""
+
+        filterstr = template.PORT_PROFILE_XPATH_FILTER
+        response = mgr.get_config(source='running',
+                                  filter=('xpath', filterstr)).data_xml
+        return response
+
+    def get_port_profile(self, mgr, name):
+        """Retrieves a port profile."""
+
+        filterstr = template.PORT_PROFILE_NAME_XPATH_FILTER.format(name=name)
+        response = mgr.get_config(source='running',
+                                  filter=('xpath', filterstr)).data_xml
+        return response
+
+    def create_port_profile(self, mgr, name):
+        """Creates a port profile."""
+
+        confstr = template.CREATE_PORT_PROFILE.format(name=name)
+        mgr.edit_config(target='running', config=confstr)
+
+    def delete_port_profile(self, mgr, name):
+        """Deletes a port profile."""
+
+        confstr = template.DELETE_PORT_PROFILE.format(name=name)
+        mgr.edit_config(target='running', config=confstr)
+
+    def activate_port_profile(self, mgr, name):
+        """Activates a port profile."""
+
+        confstr = template.ACTIVATE_PORT_PROFILE.format(name=name)
+        mgr.edit_config(target='running', config=confstr)
+
+    def deactivate_port_profile(self, mgr, name):
+        """Deactivates a port profile."""
+
+        confstr = template.DEACTIVATE_PORT_PROFILE.format(name=name)
+        mgr.edit_config(target='running', config=confstr)
+
+    def associate_mac_to_port_profile(self, mgr, name, mac_address):
+        """Associates a MAC address to a port profile."""
+
+        confstr = template.ASSOCIATE_MAC_TO_PORT_PROFILE.format(
+            name=name, mac_address=mac_address)
+        mgr.edit_config(target='running', config=confstr)
+
+    def dissociate_mac_from_port_profile(self, mgr, name, mac_address):
+        """Dissociates a MAC address from a port profile."""
+
+        confstr = template.DISSOCIATE_MAC_FROM_PORT_PROFILE.format(
+            name=name, mac_address=mac_address)
+        mgr.edit_config(target='running', config=confstr)
+
+    def create_vlan_profile_for_port_profile(self, mgr, name):
+        """Creates VLAN sub-profile for port profile."""
+
+        confstr = template.CREATE_VLAN_PROFILE_FOR_PORT_PROFILE.format(
+            name=name)
+        mgr.edit_config(target='running', config=confstr)
+
+    def configure_l2_mode_for_vlan_profile(self, mgr, name):
+        """Configures L2 mode for VLAN sub-profile."""
+
+        confstr = template.CONFIGURE_L2_MODE_FOR_VLAN_PROFILE.format(
+            name=name)
+        mgr.edit_config(target='running', config=confstr)
+
+    def configure_trunk_mode_for_vlan_profile(self, mgr, name):
+        """Configures trunk mode for VLAN sub-profile."""
+
+        confstr = template.CONFIGURE_TRUNK_MODE_FOR_VLAN_PROFILE.format(
+            name=name)
+        mgr.edit_config(target='running', config=confstr)
+
+    def configure_allowed_vlans_for_vlan_profile(self, mgr, name, vlan_id):
+        """Configures allowed VLANs for VLAN sub-profile."""
+
+        confstr = template.CONFIGURE_ALLOWED_VLANS_FOR_VLAN_PROFILE.format(
+            name=name, vlan_id=vlan_id)
+        mgr.edit_config(target='running', config=confstr)
diff --git a/quantum/plugins/brocade/tests/README b/quantum/plugins/brocade/tests/README
new file mode 100644 (file)
index 0000000..1607279
--- /dev/null
@@ -0,0 +1,24 @@
+Start the quantum-server with IP address of switch configured in brocade.ini:
+(for configuration instruction please see README.md in the above directory)
+
+nostest.py:
+This tests two things:
+     1. Creates port-profile on the physical switch when a quantum 'network' is created
+     2. Associates the MAC address with the created port-profile
+
+noscli.py:
+    CLI interface to create/delete/associate MAC/dissociate MAC
+    Commands:
+        % noscli.py create <network>
+          (after running check that PP is created on the switch)
+
+        % noscli.py delete <network>
+          (after running check that PP is deleted from the switch)
+
+        % noscli.py associate <network> <mac>
+          (after running check that MAC is associated with PP)
+
+        % noscli.py dissociate <network> <mac>
+          (after running check that MAC is dissociated from the PP)
+
+
diff --git a/quantum/plugins/brocade/tests/noscli.py b/quantum/plugins/brocade/tests/noscli.py
new file mode 100644 (file)
index 0000000..85d02c7
--- /dev/null
@@ -0,0 +1,93 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2013 Brocade Communications 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.
+#
+# Authors:
+# Varma Bhupatiraju (vbhupati@#brocade.com)
+# Shiv Haris (sharis@brocade.com)
+
+
+"""
+Brocade NOS Driver CLI
+"""
+import argparse
+
+from quantum.openstack.common import log as logging
+from quantum.plugins.brocade.nos import nosdriver as nos
+
+LOG = logging.getLogger(__name__)
+
+
+class NOSCli(object):
+
+    def __init__(self, host, username, password):
+        self.host = host
+        self.username = username
+        self.password = password
+        self.driver = nos.NOSdriver()
+
+    def execute(self, cmd):
+        numargs = len(args.otherargs)
+
+        if args.cmd == 'create' and numargs == 1:
+            self._create(args.otherargs[0])
+        elif args.cmd == 'delete' and numargs == 1:
+            self._delete(args.otherargs[0])
+        elif args.cmd == 'associate' and numargs == 2:
+            self._associate(args.otherargs[0], args.otherargs[1])
+        elif args.cmd == 'dissociate' and numargs == 2:
+            self._dissociate(args.otherargs[0], args.otherargs[1])
+        else:
+            print usage_desc
+            exit(0)
+
+    def _create(self, net_id):
+        self.driver.create_network(self.host, self.username, self.password,
+                                   net_id)
+
+    def _delete(self, net_id):
+        self.driver.delete_network(self.host, self.username, self.password,
+                                   net_id)
+
+    def _associate(self, net_id, mac):
+        self.driver.associate_mac_to_network(
+            self.host, self.username, self.password, net_id, mac)
+
+    def _dissociate(self, net_id, mac):
+        self.driver.dissociate_mac_from_network(
+            self.host, self.username, self.password, net_id, mac)
+
+
+usage_desc = """
+Command descriptions:
+
+    create <id>
+    delete <id>
+    associate <id> <mac>
+    dissociate <id> <mac>
+"""
+
+parser = argparse.ArgumentParser(description='process args',
+                                 usage=usage_desc, epilog='foo bar help')
+parser.add_argument('--ip', default='localhost')
+parser.add_argument('--username', default='admin')
+parser.add_argument('--password', default='password')
+parser.add_argument('cmd')
+parser.add_argument('otherargs', nargs='*')
+args = parser.parse_args()
+
+noscli = NOSCli(args.ip, args.username, args.password)
+noscli.execute(args.cmd)
diff --git a/quantum/plugins/brocade/tests/nostest.py b/quantum/plugins/brocade/tests/nostest.py
new file mode 100644 (file)
index 0000000..a181eff
--- /dev/null
@@ -0,0 +1,48 @@
+# Copyright (c) 2013 Brocade Communications 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.
+#
+# Authors:
+# Varma Bhupatiraju (vbhupati@#brocade.com)
+# Shiv Haris (sharis@brocade.com)
+
+
+"""
+Brocade NOS Driver Test
+"""
+import sys
+
+from quantum.plugins.brocade.nos import nosdriver as nos
+
+
+def nostest(host, username, password):
+    # Driver
+    driver = nos.NOSdriver()
+
+    # Quantum operations
+    vlan = 1001
+    mac = '0050.56bf.0001'
+    driver.create_network(host, username, password, vlan)
+    driver.associate_mac_to_network(host, username, password, vlan, mac)
+    driver.dissociate_mac_from_network(host, username, password, vlan, mac)
+    driver.delete_network(host, username, password, vlan)
+
+    # AMPP enumeration
+    with driver.connect(host, username, password) as mgr:
+        print driver.get_port_profiles(mgr)
+        print driver.get_port_profile(mgr, 'default')
+
+
+if __name__ == '__main__':
+    nostest(sys.argv[1], sys.argv[2], sys.argv[3])
diff --git a/quantum/plugins/brocade/vlanbm.py b/quantum/plugins/brocade/vlanbm.py
new file mode 100644 (file)
index 0000000..baf4cbe
--- /dev/null
@@ -0,0 +1,61 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 Brocade Communications System, 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.
+#
+# Authors:
+# Shiv Haris (sharis@brocade.com)
+# Varma Bhupatiraju (vbhupati@#brocade.com)
+
+
+"""
+A Vlan Bitmap class to handle allocation/de-allocation of vlan ids.
+"""
+from quantum.plugins.brocade.db import models as brocade_db
+
+
+MIN_VLAN = 2
+MAX_VLAN = 4094
+
+
+class VlanBitmap(object):
+    """Setup a vlan bitmap for allocation/de-allocation."""
+
+    # Keep track of the vlans that have been allocated/de-allocated
+    # uses a bitmap to do this
+
+    def __init__(self, ctxt):
+        """initialize the vlan as a set."""
+        self.vlans = set(int(net['vlan'])
+                         for net in brocade_db.get_networks(ctxt)
+                         if net['vlan']
+                         )
+
+    def get_next_vlan(self, vlan_id=None):
+        """try to get a specific vlan if requested
+        or get the next vlan.
+        """
+        min_vlan_search = vlan_id or MIN_VLAN
+        max_vlan_search = (vlan_id and vlan_id + 1) or MAX_VLAN
+
+        for vlan in xrange(min_vlan_search, max_vlan_search):
+            if vlan not in self.vlans:
+                self.vlans.add(vlan)
+                return vlan
+
+    def release_vlan(self, vlan_id):
+        """return the vlan to the pool."""
+        if vlan_id in self.vlans:
+            self.vlans.remove(vlan_id)
diff --git a/quantum/tests/unit/brocade/__init__.py b/quantum/tests/unit/brocade/__init__.py
new file mode 100644 (file)
index 0000000..4aeadb8
--- /dev/null
@@ -0,0 +1,17 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 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.
diff --git a/quantum/tests/unit/brocade/test_brocade_db.py b/quantum/tests/unit/brocade/test_brocade_db.py
new file mode 100644 (file)
index 0000000..441dc41
--- /dev/null
@@ -0,0 +1,98 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright (c) 2013 OpenStack, LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""
+Unit test brocade db.
+"""
+import uuid
+
+from quantum import context
+from quantum.plugins.brocade.db import models as brocade_db
+from quantum.tests.unit import test_db_plugin as test_plugin
+
+TEST_VLAN = 1000
+
+
+class TestBrocadeDb(test_plugin.QuantumDbPluginV2TestCase):
+    """Test brocade db functionality"""
+
+    def test_create_network(self):
+        """Test brocade specific network db."""
+
+        net_id = str(uuid.uuid4())
+
+        # Create a network
+        self.context = context.get_admin_context()
+        brocade_db.create_network(self.context, net_id, TEST_VLAN)
+
+        # Get the network and verify
+        net = brocade_db.get_network(self.context, net_id)
+        self.assertEqual(net['id'], net_id)
+        self.assertEqual(int(net['vlan']), TEST_VLAN)
+
+        # Delete the network
+        brocade_db.delete_network(self.context, net['id'])
+
+    def test_create_port(self):
+        """Test brocade specific port db."""
+
+        net_id = str(uuid.uuid4())
+        port_id = str(uuid.uuid4())
+        # port_id is truncated: since the linux-bridge tap device names are
+        # based on truncated port id, this enables port lookups using
+        # tap devices
+        port_id = port_id[0:11]
+        tenant_id = str(uuid.uuid4())
+        admin_state_up = True
+
+        # Create Port
+
+        # To create a port a network must exists, Create a network
+        self.context = context.get_admin_context()
+        brocade_db.create_network(self.context, net_id, TEST_VLAN)
+
+        physical_interface = "em1"
+        brocade_db.create_port(self.context, port_id, net_id,
+                               physical_interface,
+                               TEST_VLAN, tenant_id, admin_state_up)
+
+        port = brocade_db.get_port(self.context, port_id)
+        self.assertEqual(port['port_id'], port_id)
+        self.assertEqual(port['network_id'], net_id)
+        self.assertEqual(port['physical_interface'], physical_interface)
+        self.assertEqual(int(port['vlan_id']), TEST_VLAN)
+        self.assertEqual(port['tenant_id'], tenant_id)
+        self.assertEqual(port['admin_state_up'], admin_state_up)
+
+        admin_state_up = True
+        brocade_db.update_port_state(self.context, port_id, admin_state_up)
+        port = brocade_db.get_port(self.context, port_id)
+        self.assertEqual(port['admin_state_up'], admin_state_up)
+
+        admin_state_up = False
+        brocade_db.update_port_state(self.context, port_id, admin_state_up)
+        port = brocade_db.get_port(self.context, port_id)
+        self.assertEqual(port['admin_state_up'], admin_state_up)
+
+        admin_state_up = True
+        brocade_db.update_port_state(self.context, port_id, admin_state_up)
+        port = brocade_db.get_port(self.context, port_id)
+        self.assertEqual(port['admin_state_up'], admin_state_up)
+
+        # Delete Port
+        brocade_db.delete_port(self.context, port_id)
diff --git a/quantum/tests/unit/brocade/test_brocade_plugin.py b/quantum/tests/unit/brocade/test_brocade_plugin.py
new file mode 100644 (file)
index 0000000..c5e864e
--- /dev/null
@@ -0,0 +1,74 @@
+# Copyright (c) 2012 OpenStack, LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import mock
+
+from quantum.extensions import portbindings
+from quantum.openstack.common import importutils
+from quantum.plugins.brocade import QuantumPlugin as brocade_plugin
+from quantum.tests.unit import _test_extension_portbindings as test_bindings
+from quantum.tests.unit import test_db_plugin as test_plugin
+
+
+PLUGIN_NAME = ('quantum.plugins.brocade.'
+               'QuantumPlugin.BrocadePluginV2')
+NOS_DRIVER = ('quantum.plugins.brocade.'
+              'nos.fake_nosdriver.NOSdriver')
+FAKE_IPADDRESS = '2.2.2.2'
+FAKE_USERNAME = 'user'
+FAKE_PASSWORD = 'password'
+FAKE_PHYSICAL_INTERFACE = 'em1'
+
+
+class BrocadePluginV2TestCase(test_plugin.QuantumDbPluginV2TestCase):
+    _plugin_name = PLUGIN_NAME
+
+    def setUp(self):
+
+        def mocked_brocade_init(self):
+
+            self._switch = {'address': FAKE_IPADDRESS,
+                            'username': FAKE_USERNAME,
+                            'password': FAKE_PASSWORD
+                            }
+            self._driver = importutils.import_object(NOS_DRIVER)
+
+        with mock.patch.object(brocade_plugin.BrocadePluginV2,
+                               'brocade_init', new=mocked_brocade_init):
+            super(BrocadePluginV2TestCase, self).setUp(self._plugin_name)
+
+
+class TestBrocadeBasicGet(test_plugin.TestBasicGet,
+                          BrocadePluginV2TestCase):
+    pass
+
+
+class TestBrocadeV2HTTPResponse(test_plugin.TestV2HTTPResponse,
+                                BrocadePluginV2TestCase):
+    pass
+
+
+class TestBrocadePortsV2(test_plugin.TestPortsV2,
+                         BrocadePluginV2TestCase,
+                         test_bindings.PortBindingsTestCase):
+
+    VIF_TYPE = portbindings.VIF_TYPE_BRIDGE
+    HAS_PORT_FILTER = True
+
+
+class TestBrocadeNetworksV2(test_plugin.TestNetworksV2,
+                            BrocadePluginV2TestCase):
+    pass
diff --git a/quantum/tests/unit/brocade/test_brocade_vlan.py b/quantum/tests/unit/brocade/test_brocade_vlan.py
new file mode 100644 (file)
index 0000000..8c5eb9e
--- /dev/null
@@ -0,0 +1,74 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright (c) 2013 OpenStack, LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""
+Test vlans alloc/dealloc.
+"""
+import unittest2 as unittest
+
+from quantum.db import api as db
+from quantum.openstack.common import context
+from quantum.plugins.brocade import vlanbm as vlan_bitmap
+
+
+class TestVlanBitmap(unittest.TestCase):
+    """exercise Vlan bitmap ."""
+
+    def setUp(self):
+        db.configure_db()
+        self.context = context.get_admin_context()
+        self.context.session = db.get_session()
+
+    def test_vlan(self):
+        """test vlan allocation/de-alloc."""
+
+        self.vbm_ = vlan_bitmap.VlanBitmap(self.context)
+        vlan_id = self.vbm_.get_next_vlan(None)
+
+        # First vlan is always 2
+        self.assertEqual(vlan_id, 2)
+
+        # next vlan is always 3
+        vlan_id = self.vbm_.get_next_vlan(None)
+        self.assertEqual(vlan_id, 3)
+
+        # get a specific vlan i.e. 4
+        vlan_id = self.vbm_.get_next_vlan(4)
+        self.assertEqual(vlan_id, 4)
+
+        # get a specific vlan i.e. 5
+        vlan_id = self.vbm_.get_next_vlan(5)
+        self.assertEqual(vlan_id, 5)
+
+        # Skip 6
+
+        # get a specific vlan i.e. 7
+        vlan_id = self.vbm_.get_next_vlan(7)
+        self.assertEqual(vlan_id, 7)
+
+        # get a specific vlan i.e. 1900
+        vlan_id = self.vbm_.get_next_vlan(1900)
+        self.assertEqual(vlan_id, 1900)
+
+        # Release 4 and get next again
+        self.vbm_.release_vlan(4)
+        vlan_id = self.vbm_.get_next_vlan(None)
+        self.assertEqual(vlan_id, 4)
+
+    def tearDown(self):
+        db.clear_db()
index b46dbb5be4f948434f3694a3a45bc077991eba0c..cc2e399c47f8bd9e98d53e16be25f3e062dfbd82 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -45,6 +45,7 @@ init_path = 'etc/init.d'
 rootwrap_path = 'etc/quantum/rootwrap.d'
 ovs_plugin_config_path = 'etc/quantum/plugins/openvswitch'
 bigswitch_plugin_config_path = 'etc/quantum/plugins/bigswitch'
+brocade_plugin_config_path = 'etc/quantum/plugins/brocade'
 cisco_plugin_config_path = 'etc/quantum/plugins/cisco'
 linuxbridge_plugin_config_path = 'etc/quantum/plugins/linuxbridge'
 nvp_plugin_config_path = 'etc/quantum/plugins/nicira'
@@ -94,6 +95,8 @@ else:
              'etc/quantum/plugins/cisco/db_conn.ini']),
         (bigswitch_plugin_config_path,
             ['etc/quantum/plugins/bigswitch/restproxy.ini']),
+        (brocade_plugin_config_path,
+            ['etc/quantum/plugins/brocade/brocade.ini']),
         (linuxbridge_plugin_config_path,
             ['etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini']),
         (nvp_plugin_config_path,