From: Shiv Haris Date: Thu, 5 Dec 2013 02:02:17 +0000 (-0800) Subject: Implementaion of Mechanism driver for X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=0b5a2fac5d0f7898ac89f64d11b263dd98dc2a50;p=openstack-build%2Fneutron-build.git Implementaion of Mechanism driver for Brocade VDX cluster of switches Change-Id: Ic1649f7cee73a41f286e12d8ba6ca30be6261cfe Implements: blueprint brocade-ml2-mechanism-driver --- diff --git a/etc/neutron/plugins/ml2/ml2_conf.ini b/etc/neutron/plugins/ml2/ml2_conf.ini index 55af11f7b..b8b766f0d 100644 --- a/etc/neutron/plugins/ml2/ml2_conf.ini +++ b/etc/neutron/plugins/ml2/ml2_conf.ini @@ -18,6 +18,8 @@ # Example: mechanism drivers = openvswitch,mlnx # Example: mechanism_drivers = arista # Example: mechanism_drivers = cisco,logger +# Example: mechanism_drivers = openvswitch,brocade +# Example: mechanism_drivers = linuxbridge,brocade [ml2_type_flat] # (ListOpt) List of physical_network names with which flat networks diff --git a/etc/neutron/plugins/ml2/ml2_conf_brocade.ini b/etc/neutron/plugins/ml2/ml2_conf_brocade.ini new file mode 100644 index 000000000..66987e991 --- /dev/null +++ b/etc/neutron/plugins/ml2/ml2_conf_brocade.ini @@ -0,0 +1,13 @@ +[ml2_brocade] +# username = +# password = +# address = +# ostype = NOS +# physical_networks = physnet1,physnet2 +# +# Example: +# username = admin +# password = password +# address = 10.24.84.38 +# ostype = NOS +# physical_networks = physnet1,physnet2 diff --git a/neutron/db/migration/alembic_migrations/versions/492a106273f8_brocade_ml2_mech_dri.py b/neutron/db/migration/alembic_migrations/versions/492a106273f8_brocade_ml2_mech_dri.py new file mode 100644 index 000000000..f8bf7995b --- /dev/null +++ b/neutron/db/migration/alembic_migrations/versions/492a106273f8_brocade_ml2_mech_dri.py @@ -0,0 +1,70 @@ +# Copyright 2014 OpenStack Foundation +# +# 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. +# + +"""Brocade ML2 Mech. Driver + +Revision ID: 492a106273f8 +Revises: fcac4c42e2cc +Create Date: 2014-03-03 15:35:46.974523 + +""" + +# revision identifiers, used by Alembic. +revision = '492a106273f8' +down_revision = 'fcac4c42e2cc' + +# Change to ['*'] if this migration applies to all plugins + +migration_for_plugins = [ + 'neutron.plugins.ml2.plugin.Ml2Plugin' +] + +from alembic import op +import sqlalchemy as sa + + +from neutron.db import migration + + +def upgrade(active_plugins=None, options=None): + if not migration.should_run(active_plugins, migration_for_plugins): + return + + op.create_table( + 'ml2_brocadenetworks', + sa.Column('id', sa.String(length=36), nullable=False), + sa.Column('vlan', sa.String(length=10), nullable=True), + sa.Column('segment_id', sa.String(length=36), nullable=True), + sa.Column('network_type', sa.String(length=10), nullable=True), + sa.Column('tenant_id', sa.String(length=255), nullable=True), + sa.PrimaryKeyConstraint('id')) + + op.create_table( + 'ml2_brocadeports', + sa.Column('id', sa.String(length=36), nullable=False), + sa.Column('network_id', sa.String(length=36), nullable=False), + sa.Column('admin_state_up', sa.Boolean()), + sa.Column('physical_interface', sa.String(length=36), nullable=True), + sa.Column('vlan_id', sa.String(length=36), nullable=True), + sa.Column('tenant_id', sa.String(length=255), nullable=True), + sa.PrimaryKeyConstraint('id')) + + +def downgrade(active_plugins=None, options=None): + if not migration.should_run(active_plugins, migration_for_plugins): + return + + op.drop_table('ml2_brocadenetworks') + op.drop_table('ml2_brocadeports') diff --git a/neutron/plugins/ml2/drivers/brocade/README.md b/neutron/plugins/ml2/drivers/brocade/README.md new file mode 100644 index 000000000..73c194f20 --- /dev/null +++ b/neutron/plugins/ml2/drivers/brocade/README.md @@ -0,0 +1,60 @@ +Brocade ML2 Mechanism driver from ML2 plugin +============================================ + +* up-to-date version of these instructions are located at: + http://50.56.236.34/docs/brocade-ml2-mechanism.txt +* N.B.: Please see Prerequisites section regarding ncclient (netconf client library) +* Supports VCS (Virtual Cluster of Switches) +* Issues/Questions/Bugs: sharis@brocade.com + + + + 1. VDX 67xx series of switches + 2. VDX 87xx series of switches + +ML2 plugin requires mechanism driver to support configuring of hardware switches. +Brocade Mechanism for ML2 uses NETCONF at the backend to configure the Brocade switch. +Currently the mechanism drivers support VLANs only. + + +------------+ +------------+ +-------------+ + | | | | | | + Neutron | | | | | Brocade | + v2.0 | Openstack | | Brocade | NETCONF | VCS Switch | + ----+ Neutron +--------+ Mechanism +----------+ | + | ML2 | | Driver | | VDX 67xx | + | Plugin | | | | VDX 87xx | + | | | | | | + | | | | | | + +------------+ +------------+ +-------------+ + + +Configuration + +In order to use this mechnism the brocade configuration file needs to be edited with the appropriate +configuration information: + + % cat /etc/neutron/plugins/ml2/ml2_conf_brocade.ini + [switch] + username = admin + password = password + address = + ostype = NOS + physical_networks = phys1 + +Additionally the brocade mechanism driver needs to be enabled from the ml2 config file: + + % cat /etc/neutron/plugins/ml2/ml2_conf.ini + + [ml2] + tenant_network_types = vlan + type_drivers = local,flat,vlan,gre,vxlan + mechanism_drivers = openvswitch,brocade + # OR mechanism_drivers = openvswitch,linuxbridge,hyperv,brocade + ... + ... + ... + + +Required L2 Agent + +This mechanism driver works in conjuction with an L2 Agent. The agent should be loaded as well in order for it to configure the virtual network int the host machine. Please see the configuration above. Atleast one of linuxbridge or openvswitch must be specified. diff --git a/neutron/plugins/ml2/drivers/brocade/__init__.py b/neutron/plugins/ml2/drivers/brocade/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/neutron/plugins/ml2/drivers/brocade/db/__init__.py b/neutron/plugins/ml2/drivers/brocade/db/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/neutron/plugins/ml2/drivers/brocade/db/models.py b/neutron/plugins/ml2/drivers/brocade/db/models.py new file mode 100644 index 000000000..249540527 --- /dev/null +++ b/neutron/plugins/ml2/drivers/brocade/db/models.py @@ -0,0 +1,139 @@ +# Copyright 2014 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 neutron.db import model_base +from neutron.db import models_v2 + + +class ML2_BrocadeNetwork(model_base.BASEV2, models_v2.HasId, + models_v2.HasTenant): + """Schema for brocade network.""" + + vlan = sa.Column(sa.String(10)) + segment_id = sa.Column(sa.String(36)) + network_type = sa.Column(sa.String(10)) + + +class ML2_BrocadePort(model_base.BASEV2, models_v2.HasId, + models_v2.HasTenant): + """Schema for brocade port.""" + + network_id = sa.Column(sa.String(36), + sa.ForeignKey("ml2_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)) + + +def create_network(context, net_id, vlan, segment_id, network_type, tenant_id): + """Create a brocade specific network/port-profiles.""" + + # only network_type of vlan is supported + session = context.session + with session.begin(subtransactions=True): + net = get_network(context, net_id, None) + if not net: + net = ML2_BrocadeNetwork(id=net_id, vlan=vlan, + segment_id=segment_id, + network_type='vlan', + tenant_id=tenant_id) + 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 = get_network(context, net_id, None) + if net: + session.delete(net) + + +def get_network(context, net_id, fields=None): + """Get brocade specific network, with vlan extension.""" + + session = context.session + return session.query(ML2_BrocadeNetwork).filter_by(id=net_id).first() + + +def get_networks(context, filters=None, fields=None): + """Get all brocade specific networks.""" + + session = context.session + return session.query(ML2_BrocadeNetwork).all() + + +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.""" + + session = context.session + with session.begin(subtransactions=True): + port = get_port(context, port_id) + if not port: + port = ML2_BrocadePort(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.""" + + session = context.session + return session.query(ML2_BrocadePort).filter_by(id=port_id).first() + + +def get_ports(context, network_id=None): + """get a brocade specific port.""" + + session = context.session + return session.query(ML2_BrocadePort).filter_by( + network_id=network_id).all() + + +def delete_port(context, port_id): + """delete brocade specific port.""" + + session = context.session + with session.begin(subtransactions=True): + port = get_port(context, port_id) + if port: + session.delete(port) + + +def update_port_state(context, port_id, admin_state_up): + """Update port attributes.""" + + session = context.session + with session.begin(subtransactions=True): + session.query(ML2_BrocadePort).filter_by( + id=port_id).update({'admin_state_up': admin_state_up}) diff --git a/neutron/plugins/ml2/drivers/brocade/mechanism_brocade.py b/neutron/plugins/ml2/drivers/brocade/mechanism_brocade.py new file mode 100644 index 000000000..a0f4dcc6c --- /dev/null +++ b/neutron/plugins/ml2/drivers/brocade/mechanism_brocade.py @@ -0,0 +1,384 @@ +# Copyright 2014 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. +# +# Author: +# Shiv Haris (shivharis@hotmail.com) + + +"""Implentation of Brocade ML2 Mechanism driver for ML2 Plugin.""" + +from oslo.config import cfg + +from neutron.openstack.common import importutils +from neutron.openstack.common import log as logging +from neutron.plugins.ml2.drivers.brocade.db import models as brocade_db + +LOG = logging.getLogger(__name__) +MECHANISM_VERSION = 0.9 +NOS_DRIVER = 'neutron.plugins.ml2.drivers.brocade.nos.nosdriver.NOSdriver' + +ML2_BROCADE = [cfg.StrOpt('address', default='', + help=_('The address of the host to SSH to')), + cfg.StrOpt('username', default='admin', + help=_('The SSH username to use')), + cfg.StrOpt('password', default='password', secret=True, + help=_('The SSH password to use')), + cfg.StrOpt('physical_networks', default='', + help=_('Allowed physical networks')), + cfg.StrOpt('ostype', default='NOS', + help=_('Unused')) + ] + +cfg.CONF.register_opts(ML2_BROCADE, "ml2_brocade") + + +class BrocadeMechanism(): + """ML2 Mechanism driver for Brocade VDX switches. This is the upper + layer driver class that interfaces to lower layer (NETCONF) below. + + """ + + def __init__(self): + self._driver = None + self._physical_networks = None + self._switch = None + self.initialize() + + def initialize(self): + """Initilize of variables needed by this class.""" + + self._physical_networks = cfg.CONF.ml2_brocade.physical_networks + self.brocade_init() + + def brocade_init(self): + """Brocade specific initialization for this class.""" + + self._switch = {'address': cfg.CONF.ml2_brocade.address, + 'username': cfg.CONF.ml2_brocade.username, + 'password': cfg.CONF.ml2_brocade.password + } + self._driver = importutils.import_object(NOS_DRIVER) + + def create_network_precommit(self, mech_context): + """Create Network in the mechanism specific database table.""" + + network = mech_context.current + context = mech_context._plugin_context + tenant_id = network['tenant_id'] + network_id = network['id'] + + segments = mech_context.network_segments + # currently supports only one segment per network + segment = segments[0] + + network_type = segment['network_type'] + vlan_id = segment['segmentation_id'] + segment_id = segment['id'] + + if segment['physical_network'] not in self._physical_networks: + raise Exception( + _("Brocade Mechanism: failed to create network, " + "network cannot be created in the configured " + "physical network")) + + if network_type != 'vlan': + raise Exception( + _("Brocade Mechanism: failed to create network, " + "only network type vlan is supported")) + + try: + brocade_db.create_network(context, network_id, vlan_id, + segment_id, network_type, tenant_id) + except Exception: + LOG.exception( + _("Brocade Mechanism: failed to create network in db")) + raise Exception( + _("Brocade Mechanism: create_network_precommit failed")) + + LOG.info(_("create network (precommit): %(network_id)s " + "of network type = %(network_type)s " + "with vlan = %(vlan_id)s " + "for tenant %(tenant_id)s"), + {'network_id': network_id, + 'network_type': network_type, + 'vlan_id': vlan_id, + 'tenant_id': tenant_id}) + + def create_network_postcommit(self, mech_context): + """Create Network as a portprofile on the switch.""" + + LOG.debug(_("create_network_postcommit: called")) + + network = mech_context.current + # use network_id to get the network attributes + # ONLY depend on our db for getting back network attributes + # this is so we can replay postcommit from db + context = mech_context._plugin_context + + network_id = network['id'] + network = brocade_db.get_network(context, network_id) + network_type = network['network_type'] + tenant_id = network['tenant_id'] + vlan_id = network['vlan'] + + try: + self._driver.create_network(self._switch['address'], + self._switch['username'], + self._switch['password'], + vlan_id) + except Exception: + LOG.exception(_("Brocade NOS driver: failed in create network")) + brocade_db.delete_network(context, network_id) + raise Exception( + _("Brocade Mechanism: create_network_postcommmit failed")) + + LOG.info(_("created network (postcommit): %(network_id)s" + " of network type = %(network_type)s" + " with vlan = %(vlan_id)s" + " for tenant %(tenant_id)s"), + {'network_id': network_id, + 'network_type': network_type, + 'vlan_id': vlan_id, + 'tenant_id': tenant_id}) + + def delete_network_precommit(self, mech_context): + """Delete Network from the plugin specific database table.""" + + LOG.debug(_("delete_network_precommit: called")) + + network = mech_context.current + network_id = network['id'] + vlan_id = network['provider:segmentation_id'] + tenant_id = network['tenant_id'] + + context = mech_context._plugin_context + + try: + brocade_db.delete_network(context, network_id) + except Exception: + LOG.exception( + _("Brocade Mechanism: failed to delete network in db")) + raise Exception( + _("Brocade Mechanism: delete_network_precommit failed")) + + LOG.info(_("delete network (precommit): %(network_id)s" + " with vlan = %(vlan_id)s" + " for tenant %(tenant_id)s"), + {'network_id': network_id, + 'vlan_id': vlan_id, + 'tenant_id': tenant_id}) + + def delete_network_postcommit(self, mech_context): + """Delete network which translates to removng portprofile + from the switch. + """ + + LOG.debug(_("delete_network_postcommit: called")) + network = mech_context.current + network_id = network['id'] + vlan_id = network['provider:segmentation_id'] + tenant_id = network['tenant_id'] + + try: + self._driver.delete_network(self._switch['address'], + self._switch['username'], + self._switch['password'], + vlan_id) + except Exception: + LOG.exception(_("Brocade NOS driver: failed to delete network")) + raise Exception( + _("Brocade switch exception, " + "delete_network_postcommit failed")) + + LOG.info(_("delete network (postcommit): %(network_id)s" + " with vlan = %(vlan_id)s" + " for tenant %(tenant_id)s"), + {'network_id': network_id, + 'vlan_id': vlan_id, + 'tenant_id': tenant_id}) + + def update_network_precommit(self, mech_context): + """Noop now, it is left here for future.""" + pass + + def update_network_postcommit(self, mech_context): + """Noop now, it is left here for future.""" + pass + + def create_port_precommit(self, mech_context): + """Create logical port on the switch (db update).""" + + LOG.debug(_("create_port_precommit: called")) + + port = mech_context.current + port_id = port['id'] + network_id = port['network_id'] + tenant_id = port['tenant_id'] + admin_state_up = port['admin_state_up'] + + context = mech_context._plugin_context + + network = brocade_db.get_network(context, network_id) + vlan_id = network['vlan'] + + try: + brocade_db.create_port(context, port_id, network_id, + None, + vlan_id, tenant_id, admin_state_up) + except Exception: + LOG.exception(_("Brocade Mechanism: failed to create port in db")) + raise Exception( + _("Brocade Mechanism: create_port_precommit failed")) + + def create_port_postcommit(self, mech_context): + """Associate the assigned MAC address to the portprofile.""" + + LOG.debug(_("create_port_postcommit: called")) + + port = mech_context.current + port_id = port['id'] + network_id = port['network_id'] + tenant_id = port['tenant_id'] + + context = mech_context._plugin_context + + network = brocade_db.get_network(context, network_id) + vlan_id = network['vlan'] + + interface_mac = port['mac_address'] + + # 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(self._switch['address'], + self._switch['username'], + self._switch['password'], + vlan_id, + mac) + except Exception: + LOG.exception( + _("Brocade NOS driver: failed to associate mac %s") + % interface_mac) + raise Exception( + _("Brocade switch exception: create_port_postcommit failed")) + + LOG.info( + _("created port (postcommit): port_id=%(port_id)s" + " network_id=%(network_id)s tenant_id=%(tenant_id)s"), + {'port_id': port_id, + 'network_id': network_id, 'tenant_id': tenant_id}) + + def delete_port_precommit(self, mech_context): + """Delete logical port on the switch (db update).""" + + LOG.debug(_("delete_port_precommit: called")) + port = mech_context.current + port_id = port['id'] + + context = mech_context._plugin_context + + try: + brocade_db.delete_port(context, port_id) + except Exception: + LOG.exception(_("Brocade Mechanism: failed to delete port in db")) + raise Exception( + _("Brocade Mechanism: delete_port_precommit failed")) + + def delete_port_postcommit(self, mech_context): + """Dissociate MAC address from the portprofile.""" + + LOG.debug(_("delete_port_postcommit: called")) + port = mech_context.current + port_id = port['id'] + network_id = port['network_id'] + tenant_id = port['tenant_id'] + + context = mech_context._plugin_context + + network = brocade_db.get_network(context, network_id) + vlan_id = network['vlan'] + + interface_mac = port['mac_address'] + + # convert mac format: xx:xx:xx:xx:xx:xx -> xxxx.xxxx.xxxx + mac = self.mac_reformat_62to34(interface_mac) + try: + self._driver.dissociate_mac_from_network( + self._switch['address'], + self._switch['username'], + self._switch['password'], + vlan_id, + mac) + except Exception: + LOG.exception( + _("Brocade NOS driver: failed to dissociate MAC %s") % + interface_mac) + raise Exception( + _("Brocade switch exception, delete_port_postcommit failed")) + + LOG.info( + _("delete port (postcommit): port_id=%(port_id)s" + " network_id=%(network_id)s tenant_id=%(tenant_id)s"), + {'port_id': port_id, + 'network_id': network_id, 'tenant_id': tenant_id}) + + def update_port_precommit(self, mech_context): + """Noop now, it is left here for future.""" + LOG.debug(_("update_port_precommit(self: called")) + + def update_port_postcommit(self, mech_context): + """Noop now, it is left here for future.""" + LOG.debug(_("update_port_postcommit: called")) + + def create_subnet_precommit(self, mech_context): + """Noop now, it is left here for future.""" + LOG.debug(_("create_subnetwork_precommit: called")) + + def create_subnet_postcommit(self, mech_context): + """Noop now, it is left here for future.""" + LOG.debug(_("create_subnetwork_postcommit: called")) + + def delete_subnet_precommit(self, mech_context): + """Noop now, it is left here for future.""" + LOG.debug(_("delete_subnetwork_precommit: called")) + + def delete_subnet_postcommit(self, mech_context): + """Noop now, it is left here for future.""" + LOG.debug(_("delete_subnetwork_postcommit: called")) + + def update_subnet_precommit(self, mech_context): + """Noop now, it is left here for future.""" + LOG.debug(_("update_subnet_precommit(self: called")) + + def update_subnet_postcommit(self, mech_context): + """Noop now, it is left here for future.""" + LOG.debug(_("update_subnet_postcommit: called")) + + @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/neutron/plugins/ml2/drivers/brocade/nos/__init__.py b/neutron/plugins/ml2/drivers/brocade/nos/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/neutron/plugins/ml2/drivers/brocade/nos/nctemplates.py b/neutron/plugins/ml2/drivers/brocade/nos/nctemplates.py new file mode 100644 index 000000000..dbf7575de --- /dev/null +++ b/neutron/plugins/ml2/drivers/brocade/nos/nctemplates.py @@ -0,0 +1,197 @@ +# Copyright (c) 2014 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} + + + + + + + +""" + +# 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} + + + + +""" + +# +# Constants +# + +# Port profile naming convention for Neutron 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/neutron/plugins/ml2/drivers/brocade/nos/nosdriver.py b/neutron/plugins/ml2/drivers/brocade/nos/nosdriver.py new file mode 100644 index 000000000..26b37f63f --- /dev/null +++ b/neutron/plugins/ml2/drivers/brocade/nos/nosdriver.py @@ -0,0 +1,236 @@ +# Copyright 2014 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 (shivharis@hotmail.com) + + +"""Brocade NOS Driver implements NETCONF over SSHv2 for +Neutron network life-cycle management. +""" + +from ncclient import manager + +from neutron.openstack.common import excutils +from neutron.openstack.common import log as logging +from neutron.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 Neutron network. + + Handles life-cycle management of Neutron network (leverages AMPP on NOS) + """ + + def __init__(self): + self.mgr = None + + def connect(self, host, username, password): + """Connect via SSH and initialize the NETCONF session.""" + + # Use the persisted NETCONF connection + if self.mgr and self.mgr.connected: + return self.mgr + + # check if someone forgot to edit the conf file with real values + if host == '': + raise Exception(_("Brocade Switch IP address is not set, " + "check config ml2_conf_brocade.ini file")) + + # Open new NETCONF connection + try: + self.mgr = manager.connect(host=host, port=SSH_PORT, + username=username, password=password, + unknown_host_cb=nos_unknown_host_cb) + except Exception: + LOG.exception(_("Connect failed to switch")) + raise + + LOG.debug(_("Connect success to host %(host)s:%(ssh_port)d"), + dict(host=host, ssh_port=SSH_PORT)) + return self.mgr + + def close_session(self): + """Close NETCONF session.""" + if self.mgr: + self.mgr.close_session() + self.mgr = None + + def create_network(self, host, username, password, net_id): + """Creates a new virtual network.""" + + name = template.OS_PORT_PROFILE_NAME.format(id=net_id) + try: + mgr = self.connect(host, username, password) + 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) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_("NETCONF error")) + self.close_session() + + def delete_network(self, host, username, password, net_id): + """Deletes a virtual network.""" + + name = template.OS_PORT_PROFILE_NAME.format(id=net_id) + try: + mgr = self.connect(host, username, password) + self.deactivate_port_profile(mgr, name) + self.delete_port_profile(mgr, name) + self.delete_vlan_interface(mgr, net_id) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_("NETCONF error")) + self.close_session() + + 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) + try: + mgr = self.connect(host, username, password) + self.associate_mac_to_port_profile(mgr, name, mac) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_("NETCONF error")) + self.close_session() + + 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) + try: + mgr = self.connect(host, username, password) + self.dissociate_mac_from_port_profile(mgr, name, mac) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_("NETCONF error")) + self.close_session() + + 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/neutron/tests/unit/ml2/drivers/brocade/__init__.py b/neutron/tests/unit/ml2/drivers/brocade/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/neutron/tests/unit/ml2/drivers/brocade/test_brocade_mechanism_driver.py b/neutron/tests/unit/ml2/drivers/brocade/test_brocade_mechanism_driver.py new file mode 100644 index 000000000..439f42899 --- /dev/null +++ b/neutron/tests/unit/ml2/drivers/brocade/test_brocade_mechanism_driver.py @@ -0,0 +1,71 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright (c) 2013 OpenStack Foundation +# +# 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 neutron.openstack.common import importutils +from neutron.openstack.common import log as logging +from neutron.plugins.ml2 import config as ml2_config +from neutron.plugins.ml2.drivers.brocade import (mechanism_brocade + as brocademechanism) +from neutron.tests.unit import test_db_plugin + +LOG = logging.getLogger(__name__) +MECHANISM_NAME = ('neutron.plugins.ml2.' + 'drivers.brocade.mechanism_brocade.BrocadeMechanism') + + +class TestBrocadeMechDriverV2(test_db_plugin.NeutronDbPluginV2TestCase): + """Test Brocade VCS/VDX mechanism driver. + """ + + _mechanism_name = MECHANISM_NAME + + def setUp(self): + + _mechanism_name = MECHANISM_NAME + + ml2_opts = { + 'mechanism_drivers': ['brocade'], + 'tenant_network_types': ['vlan']} + + for opt, val in ml2_opts.items(): + ml2_config.cfg.CONF.set_override(opt, val, 'ml2') + + self.addCleanup(ml2_config.cfg.CONF.reset) + + def mocked_brocade_init(self): + self._driver = mock.MagicMock() + + with mock.patch.object(brocademechanism.BrocadeMechanism, + 'brocade_init', new=mocked_brocade_init): + super(TestBrocadeMechDriverV2, self).setUp() + self.mechanism_driver = importutils.import_object(_mechanism_name) + + +class TestBrocadeMechDriverNetworksV2(test_db_plugin.TestNetworksV2, + TestBrocadeMechDriverV2): + pass + + +class TestBrocadeMechDriverPortsV2(test_db_plugin.TestPortsV2, + TestBrocadeMechDriverV2): + pass + + +class TestBrocadeMechDriverSubnetsV2(test_db_plugin.TestSubnetsV2, + TestBrocadeMechDriverV2): + pass diff --git a/setup.cfg b/setup.cfg index 113145343..4d12fd843 100644 --- a/setup.cfg +++ b/setup.cfg @@ -58,6 +58,7 @@ data_files = etc/neutron/plugins/ml2 = etc/neutron/plugins/ml2/ml2_conf.ini etc/neutron/plugins/ml2/ml2_conf_arista.ini + etc/neutron/plugins/ml2/ml2_conf_brocade.ini etc/neutron/plugins/ml2/ml2_conf_cisco.ini etc/neutron/plugins/bigswitch/restproxy.ini etc/neutron/plugins/ml2/ml2_conf_ofa.ini @@ -171,6 +172,7 @@ neutron.ml2.mechanism_drivers = bigswitch = neutron.plugins.ml2.drivers.mech_bigswitch.driver:BigSwitchMechanismDriver ofagent = neutron.plugins.ml2.drivers.mech_ofagent:OfagentMechanismDriver mlnx = neutron.plugins.ml2.drivers.mlnx.mech_mlnx:MlnxMechanismDriver + brocade = neutron.plugins.ml2.drivers.brocade.mechanism_brocade:BrocadeMechanism neutron.openstack.common.cache.backends = memory = neutron.openstack.common.cache._backends.memory:MemoryBackend