--- /dev/null
+[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
--- /dev/null
+# 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
--- /dev/null
+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)
+
--- /dev/null
+# 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.
--- /dev/null
+# 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.
--- /dev/null
+# 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})
--- /dev/null
+# 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.
--- /dev/null
+# 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
--- /dev/null
+# 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}']"
--- /dev/null
+# 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)
--- /dev/null
+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)
+
+
--- /dev/null
+#!/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)
--- /dev/null
+# 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])
--- /dev/null
+# 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)
--- /dev/null
+# 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.
--- /dev/null
+# 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)
--- /dev/null
+# 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
--- /dev/null
+# 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()
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'
'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,