From cf133047c8ad5b0344ef0bebbca146927411e11c Mon Sep 17 00:00:00 2001 From: Shiv Haris Date: Wed, 23 Jan 2013 03:33:55 +0000 Subject: [PATCH] Adds Brocade Plugin implementation 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 --- etc/quantum/plugins/brocade/brocade.ini | 48 ++ quantum/plugins/brocade/QuantumPlugin.py | 453 ++++++++++++++++++ quantum/plugins/brocade/README.md | 112 +++++ quantum/plugins/brocade/__init__.py | 16 + quantum/plugins/brocade/db/__init__.py | 16 + quantum/plugins/brocade/db/models.py | 152 ++++++ quantum/plugins/brocade/nos/__init__.py | 16 + quantum/plugins/brocade/nos/fake_nosdriver.py | 118 +++++ quantum/plugins/brocade/nos/nctemplates.py | 204 ++++++++ quantum/plugins/brocade/nos/nosdriver.py | 202 ++++++++ quantum/plugins/brocade/tests/README | 24 + quantum/plugins/brocade/tests/noscli.py | 93 ++++ quantum/plugins/brocade/tests/nostest.py | 48 ++ quantum/plugins/brocade/vlanbm.py | 61 +++ quantum/tests/unit/brocade/__init__.py | 17 + quantum/tests/unit/brocade/test_brocade_db.py | 98 ++++ .../tests/unit/brocade/test_brocade_plugin.py | 74 +++ .../tests/unit/brocade/test_brocade_vlan.py | 74 +++ setup.py | 3 + 19 files changed, 1829 insertions(+) create mode 100644 etc/quantum/plugins/brocade/brocade.ini create mode 100644 quantum/plugins/brocade/QuantumPlugin.py create mode 100644 quantum/plugins/brocade/README.md create mode 100644 quantum/plugins/brocade/__init__.py create mode 100644 quantum/plugins/brocade/db/__init__.py create mode 100644 quantum/plugins/brocade/db/models.py create mode 100644 quantum/plugins/brocade/nos/__init__.py create mode 100644 quantum/plugins/brocade/nos/fake_nosdriver.py create mode 100644 quantum/plugins/brocade/nos/nctemplates.py create mode 100644 quantum/plugins/brocade/nos/nosdriver.py create mode 100644 quantum/plugins/brocade/tests/README create mode 100644 quantum/plugins/brocade/tests/noscli.py create mode 100644 quantum/plugins/brocade/tests/nostest.py create mode 100644 quantum/plugins/brocade/vlanbm.py create mode 100644 quantum/tests/unit/brocade/__init__.py create mode 100644 quantum/tests/unit/brocade/test_brocade_db.py create mode 100644 quantum/tests/unit/brocade/test_brocade_plugin.py create mode 100644 quantum/tests/unit/brocade/test_brocade_vlan.py diff --git a/etc/quantum/plugins/brocade/brocade.ini b/etc/quantum/plugins/brocade/brocade.ini new file mode 100644 index 0000000000..58589f8ceb --- /dev/null +++ b/etc/quantum/plugins/brocade/brocade.ini @@ -0,0 +1,48 @@ +[SWITCH] +# username = +# password = +# 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 = +# +# Example: +# physical_interface = physnet1 + +[VLANS] +# network_vlan_ranges = :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 = : +# +# Example: +# physical_interface_mappings = physnet1:em1 diff --git a/quantum/plugins/brocade/QuantumPlugin.py b/quantum/plugins/brocade/QuantumPlugin.py new file mode 100644 index 0000000000..6e1dd6156c --- /dev/null +++ b/quantum/plugins/brocade/QuantumPlugin.py @@ -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 index 0000000000..645367cc6b --- /dev/null +++ b/quantum/plugins/brocade/README.md @@ -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 = + 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 index 0000000000..c22f863e3c --- /dev/null +++ b/quantum/plugins/brocade/__init__.py @@ -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 index 0000000000..c22f863e3c --- /dev/null +++ b/quantum/plugins/brocade/db/__init__.py @@ -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 index 0000000000..ee2dd4b25d --- /dev/null +++ b/quantum/plugins/brocade/db/models.py @@ -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 index 0000000000..9d4562b0d2 --- /dev/null +++ b/quantum/plugins/brocade/nos/__init__.py @@ -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 index 0000000000..495d23a3c8 --- /dev/null +++ b/quantum/plugins/brocade/nos/fake_nosdriver.py @@ -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 index 0000000000..1bbfc2218b --- /dev/null +++ b/quantum/plugins/brocade/nos/nctemplates.py @@ -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 = """ + + + + + {vlan_id} + + + + +""" + +# Delete VLAN (vlan_id) +DELETE_VLAN_INTERFACE = """ + + + + + {vlan_id} + + + + +""" + +# +# AMPP Life-cycle Management Configuration Commands +# + +# Create AMPP port-profile (port_profile_name) +CREATE_PORT_PROFILE = """ + + + {name} + + +""" + +# Create VLAN sub-profile for port-profile (port_profile_name) +CREATE_VLAN_PROFILE_FOR_PORT_PROFILE = """ + + + {name} + + + +""" + +# Configure L2 mode for VLAN sub-profile (port_profile_name) +CONFIGURE_L2_MODE_FOR_VLAN_PROFILE = """ + + + {name} + + + + + +""" + +# Configure trunk mode for VLAN sub-profile (port_profile_name) +CONFIGURE_TRUNK_MODE_FOR_VLAN_PROFILE = """ + + + {name} + + + + trunk + + + + + +""" + +# Configure allowed VLANs for VLAN sub-profile +# (port_profile_name, allowed_vlan, native_vlan) +CONFIGURE_ALLOWED_VLANS_FOR_VLAN_PROFILE = """ + + + {name} + + + + + + {vlan_id} + + + {vlan_id} + + + + + +""" + +# Delete port-profile (port_profile_name) +DELETE_PORT_PROFILE = """ + + + {name} + + +""" + +# Activate port-profile (port_profile_name) +ACTIVATE_PORT_PROFILE = """ + + + + {name} + + + + +""" + +# Deactivate port-profile (port_profile_name) +DEACTIVATE_PORT_PROFILE = """ + + + + {name} + + + + +""" + +# Associate MAC address to port-profile (port_profile_name, mac_address) +ASSOCIATE_MAC_TO_PORT_PROFILE = """ + + + + {name} + + {mac_address} + + + + +""" + +# Dissociate MAC address from port-profile (port_profile_name, mac_address) +DISSOCIATE_MAC_FROM_PORT_PROFILE = """ + + + + {name} + + {mac_address} + + + + +""" + +# +# 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 index 0000000000..be0c7880fa --- /dev/null +++ b/quantum/plugins/brocade/nos/nosdriver.py @@ -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 index 0000000000..16072799a1 --- /dev/null +++ b/quantum/plugins/brocade/tests/README @@ -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 + (after running check that PP is created on the switch) + + % noscli.py delete + (after running check that PP is deleted from the switch) + + % noscli.py associate + (after running check that MAC is associated with PP) + + % noscli.py dissociate + (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 index 0000000000..85d02c7569 --- /dev/null +++ b/quantum/plugins/brocade/tests/noscli.py @@ -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 + delete + associate + dissociate +""" + +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 index 0000000000..a181eff70e --- /dev/null +++ b/quantum/plugins/brocade/tests/nostest.py @@ -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 index 0000000000..baf4cbea6e --- /dev/null +++ b/quantum/plugins/brocade/vlanbm.py @@ -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 index 0000000000..4aeadb8bdc --- /dev/null +++ b/quantum/tests/unit/brocade/__init__.py @@ -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 index 0000000000..441dc41bd7 --- /dev/null +++ b/quantum/tests/unit/brocade/test_brocade_db.py @@ -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 index 0000000000..c5e864e40b --- /dev/null +++ b/quantum/tests/unit/brocade/test_brocade_plugin.py @@ -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 index 0000000000..8c5eb9eef6 --- /dev/null +++ b/quantum/tests/unit/brocade/test_brocade_vlan.py @@ -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() diff --git a/setup.py b/setup.py index b46dbb5be4..cc2e399c47 100644 --- 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,